From 59bf9b4501419a6b366fac9150ea8a2e1831645d Mon Sep 17 00:00:00 2001 From: Simon Mak Date: Sat, 3 Oct 2026 07:56:52 +0800 Subject: [PATCH] =?UTF-8?q?feat(mcp)!:=20TDQS=20pass=20=E2=80=94=20rename?= =?UTF-8?q?=20bootstrap=5Fhost->provision=5Fhost,=20get=5Fworkbench=5Finfo?= =?UTF-8?q?->describe=5Fworkbench=20(v3.0.0)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - naming: standard provisioning verb + 'describe' for the info tool (TDQS naming) - disambiguation: shorten/de-overlap plan_clone vs apply_clone - parameter-semantics + behavioral-transparency lifts on verify/list/update - completeness 5, tool-count 5 in TDQS server coherence - verified: 55/55 tests, build ok, mcp-tdqs lint 0 errors --- AGENTS.md | 4 +- WORKFLOW_STATE.md | 4 +- connector.json | 2 +- context7.json | 4 +- docs/CHANGELOG_WORKBENCH.md | 21 ++++++++++ docs/architecture/clone-mcp.md | 4 +- .../architecture/opencode-runtime-snapshot.md | 4 +- docs/recovery/recovery-gap-analysis.md | 2 +- glama.json | 2 +- llms.txt | 6 +-- mcp-server/README.md | 4 +- mcp-server/src/bootstrap.ts | 2 +- mcp-server/src/platform.ts | 2 +- mcp-server/src/tools.ts | 40 +++++++++---------- mcp-server/test/server.test.ts | 12 +++--- package.json | 2 +- public/index.html | 8 ++-- server.json | 4 +- 18 files changed, 74 insertions(+), 53 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 08f9b11..85c5e53 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -13,9 +13,9 @@ offloaded to a compute box (`cs run "pnpm test"`) or CI — do not run them loca ## Repo structure - `mcp-server/` — the `opencode-workbench` MCP server + connector (TypeScript). - Tools: `get_workbench_info`, `inspect_target`, `plan_clone`, `apply_clone`, + Tools: `describe_workbench`, `inspect_target`, `plan_clone`, `apply_clone`, `verify_clone`, `install_component`, `remove_component`, `update_component`, - `list_required_credentials`, `run_auth_flow`, `bootstrap_host`. + `list_required_credentials`, `run_auth_flow`, `provision_host`. Local + SSH transports. See `mcp-server/README.md` and `docs/architecture/clone-mcp.md`. - `connector.json` — connector manifest / registration snippet. diff --git a/WORKFLOW_STATE.md b/WORKFLOW_STATE.md index faafbf5..778935d 100644 --- a/WORKFLOW_STATE.md +++ b/WORKFLOW_STATE.md @@ -10,7 +10,7 @@ | Phase #0 — Verify Docs | **COMPLETED** | | Phase #1 — Research | **COMPLETED** | | Phase #2 — Architecture | **COMPLETED** | -| Phase #3 — Implementation | **COMPLETED** (clone MCP + bootstrap_host shipped) | +| Phase #3 — Implementation | **COMPLETED** (clone MCP + provision_host shipped) | | VDD chain (Phase 0–8) | **COMPLETED** — `constitution.md` + `vdd/` (gates G1–G7 PASS) | | Distribution | In progress — npm published; Vercel live; registry publish prepared | @@ -29,7 +29,7 @@ Timestamp: 2026-10-03 |------|--------|--------| | Clone engine (inspect/plan/apply/verify) | DONE | `mcp-server/src/clone.ts` | | Consent gate + value-blind credentials | DONE | `apply_clone`/`install_component` `confirm:true`; `list_required_credentials`, `run_auth_flow` | -| One-call provisioning | DONE | `bootstrap_host` (platform scan + upgrade plan + OpenCode pin + verify + `help`) | +| One-call provisioning | DONE | `provision_host` (platform scan + upgrade plan + OpenCode pin + verify + `help`) | | Tests + CI | DONE | 49 tests; `mcp-server.yml` | ### VDD chain — DELIVERED diff --git a/connector.json b/connector.json index 71e72ce..4602665 100644 --- a/connector.json +++ b/connector.json @@ -3,7 +3,7 @@ "name": "opencode-workbench", "title": "OpenCode Workbench", "description": "Clones the OpenCode workbench installation onto Linux machines, locally or over SSH.", - "version": "2.1.0", + "version": "3.0.0", "transport": { "type": "stdio", "command": "npx", diff --git a/context7.json b/context7.json index 6c8e715..affc6f4 100644 --- a/context7.json +++ b/context7.json @@ -1,7 +1,7 @@ { "$schema": "https://context7.com/schema/context7.json", "projectTitle": "OpenCode Workbench (opencode-workbench)", - "description": "Provisions and clones the OpenCode workbench onto Linux machines — locally or over SSH — reproducing the config, MCP servers (35), agent skills (45), plugins and Docker tooling. Exposes 11 tools over stdio, including bootstrap_host for one-call provisioning and remove_component/update_component for the lifecycle.", + "description": "Provisions and clones the OpenCode workbench onto Linux machines — locally or over SSH — reproducing the config, MCP servers (35), agent skills (45), plugins and Docker tooling. Exposes 11 tools over stdio, including provision_host for one-call provisioning and a full install/update/remove lifecycle.", "folders": [ "mcp-server", "docs", @@ -9,7 +9,7 @@ ], "rules": [ "Run locally with `npx -y @simonmak-ascent/opencode-workbench` (Node stdio MCP server; no API key).", - "Drive a clone with the tool order `get_workbench_info` → `inspect_target` → `plan_clone` → `apply_clone` → `verify_clone`; use `install_component` to install a single component by id, and `bootstrap_host` for a one-call end-to-end provision (platform scan + OpenCode + apply + verify; `help:true` documents parameters).", + "Drive a clone with the tool order `describe_workbench` → `inspect_target` → `plan_clone` → `apply_clone` → `verify_clone`; use `install_component`/`remove_component`/`update_component` for per-component lifecycle, and `provision_host` for a one-call end-to-end provision (platform scan + OpenCode + apply + verify; `help:true` documents parameters).", "`apply_clone` and `install_component` are consent-gated: without `confirm:true` they return a plan and change nothing.", "Every tool takes a `target` of `{mode: \"local\"}` or `{mode: \"ssh\", host, user, port?, identityFile?}`.", "The connector never reads, transmits or writes secret values; `apply_clone` writes an empty `~/.env.workbench` template (mode 600) listing the `{env:VAR}` names to fill in.", diff --git a/docs/CHANGELOG_WORKBENCH.md b/docs/CHANGELOG_WORKBENCH.md index 78375c7..0e33369 100644 --- a/docs/CHANGELOG_WORKBENCH.md +++ b/docs/CHANGELOG_WORKBENCH.md @@ -3,6 +3,27 @@ > All notable changes to the workbench configuration. > Format: [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) +## [2026-10-03] — v3.0.0 + +### Breaking +- Renamed the MCP tool `bootstrap_host` → **`provision_host`** for a clearer + standard provisioning verb (TDQS naming). Clients that call `bootstrap_host` + must update. +- Renamed the MCP tool `get_workbench_info` → **`describe_workbench`** (the tool + describes the server; "describe" reads as its verb). Clients that call + `get_workbench_info` must update. + +### Added +- (none) + +### Changed +- TDQS description pass: behavioral transparency (what is overwritten/destroyed, + auth prerequisites, duration) and parameter semantics across the tool set; + boundary phrasing replaces "do not combine" prohibitions (disambiguation). +- `get_component_status` was prototyped and removed as redundant with + `verify_clone`/`describe_workbench` (it lowered TDQS coherence). Tool count + remains 11. + ## [2026-10-03] — v2.1.0 ### Added diff --git a/docs/architecture/clone-mcp.md b/docs/architecture/clone-mcp.md index 7eafcae..510bdb8 100644 --- a/docs/architecture/clone-mcp.md +++ b/docs/architecture/clone-mcp.md @@ -37,7 +37,7 @@ mcp-server/src/ | Tool | Purpose | |------|---------| -| `get_workbench_info` | Repo, components by tier, optional MCP add-ons. | +| `describe_workbench` | Repo, components by tier, optional MCP add-ons. | | `inspect_target` | OS/arch/pkg-manager/Node/Docker/OpenCode and the *names* of credentials present. | | `plan_clone` | Diff the target against the profile → steps (with privileged-command previews). | | `apply_clone` | Consent-gated: without `confirm:true` returns the plan; with it, clones + installs. | @@ -47,7 +47,7 @@ mcp-server/src/ | `verify_clone` | Re-check config, env template, and every component. | | `list_required_credentials` | Value-blind: which keys are missing and how to acquire each. | | `run_auth_flow` | Emit-and-verify acquisition guidance for one credential. | -| `bootstrap_host` | One-call provisioning: kernel-up platform scan + dry-run upgrade plan, latest-stable OpenCode with recorded version, apply + verify; `help:true` documents every parameter without touching the target. Consent-gated. | +| `provision_host` | One-call provisioning: kernel-up platform scan + dry-run upgrade plan, latest-stable OpenCode with recorded version, apply + verify; `help:true` documents every parameter without touching the target. Consent-gated. | ### Connector model diff --git a/docs/architecture/opencode-runtime-snapshot.md b/docs/architecture/opencode-runtime-snapshot.md index e848f28..874b5be 100644 --- a/docs/architecture/opencode-runtime-snapshot.md +++ b/docs/architecture/opencode-runtime-snapshot.md @@ -35,9 +35,9 @@ Notes: ## MCP Tool Surface -The server exposes **11 tools**: `get_workbench_info`, `inspect_target`, `plan_clone`, +The server exposes **11 tools**: `describe_workbench`, `inspect_target`, `plan_clone`, `apply_clone`, `verify_clone`, `install_component`, `remove_component`, -`update_component`, `list_required_credentials`, `run_auth_flow`, `bootstrap_host`. +`update_component`, `list_required_credentials`, `run_auth_flow`, `provision_host`. ## Agent Configuration diff --git a/docs/recovery/recovery-gap-analysis.md b/docs/recovery/recovery-gap-analysis.md index 23dfad9..d173ad1 100644 --- a/docs/recovery/recovery-gap-analysis.md +++ b/docs/recovery/recovery-gap-analysis.md @@ -68,7 +68,7 @@ - Agent skills: **45** (was 28). - Clone MCP hardened and extended: consent gate (`confirm:true` on `apply_clone`/`install_component`), value-blind credential tools - (`list_required_credentials`, `run_auth_flow`), and `bootstrap_host` (one-call + (`list_required_credentials`, `run_auth_flow`), and `provision_host` (one-call provisioning with a kernel-up scan and a recorded OpenCode version pin). - Distribution: npm `@simonmak-ascent/opencode-workbench@1.1.2` published; hosted connector live at `https://opencode-workbench.simonmak.com/mcp`; `server.json` diff --git a/glama.json b/glama.json index 75c3e1b..01a4207 100644 --- a/glama.json +++ b/glama.json @@ -2,7 +2,7 @@ "$schema": "https://glama.ai/mcp/schemas/server.json", "maintainers": ["simonmak-ascent"], "name": "opencode-workbench", - "description": "One-command reproducibility for your AI-agent workstation. Eleven tools let an agent inspect, plan, apply, verify, update, and remove an idempotent, consent-gated clone of the full OpenCode stack — the OpenCode CLI, 35 MCP servers, agent skills, plugins, and Docker infrastructure — onto any Linux machine, over SSH or locally, with zero secrets ever written. bootstrap_host does it end-to-end, including a kernel-up platform scan and a recorded OpenCode version pin.", + "description": "One-command reproducibility for your AI-agent workstation. Eleven tools let an agent inspect, plan, apply, verify, and manage an idempotent, consent-gated clone of the full OpenCode stack — the OpenCode CLI, 35 MCP servers, agent skills, plugins, and Docker infrastructure — onto any Linux machine, over SSH or locally, with zero secrets ever written. provision_host does it end-to-end, including a kernel-up platform scan and a recorded OpenCode version pin.", "repository": "https://github.com/simonmak-ascent/opencode-workbench", "license": "MIT", "runtime": "node", diff --git a/llms.txt b/llms.txt index bf9076a..73840e7 100644 --- a/llms.txt +++ b/llms.txt @@ -16,7 +16,7 @@ ## MCP tools (11) -- `get_workbench_info` — describe this server: repo, components by tier, optional MCP add-ons +- `describe_workbench` — describe this server: repo, components by tier, optional MCP add-ons - `inspect_target` — probe a machine: OS, arch, package manager, Node, Docker, OpenCode, home and npm-global paths - `plan_clone` — diff the target against the profile → install / present / manual steps (with command previews) - `apply_clone` — consent-gated: without `confirm:true` returns the plan; with it, clones + installs the profile (idempotent) @@ -26,7 +26,7 @@ - `update_component` — update/upgrade a single component in place (`confirm`-gated) - `list_required_credentials` — value-blind: which credentials are missing and how to acquire each - `run_auth_flow` — emit-and-verify acquisition guidance for one credential -- `bootstrap_host` — one-call provisioning: kernel-up platform scan + dry-run upgrade plan, latest-stable OpenCode with recorded version, apply + verify; `help:true` documents every parameter without touching the target +- `provision_host` — one-call provisioning: kernel-up platform scan + dry-run upgrade plan, latest-stable OpenCode with recorded version, apply + verify; `help:true` documents every parameter without touching the target Every tool takes a `target`: @@ -35,7 +35,7 @@ Every tool takes a `target`: ``` `apply_clone` options: `components[]`, `workspace`, `profileUrl`, `skipRepo`, `dryRun`, `confirm`. -`bootstrap_host` options: `help`, `confirm`, `upgrade`, `assumeYes`, `opencodeVersion`, `dryRun`. +`provision_host` options: `help`, `confirm`, `upgrade`, `assumeYes`, `opencodeVersion`, `dryRun`. ## Docs diff --git a/mcp-server/README.md b/mcp-server/README.md index d6fc459..7a986ac 100644 --- a/mcp-server/README.md +++ b/mcp-server/README.md @@ -34,7 +34,7 @@ From a checkout, use the local build instead: | Tool | Purpose | |------|---------| -| `get_workbench_info` | Repo, components by tier, optional MCP add-ons. | +| `describe_workbench` | Repo, components by tier, optional MCP add-ons. | | `inspect_target` | OS/arch/pkg-manager/Node/Docker/OpenCode/home/npm paths. | | `plan_clone` | Diff the target against the profile → steps to install / present / manual. | | `apply_clone` | Clone the profile + install missing components + write rendered config. Idempotent. | @@ -42,7 +42,7 @@ From a checkout, use the local build instead: | `remove_component` | Uninstall a single component by id (bounded subset; consent-gated). | | `update_component` | Update/upgrade a single component in place (consent-gated). | | `verify_clone` | Re-check config, env template, and every component. | -| `bootstrap_host` | One-call provisioning: kernel-up platform scan + dry-run upgrade plan, latest-stable OpenCode + version pin, apply, verify; `help:true` documents parameters without touching the target. | +| `provision_host` | One-call provisioning: kernel-up platform scan + dry-run upgrade plan, latest-stable OpenCode + version pin, apply, verify; `help:true` documents parameters without touching the target. | | `list_required_credentials` | Value-blind: which credentials are missing and how to acquire each. | | `run_auth_flow` | Emit-and-verify acquisition guidance for one credential. | diff --git a/mcp-server/src/bootstrap.ts b/mcp-server/src/bootstrap.ts index 43c21c8..54bcc00 100644 --- a/mcp-server/src/bootstrap.ts +++ b/mcp-server/src/bootstrap.ts @@ -1,5 +1,5 @@ /** - * `bootstrap_host` orchestration: take a bare Linux target (local or SSH) to a + * `provision_host` orchestration: take a bare Linux target (local or SSH) to a * VDD-configured OpenCode workstation in one call. * * Order: inspect → platform plan (dry-run unless `upgrade`) → apply the profile diff --git a/mcp-server/src/platform.ts b/mcp-server/src/platform.ts index 7696118..67c5c0e 100644 --- a/mcp-server/src/platform.ts +++ b/mcp-server/src/platform.ts @@ -1,5 +1,5 @@ /** - * Pure, side-effect-free platform logic for `bootstrap_host`. + * Pure, side-effect-free platform logic for `provision_host`. * * Nothing here touches the network, the filesystem or a target: every function * takes plain input and returns plain output, so the whole cross-distro mapping diff --git a/mcp-server/src/tools.ts b/mcp-server/src/tools.ts index 3a0d7e4..c2d7638 100644 --- a/mcp-server/src/tools.ts +++ b/mcp-server/src/tools.ts @@ -201,11 +201,11 @@ function fail(message: string, error: unknown) { export function registerTools(server: McpServer): void { server.registerTool( - "get_workbench_info", + "describe_workbench", { title: "Workbench server info", description: - "Describe this MCP server and its capabilities without contacting any target: repository URL, the default component set, components grouped by tier (required/core/optional), and optional MCP add-ons. Read-only and static — it reads only the bundled profile. Use it first to look up a component id for install_component or to see available add-ons; use inspect_target for a machine's live state.", + "Describe this MCP server and its capabilities without contacting any target: repository URL, the default component set, components grouped by tier (required/core/optional), and optional MCP add-ons. Read-only and static — it reads only the bundled profile, makes no network call, and touches no target. Use it to look up a component id for install_component/remove_component/update_component, or to see available add-ons; use inspect_target for a machine's live state.", inputSchema: {}, outputSchema: infoOutput, annotations: { readOnlyHint: true, idempotentHint: true }, @@ -232,7 +232,7 @@ export function registerTools(server: McpServer): void { { title: "Inspect a Linux target", description: - "Probe one Linux machine — this host (`local`) or a remote host over SSH (`ssh`) — and report its OS, kernel, architecture, package manager, Node/npm, OpenCode, Docker, and per-tool detection flags, plus the names (never values) of credentials present. Read-only: one shell probe, changes nothing. Use it to see what a clone would touch, then plan_clone to turn that into an ordered plan; for a first-time end-to-end provision use bootstrap_host. It installs nothing.", + "Probe one Linux machine — this host (`local`) or a remote host over SSH (`ssh`) — and report its OS, kernel, architecture, package manager, Node/npm, OpenCode, Docker, and per-tool detection flags, plus the names (never values) of credentials present. Read-only: it runs a single shell probe on the target, installs nothing, writes nothing, and contacts no external service; `target.cwd` sets the working directory for relative checks, and `target.port`/`identityFile` are passed to ssh. Use it to see what a clone would touch, then plan_clone to turn that into an ordered plan; for a first-time end-to-end provision use provision_host.", inputSchema: { target: targetShape }, outputSchema: inspectOutput, annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: true }, @@ -251,7 +251,7 @@ export function registerTools(server: McpServer): void { { title: "Plan a Workbench clone", description: - "Compare a target against the portable Workbench profile and return each component as `install`, `present`, or `manual`, with privileged-command previews and the list to install. Read-only; installs nothing and needs SSH for `mode: ssh`. Use it before apply_clone for granular control of an existing profile; for a first-time end-to-end provision use bootstrap_host instead, which composes inspect + plan + apply + verify.", + "Return a read-only plan for a target: compare it against the Workbench profile and list each component as `install`, `present`, or `manual`, with the commands that would run. It clones nothing and writes nothing; `components` narrows the plan and `workspace` sets where the profile repo is expected. Use it to preview before apply_clone (which performs the changes) or provision_host (which plans and applies in one call).", inputSchema: { target: targetShape, ...optionsShape }, outputSchema: planOutput, annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: true }, @@ -270,7 +270,7 @@ export function registerTools(server: McpServer): void { { title: "Apply a Workbench clone", description: - "Install the Workbench profile on a target: clone the profile repo and install missing components (repo, OpenCode CLI, Node/pnpm, npm/vendored/research MCPs, skills, plugins, optional Docker). Writes and OVERWRITES `/.config/opencode/opencode.json` and copies `AGENTS.md`; on an existing clone it runs `git reset --hard origin/main` (discarding local edits in the profile repo); global npm installs can take minutes and require write permission (plus SSH access for `mode:ssh`). Writes an empty, names-only `~/.env.workbench` (mode 600) and never secret values. Idempotent — present components are skipped. Consent-gated: without `confirm:true` it returns the plan and changes nothing. Use it for a component subset or per-component control on an already-provisioned host; for a first-time provision use bootstrap_host, and do not call both for the same host and change.", + "Apply the Workbench profile to a target: clone the repo and install missing components (OpenCode CLI, Node/pnpm, MCP servers, skills, plugins, optional Docker). It OVERWRITES `/.config/opencode/opencode.json`, copies `AGENTS.md`, resets an existing profile repo to `origin/main`, and runs global installs that need write permission (plus SSH for `mode:ssh`) and can take minutes. Writes a names-only `~/.env.workbench` (mode 600); never secret values. Consent-gated and idempotent: without `confirm:true` it returns the plan and changes nothing. To preview only, use plan_clone; for a first-time provision, use provision_host.", inputSchema: { target: targetShape, confirm: z.boolean().optional().describe("Set true to actually install. When absent, the call returns a plan and makes no changes."), @@ -306,7 +306,7 @@ export function registerTools(server: McpServer): void { { title: "Verify a Workbench clone", description: - "Re-check a target after a clone: confirm `opencode.json` and `~/.env.workbench` exist and re-detect every component, returning the missing list. Read-only; needs SSH for `mode: ssh`. Use it after apply_clone; bootstrap_host runs it automatically, so call verify_clone directly only for a targeted re-check. For a pre-clone preview use plan_clone.", + "Re-check a target after a clone: return a per-component `present`/`missing` list plus whether `opencode.json` and `~/.env.workbench` exist. Read-only: it checks files and re-runs detection, making no changes; `components` narrows the check. Use it after apply_clone; provision_host runs it automatically, so call verify_clone directly only for a targeted re-check. For a pre-clone preview use plan_clone.", inputSchema: { target: targetShape, ...optionsShape }, outputSchema: verifyOutput, annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: true }, @@ -325,10 +325,10 @@ export function registerTools(server: McpServer): void { { title: "Install one Workbench component", description: - "Install a single Workbench component by id (e.g. `node`, `opencode`, `npm-mcps`, `skills`) on a target; `component` must be an id from get_workbench_info. Overwrites that component's files when present (e.g. `skills`/`plugins` replace the copies under `/.config/opencode`); global installs need write permission and can take minutes. Idempotent and consent-gated: without `confirm:true` it returns the plan. Use it for one targeted component; use apply_clone for a component subset, or bootstrap_host for a first-time end-to-end provision — do not combine them for the same host and change.", + "Install a single Workbench component by id (e.g. `node`, `opencode`, `npm-mcps`, `skills`) on a target; `component` must be an id from describe_workbench. Overwrites that component's files when present (e.g. `skills`/`plugins` replace the copies under `/.config/opencode`); global installs need write permission and can take minutes. Idempotent and consent-gated: without `confirm:true` it returns the plan. Use it for one targeted component on an existing host; use apply_clone for a subset, or provision_host for a first-time end-to-end provision.", inputSchema: { target: targetShape, - component: z.string().describe("Component id from get_workbench_info."), + component: z.string().describe("Component id from describe_workbench."), workspace: z.string().optional().describe("Target directory for the profile repo."), confirm: z.boolean().optional().describe("Set true to actually install. When absent, the call returns a plan and makes no changes."), }, @@ -369,10 +369,10 @@ export function registerTools(server: McpServer): void { { title: "Remove one Workbench component", description: - "Uninstall a single Workbench component by id from a target — the inverse of install_component — for a bounded, documented subset (`opencode`, `pnpm`, `uv`, `npm-mcps`, `vendored-mcps`, `research-mcps`, `github-mcp`, `skills`, `plugins`, `docker-containers`, `playwright-browsers`); `component` must be an id from get_workbench_info. Idempotent: an already-absent component reports `absent`; components with no automated uninstall (system packages such as git/curl/node) report `manual` and require manual removal. Consent-gated and destructive: without `confirm:true` it returns a preview and changes nothing. Removes only that component's files — it does not delete `opencode.json` or the profile repo. Use it to tear down one component; to remove several, call it per id.", + "Uninstall a single Workbench component by id from a target — the inverse of install_component — for a bounded, documented subset (`opencode`, `pnpm`, `uv`, `npm-mcps`, `vendored-mcps`, `research-mcps`, `github-mcp`, `skills`, `plugins`, `docker-containers`, `playwright-browsers`); `component` must be an id from describe_workbench. Idempotent: an already-absent component reports `absent`; components with no automated uninstall (system packages such as git/curl/node) report `manual` and require manual removal. Consent-gated and destructive: without `confirm:true` it returns a preview and changes nothing. Removes only that component's files — it does not delete `opencode.json` or the profile repo. Use it to tear down one component; to remove several, call it per id.", inputSchema: { target: targetShape, - component: z.string().describe("Component id from get_workbench_info."), + component: z.string().describe("Component id from describe_workbench."), workspace: z.string().optional().describe("Profile repo directory (used by components whose removal needs it)."), confirm: z.boolean().optional().describe("Set true to actually remove. When absent, the call returns a preview and makes no changes."), dryRun: z.boolean().optional().describe("Report the action without changing anything."), @@ -401,10 +401,10 @@ export function registerTools(server: McpServer): void { { title: "Update one Workbench component", description: - "Update (re-install / upgrade in place) a single Workbench component by id on a target by re-running its install script, which fetches the current version (e.g. `opencode` re-runs the official installer; `npm-mcps` reinstalls the latest globals). Idempotent and consent-gated: without `confirm:true` it returns a preview and changes nothing. Requires write permission; global installs can take minutes. Use it to refresh one component; use apply_clone to reconcile the whole profile, or bootstrap_host to also upgrade the platform and pin the OpenCode version.", + "Update one Workbench component in place by id: re-run its install script to fetch the current version (e.g. `opencode` re-runs the official installer; `npm-mcps` reinstalls the latest globals). OVERWRITES the component's files and needs write permission; global installs can take minutes. `workspace` points at the profile repo when needed; `dryRun` previews. Consent-gated: without `confirm:true` it returns a preview. For the whole profile use apply_clone; for the platform plus an OpenCode version pin use provision_host.", inputSchema: { target: targetShape, - component: z.string().describe("Component id from get_workbench_info."), + component: z.string().describe("Component id from describe_workbench."), workspace: z.string().optional().describe("Profile repo directory (used by components whose update needs it)."), confirm: z.boolean().optional().describe("Set true to actually update. When absent, the call returns a preview and makes no changes."), dryRun: z.boolean().optional().describe("Report the action without changing anything."), @@ -433,7 +433,7 @@ export function registerTools(server: McpServer): void { { title: "List required credentials", description: - "List the credentials the profile references, which the target already provides, and how to acquire each missing one (label, purpose, provider URL, method, exact command). Value-blind: checks only whether env vars are set and never reads or returns values. Use it after plan_clone to see what a clone would leave degraded; pair it with run_auth_flow to act on one credential.", + "List the credentials the profile references, which the target already provides, and how to acquire each missing one. Value-blind: it reads only whether each env var is set — no values, no network, no writes. Returns `present`/`missing` var-name arrays plus one guidance record per credential (`var`, `purpose`, `url`, `method`, `command`). Use it after plan_clone to see what would degrade; act on one credential with run_auth_flow.", inputSchema: { target: targetShape }, outputSchema: credentialsOutput, annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: true }, @@ -462,7 +462,7 @@ export function registerTools(server: McpServer): void { { title: "Acquire one credential (best-effort)", description: - "Return the acquisition plan for one credential on a target: the provider URL, the exact non-interactive command when one exists (e.g. `opencode auth login`, `opencode mcp auth vercel`, `gh auth login`), and whether it is already present. Emit-and-verify: it does not run interactive flows or handle secret values. Use it for a single credential surfaced by list_required_credentials; it does not replace that listing.", + "Return the acquisition plan for one credential on a target: the provider URL, the exact non-interactive command when one exists (e.g. `opencode auth login`, `opencode mcp auth vercel`, `gh auth login`), and whether it is already present. Read-only and value-blind: it emits guidance and re-checks presence; it does not run the commands or handle secret values. `var` is the env var name from list_required_credentials. Use it for a single credential; it does not replace that listing.", inputSchema: { target: targetShape, var: z.string().describe("Credential env var name from list_required_credentials (e.g. OPENCODE_API_KEY)."), @@ -502,9 +502,9 @@ export function registerTools(server: McpServer): void { ); const bootstrapHelp = { - name: "bootstrap_host", + name: "provision_host", description: - "Provision a bare Linux target (local or SSH) into a VDD-configured OpenCode workstation in one call: scan the platform from the kernel up, return a dry-run upgrade plan, install the latest stable OpenCode and record its version, apply the VDD profile, and verify.", + "Provision a bare Linux host (local or SSH) into a VDD-configured OpenCode workstation in one call: scan the platform from the kernel up, return a dry-run upgrade plan, install the latest stable OpenCode and record its version, apply the VDD profile, and verify.", parameters: [ { name: "target", type: "object", required: false, description: "Machine to provision; required unless help=true. { mode: local|ssh, host?, user?, port?, identityFile?, cwd? }." }, { name: "help", type: "boolean", required: false, description: "Return this parameter reference and contact no target." }, @@ -582,11 +582,11 @@ export function registerTools(server: McpServer): void { }); server.registerTool( - "bootstrap_host", + "provision_host", { - title: "Bootstrap a bare Linux host", + title: "Provision a bare Linux host", description: - "Provision a bare Linux target end-to-end in one call: scan the platform from the kernel up and return a dry-run upgrade plan, install the latest stable OpenCode and record the resolved version, apply the VDD profile config, and verify parity. Requires SSH access for `mode:ssh`; a full run can take many minutes (platform upgrade + installs). Pass `help:true` for full parameter documentation without contacting the target. Consent-gated: without `confirm:true` it returns a plan and changes nothing. Set `upgrade:true` (requires root/sudo) to run the platform upgrade; default is plan-only. Never reads or transmits secret values. Use it for a first-time provision of a bare host — do NOT also call inspect_target, plan_clone, apply_clone, or install_component for the same host and change; use those granular tools instead when you need step-by-step control of an existing profile.", + "Provision a bare Linux host end-to-end in one call: scan the platform from the kernel up and return a dry-run upgrade plan, install the latest stable OpenCode and record the resolved version, apply the VDD profile, and verify parity. It makes no change until `confirm:true`; `upgrade:true` needs root/sudo, and the installs mean a full run can take many minutes; it requires SSH for `mode:ssh` and OVERWRITES `/.config/opencode/opencode.json`. Pass `help:true` for full parameter documentation without contacting the target. Never reads or transmits secret values. Boundary: use provision_host for a first-time provision of a bare host; use the granular tools (inspect_target, plan_clone, apply_clone, verify_clone, install_component, update_component, remove_component) for step-by-step changes to an existing host.", inputSchema: { target: targetShape.optional(), help: z.boolean().optional().describe("Return parameter documentation and skip all target access."), @@ -639,7 +639,7 @@ export function registerTools(server: McpServer): void { const result = await bootstrap(spec, base); return structured({ mode: "result" as const, result }); } catch (e) { - return fail("bootstrap_host failed", e); + return fail("provision_host failed", e); } }, ); diff --git a/mcp-server/test/server.test.ts b/mcp-server/test/server.test.ts index 18841a0..7471b2b 100644 --- a/mcp-server/test/server.test.ts +++ b/mcp-server/test/server.test.ts @@ -21,12 +21,12 @@ describe("opencode-workbench server", () => { const names = tools.map((t) => t.name).sort(); expect(names).toEqual([ "apply_clone", - "bootstrap_host", - "get_workbench_info", + "describe_workbench", "inspect_target", "install_component", "list_required_credentials", "plan_clone", + "provision_host", "remove_component", "run_auth_flow", "update_component", @@ -35,9 +35,9 @@ describe("opencode-workbench server", () => { await client.close(); }); - it("documents bootstrap_host without touching a target", async () => { + it("documents provision_host without touching a target", async () => { const client = await connect(); - const res = await client.callTool({ name: "bootstrap_host", arguments: { help: true } }); + const res = await client.callTool({ name: "provision_host", arguments: { help: true } }); expect(res.isError).toBeFalsy(); const out = res.structuredContent as { mode: string; @@ -49,9 +49,9 @@ describe("opencode-workbench server", () => { await client.close(); }); - it("answers get_workbench_info with components and optional MCPs", async () => { + it("answers describe_workbench with components and optional MCPs", async () => { const client = await connect(); - const res = await client.callTool({ name: "get_workbench_info", arguments: {} }); + const res = await client.callTool({ name: "describe_workbench", arguments: {} }); const info = res.structuredContent as { components: Array<{ id: string; tier: string }>; optionalMcp: string[]; diff --git a/package.json b/package.json index a3929c9..3590d86 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@simonmak-ascent/opencode-workbench", "mcpName": "io.github.simonmak-ascent/opencode-workbench", - "version": "2.1.0", + "version": "3.0.0", "description": "MCP server and connector that clones the OpenCode workbench configuration onto Linux machines (locally or over SSH).", "license": "MIT", "type": "module", diff --git a/public/index.html b/public/index.html index e9eae6c..1c3024b 100644 --- a/public/index.html +++ b/public/index.html @@ -157,7 +157,7 @@

