Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,9 @@ offloaded to a compute box (`cs run "pnpm test"`) or CI — do not run them loca

- `mcp-server/` — the `opencode-workbench` MCP server + connector (TypeScript).
Tools: `get_workbench_info`, `inspect_target`, `plan_clone`, `apply_clone`,
`verify_clone`, `install_component`, `list_required_credentials`,
`run_auth_flow`, `bootstrap_host`. Local + SSH transports.
`verify_clone`, `install_component`, `remove_component`, `update_component`,
`list_required_credentials`, `run_auth_flow`, `bootstrap_host`.
Local + SSH transports.
See `mcp-server/README.md` and `docs/architecture/clone-mcp.md`.
- `connector.json` — connector manifest / registration snippet.
- `plugins/` — the `memory.ts` + `doc-tools.ts` OpenCode plugins (portable,
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@

> **A reproducible, self-documenting, and recoverable OpenCode development workstation** — configuration, agent skills, MCP stack, and an MCP server that clones the entire setup onto any Linux machine (locally or over SSH).

**Homepage & remote MCP:** https://opencode-workbench.simonmak.com · `POST https://opencode-workbench.simonmak.com/mcp`

## Purpose

This repository is the **single source of truth** for a complete cloud development workstation. Everything needed to rebuild from scratch is versioned here:
Expand Down
2 changes: 1 addition & 1 deletion connector.json
Original file line number Diff line number Diff line change
Expand Up @@ -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.0.0",
"version": "2.1.0",
"transport": {
"type": "stdio",
"command": "npx",
Expand Down
2 changes: 1 addition & 1 deletion context7.json
Original file line number Diff line number Diff line change
@@ -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 9 tools over stdio, including bootstrap_host for one-call provisioning.",
"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.",
"folders": [
"mcp-server",
"docs",
Expand Down
18 changes: 18 additions & 0 deletions docs/CHANGELOG_WORKBENCH.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,24 @@
> All notable changes to the workbench configuration.
> Format: [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)

## [2026-10-03] — v2.1.0

### Added
- **Web introduction page** at `/` on the hosted connector — `public/index.html`
(self-contained, responsive, no build step) plus `public/favicon.svg`. Fixes
the 404 at `https://opencode-workbench.simonmak.com/`.
- **`remove_component`** and **`update_component`** MCP tools (consent-gated,
value-blind, idempotent) — completing the install → update → remove lifecycle
(raises TDQS server coherence "completeness").

### Changed
- TDQS description pass across tools (behavioral transparency for the mutating
tools: exactly what is overwritten/destroyed, auth prerequisites, duration).
- Tool count 9 → 11 across `README`, `AGENTS.md`, `mcp-server/README.md`,
`docs/architecture/clone-mcp.md`, `docs/architecture/opencode-runtime-snapshot.md`,
`llms.txt`, `context7.json`, `glama.json`.
- `vercel.json`: `GET /` rewrite + `X-Content-Type-Options` / `Referrer-Policy` headers.

## [2026-10-03] — v2.0.0

### Breaking
Expand Down
2 changes: 2 additions & 0 deletions docs/architecture/clone-mcp.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ mcp-server/src/
| `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. |
| `install_component` | Install one component by id (also `confirm`-gated). |
| `remove_component` | Uninstall one component by id (bounded subset; `confirm`-gated). |
| `update_component` | Update/upgrade one component in place (`confirm`-gated). |
| `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. |
Expand Down
6 changes: 3 additions & 3 deletions docs/architecture/opencode-runtime-snapshot.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,9 @@ Notes:

## MCP Tool Surface

The server exposes **9 tools**: `get_workbench_info`, `inspect_target`, `plan_clone`,
`apply_clone`, `verify_clone`, `install_component`, `list_required_credentials`,
`run_auth_flow`, `bootstrap_host`.
The server exposes **11 tools**: `get_workbench_info`, `inspect_target`, `plan_clone`,
`apply_clone`, `verify_clone`, `install_component`, `remove_component`,
`update_component`, `list_required_credentials`, `run_auth_flow`, `bootstrap_host`.

## Agent Configuration

Expand Down
2 changes: 1 addition & 1 deletion glama.json
Original file line number Diff line number Diff line change
Expand Up @@ -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. Nine tools let an agent inspect, plan, apply, and verify 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, 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.",
"repository": "https://github.com/simonmak-ascent/opencode-workbench",
"license": "MIT",
"runtime": "node",
Expand Down
11 changes: 7 additions & 4 deletions llms.txt
Original file line number Diff line number Diff line change
@@ -1,26 +1,29 @@
# OpenCode Workbench (opencode-workbench)

> Open-source (MIT) MCP server + connector that provisions and clones the OpenCode
> workbench onto Linux machines — locally or over SSH. Nine tools cover a
> workbench onto Linux machines — locally or over SSH. Eleven tools cover a
> kernel-up platform scan, a one-call bootstrap, an idempotent, consent-gated
> clone, and value-blind credential guidance. No secret values are ever read,
> transmitted, or written.
> clone, the install/update/remove lifecycle, and value-blind credential guidance.
> No secret values are ever read, transmitted, or written.

## Connect

- Local stdio server: `npx -y @simonmak-ascent/opencode-workbench`
- Hosted (Streamable HTTP): `https://opencode-workbench.simonmak.com/mcp`
- Homepage: https://opencode-workbench.simonmak.com
- Glama: https://glama.ai/mcp/servers/simonmak-ascent/opencode-workbench
- Context7 library ID: `/simonmak-ascent/opencode-workbench`

## MCP tools (9)
## MCP tools (11)

- `get_workbench_info` — 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)
- `verify_clone` — re-check config, env template, and every component's detection
- `install_component` — install a single component by id (also `confirm`-gated)
- `remove_component` — uninstall a single component by id (bounded subset; `confirm`-gated)
- `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
Expand Down
4 changes: 3 additions & 1 deletion mcp-server/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,9 @@ From a checkout, use the local build instead:
| `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. |
| `install_component` | Install a single component by id. |
| `install_component` | Install a single component by id (consent-gated). |
| `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. |
| `list_required_credentials` | Value-blind: which credentials are missing and how to acquire each. |
Expand Down
66 changes: 66 additions & 0 deletions mcp-server/src/clone.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
PREAMBLE,
componentById,
defaultComponentIds,
uninstallScript,
type Component,
} from "./components.js";
import {
Expand Down Expand Up @@ -302,3 +303,68 @@ export async function verify(target: Target, options: CloneOptions = {}): Promis
missing: components.filter((c) => !c.present).map((c) => c.id),
};
}

export interface ComponentActionResult {
target: string;
id: string;
action: "removed" | "updated" | "absent" | "manual";
code: number;
output: string;
}

/**
* Remove one component from a target (reverse of install_component). Idempotent:
* an already-absent component returns `absent`. Components without an automated
* uninstall (system packages) return `manual`. Never touches secret values.
*/
export async function removeComponent(
target: Target,
id: string,
options: CloneOptions = {},
): Promise<ComponentActionResult> {
const c = componentById(id);
if (!c) throw new Error(`unknown component '${id}'`);
const script = uninstallScript(id);
const present = await detect(target, [id]);
if (options.dryRun) {
return { target: target.label, id, action: present[id] ? "removed" : "absent", code: 0, output: "(dry run)" };
}
if (present[id] !== true) {
return { target: target.label, id, action: "absent", code: 0, output: "already absent" };
}
if (!script) {
return { target: target.label, id, action: "manual", code: 0, output: "no automated uninstall; remove manually" };
}
const res = await target.run(withEnv(script, options.workspace || ""), { timeoutMs: 600_000 });
return {
target: target.label,
id,
action: "removed",
code: res.code,
output: `${(res.stdout + res.stderr).trim().slice(0, 1200)}`,
};
}

/**
* Update (re-install / upgrade) one component in place. Idempotent: runs the
* component's install script, which upgrades to the current version.
*/
export async function updateComponent(
target: Target,
id: string,
options: CloneOptions = {},
): Promise<ComponentActionResult> {
const c = componentById(id);
if (!c) throw new Error(`unknown component '${id}'`);
if (options.dryRun) {
return { target: target.label, id, action: "updated", code: 0, output: "(dry run)" };
}
const res = await target.run(withEnv(c.install, options.workspace || ""), { timeoutMs: 600_000 });
return {
target: target.label,
id,
action: "updated",
code: res.code,
output: `${(res.stdout + res.stderr).trim().slice(0, 1200)}`,
};
}
27 changes: 27 additions & 0 deletions mcp-server/src/components.ts
Original file line number Diff line number Diff line change
Expand Up @@ -340,3 +340,30 @@ export function componentById(id: string): Component | undefined {
export function defaultComponentIds(): string[] {
return COMPONENTS.filter((c) => c.tier === "required" || c.tier === "core").map((c) => c.id);
}

/**
* Bounded, idempotent uninstall scripts for the components whose removal is
* automated. Components absent from this map cannot be removed automatically
* (system packages such as git/curl/node) and are reported as `manual`.
*/
export const UNINSTALL_SCRIPTS: Record<string, string> = {
pnpm: `command -v npm >/dev/null 2>&1 && npm rm -g pnpm >/dev/null 2>&1 || true`,
opencode: `rm -rf "$WB_HOME/.opencode" "$WB_HOME/.local/bin/opencode"
command -v npm >/dev/null 2>&1 && npm rm -g opencode-ai >/dev/null 2>&1 || true`,
uv: `rm -f "$WB_HOME/.local/bin/uv" "$WB_HOME/.local/bin/uvx"`,
"research-mcps": `command -v npm >/dev/null 2>&1 && npm rm -g --silent @simonmak-ascent/primary-sources-mcp >/dev/null 2>&1 || true`,
"npm-mcps": `command -v npm >/dev/null 2>&1 || { warn "npm missing"; exit 1; }
${CORE_NPM_MCPS.map((p) => ` npm rm -g --silent ${p} >/dev/null 2>&1 || true`).join("\n")}`,
"vendored-mcps": `rm -rf "$WB_HOME/.local/bin/perplexity-agent-mcp" "$WB_HOME/.local/bin/browserless-mcp"`,
"github-mcp": `rm -f "$WB_HOME/.local/bin/github-mcp-server"`,
skills: `rm -rf "$WB_HOME/.config/opencode/skills"`,
plugins: `rm -rf "$WB_HOME/.config/opencode/plugins"`,
"docker-containers": `command -v docker >/dev/null 2>&1 || { warn "docker missing"; exit 1; }
docker rm -f pg-memory browserless >/dev/null 2>&1 || true`,
"playwright-browsers": `rm -rf "$WB_HOME/.cache/ms-playwright"`,
};

/** Uninstall script for a component id, or undefined when removal is manual. */
export function uninstallScript(id: string): string | undefined {
return UNINSTALL_SCRIPTS[id];
}
Loading
Loading