11 tools over one MCP surface

ToolWhat it doesMode - get_workbench_infoRepo, components by tier, optional MCP add-ons.read-only + describe_workbenchRepo, components by tier, optional MCP add-ons.read-only inspect_targetOS, kernel, arch, package manager, Node, Docker, OpenCode, credential names.read-only plan_cloneDiff a target against the profile → install / present / manual steps.read-only apply_cloneClone the profile + install missing components; consent-gated.mutating @@ -167,7 +167,7 @@

11 tools over one MCP surface

verify_cloneRe-check config, env template, and every component.read-only list_required_credentialsWhich credentials are missing and how to acquire each (value-blind).read-only run_auth_flowEmit-and-verify acquisition guidance for one credential.read-only - bootstrap_hostOne-call end-to-end provision: platform scan + upgrade plan, latest-stable OpenCode with recorded version, apply, verify; help:true.mutating + provision_hostOne-call end-to-end provision: platform scan + upgrade plan, latest-stable OpenCode with recorded version, apply, verify; help:true.mutating @@ -202,10 +202,10 @@

Then drive a clone

inspect_target  →  plan_clone  →  apply_clone { confirm: true }  →  verify_clone
 
 # or one call, end to end:
-bootstrap_host  { target: { mode: "ssh", host: "box", user: "me" }, confirm: true }
+provision_host  { target: { mode: "ssh", host: "box", user: "me" }, confirm: true }
 
 # see every parameter:
-bootstrap_host  { help: true }
+provision_host { help: true }
diff --git a/server.json b/server.json index 059bd6c..2905ac7 100644 --- a/server.json +++ b/server.json @@ -2,7 +2,7 @@ "$schema": "https://static.modelcontextprotocol.io/schemas/2025-07-09/server.schema.json", "name": "io.github.simonmak-ascent/opencode-workbench", "description": "Provision an OpenCode workbench and MCP stack on any Linux box, local or over SSH.", - "version": "2.1.0", + "version": "3.0.0", "repository": { "url": "https://github.com/simonmak-ascent/opencode-workbench", "source": "github" @@ -11,7 +11,7 @@ { "registryType": "npm", "identifier": "@simonmak-ascent/opencode-workbench", - "version": "2.1.0", + "version": "3.0.0", "transport": { "type": "stdio" } } ],