Files
mcpctl/package.json

66 lines
2.5 KiB
JSON
Raw Permalink Normal View History

2026-02-21 03:10:39 +00:00
{
"name": "mcpctl",
"version": "0.0.1",
2026-02-21 03:10:39 +00:00
"private": true,
"description": "kubectl-like CLI for managing MCP servers",
"type": "module",
"scripts": {
"build": "pnpm -r run build",
"test": "vitest",
"test:run": "vitest run",
"test:coverage": "vitest run --coverage",
"test:smoke": "pnpm --filter mcplocal run test:smoke",
2026-02-21 03:10:39 +00:00
"test:ui": "vitest --ui",
"lint": "eslint 'src/*/src/**/*.ts'",
"lint:fix": "eslint 'src/*/src/**/*.ts' --fix",
"clean": "pnpm -r run clean && rimraf node_modules",
"db:up": "docker compose -f deploy/docker-compose.yml up -d",
"db:down": "docker compose -f deploy/docker-compose.yml down",
feat(opencode): native opencode integration — /mcpctl switcher, live project switching, footer indicator Adds `mcpctl config opencode`, two opencode plugins and an `opencode` skills sync target, so an mcpctl project can be switched from inside opencode's TUI and the active one is visible at a glance. Unlike `config claude` / `config prime-agent`, this writes NO MCP entry into the host's config. opencode exposes an HTTP API for its own MCP registry (`POST /mcp`), so the project is mounted through the running app: - the token stays in ~/.mcpctl/opencode-state.json (0600) instead of a mode-0644 opencode.json users paste into bug reports; - switching projects takes effect on the next turn, with no restart. Inside opencode: /mcpctl filterable project picker; switches live /mcpctl-status active project, mount state, gateway URL /mcpctl-skills re-sync this project's skills plus a `mcpctl:<project>` indicator in the prompt footer, next to the model name and one line above the token counter. Design notes: - the MCP server is registered under a constant name, so tools keep a stable `mcpctl_*` prefix and opencode's per-request tool resolution shows the new project's tools by itself — no "your old tool names are dead" message to the model, unlike the pi extension; - an unchanged mount is never re-registered: mcp.add rebuilds the connection and mcplocal binds a gated project's unlocked state to that connection's mcp-session-id, so re-adding would re-lock a project begin_session had just opened; - the server plugin does not mount during setup — setup runs before the server accepts connections and mcp.add calls back into it, which hangs opencode on a blank screen before the TUI draws; - the switcher shells out to this CLI (--skip-plugin --skip-marker) so token minting, state and skills stay in one place; - no usable credential aborts non-zero with the state file untouched, so a failed switch leaves the previous project working rather than swapping it for a mount that 401s. `skills sync --agent opencode` installs into ~/.config/opencode/skill (XDG aware) with the same shared-tree semantics as pi and prime-agent. The credential plumbing shared with `config prime-agent` is lifted to one place and parameterised by agent rather than copied. The plugin sources are embedded in the CLI (generated, freshness-tested) so an installed binary with no source tree can provision them, and are typechecked against the real @opencode-ai/plugin types. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BVwuCjuMoA13gmzYEfcrNP
2026-08-08 21:03:53 +01:00
"typecheck": "tsc --build && pnpm run typecheck:pi-ext && pnpm run typecheck:opencode-ext",
"completions:generate": "tsx scripts/generate-completions.ts --write",
"completions:check": "tsx scripts/generate-completions.ts --check",
"rpm:build": "bash scripts/build-rpm.sh",
"rpm:build:amd64": "MCPCTL_TARGET_ARCH=amd64 bash scripts/build-rpm.sh",
"rpm:build:arm64": "MCPCTL_TARGET_ARCH=arm64 bash scripts/build-rpm.sh",
"rpm:publish": "bash scripts/publish-rpm.sh",
"deb:build": "bash scripts/build-deb.sh",
"deb:build:amd64": "MCPCTL_TARGET_ARCH=amd64 bash scripts/build-deb.sh",
"deb:build:arm64": "MCPCTL_TARGET_ARCH=arm64 bash scripts/build-deb.sh",
"deb:publish": "bash scripts/publish-deb.sh",
"release": "bash scripts/release.sh",
"release:both": "bash scripts/release.sh --both-arches",
"mcpd:build": "bash scripts/build-mcpd.sh",
"mcpd:deploy": "bash deploy.sh",
"mcpd:deploy-dry": "bash deploy.sh --dry-run",
fix(pi): repair the /mcpctl menu, skills target, and typecheck the extension The pi extension shipped in `src/pi-ext/` was covered by no tsconfig and no eslint config, so nothing ever checked it against pi's API. Pointing tsc at the published @earendil-works/pi-coding-agent types found the command surface to be inert. Fixes: - `/mcpctl` did nothing. `ctx.ui.select` takes `string[]` and returns the chosen string; it was called with `{value,label}` objects, so the menu rendered five `[object Object]` rows and `choice === "status"` never matched any branch. Labels are now plain strings mapped back to actions. - The headless branch returned a status string from a handler typed `Promise<void>`; pi drops it. Reports via notify instead. - "Sync skills" omitted `--agent pi`, writing into ~/.claude/skills — in an integration whose stated purpose is to not depend on ~/.claude — and said so in its own success message. It also ran execSync with `stdio: "inherit"`, painting raw output over pi's TUI, and interpolated the project name into a shell string. Now execFile with `--agent pi` and captured output. - Tool results typed `content[].type` as `string`; pi's AgentToolResult wants the `"text"` literal. - `callTool` asserted `Promise<unknown>` to `ToolCallResult`. - Sanitising MCP tool names to `[a-z0-9_]` can collide (`docs.search` vs `docs-search`). The colliding tool was silently never registered but still reported active, so its calls were forwarded to the first tool. Names are now disambiguated and tracked with the MCP tool they forward to. - `registerWithPi` rewrote settings.json even when nothing changed. Since parsing strips `//` comments, a no-op run destroyed them. Guards, so this class of bug can't return: - `src/pi-ext/tsconfig.json` checks the extension against the real published pi types (dev dependency, not a shim — a shim drifting from the published API is the exact failure being guarded). Wired into `pnpm typecheck`. - eslint now covers `src/pi-ext/*.ts` like every other source file. - A test fails if the embedded copy in `config/pi-extension.ts` is stale; editing the sources without regenerating silently shipped old code. Also: the branch added `config pi` without regenerating shell completions (the committed-completions test was failing), and the doc advertised `mcpctl pi sync-skills`, which does not exist. Both corrected, plus a note on the session-token vs `mcpctl_pat_` bearer difference that would bite against an authenticated `mcplocal serve`. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017BMXdb2qZbPSh8Q7XpTyjB
2026-08-08 17:30:42 +01:00
"mcpd:logs": "bash logs.sh",
fix(smoke): actually clean up smoke-test resources 341 of 356 projects on the shared mcpd were smoke-test leftovers, plus 11 never-expiring `smoke-*` mcptokens sitting on the real `mcpctl-development` and `sre` projects. Enough to make the project picker unusable. Two causes, both silent: - `delete project X --force` — `--force` is valid on `create project` but NOT on `delete`, so every cleanup call exited non-zero and deleted nothing. project-llm-ref's afterAll looked correct and had never worked, which is why there were 84 each of smoke-proj-{ok,orphan,none}-*. - mcptoken.smoke had no afterAll at all; its cleanup was an `it()` at the end of the file, so it was skipped whenever an earlier assertion failed. Cleanup now lives in `afterAll` (runs on failure too) and is no longer gated on the health probe: the project is created through mcpd, which can be up when the gateway probe is not, and deleting a project that was never created is a no-op. Adds `pnpm smoke:clean` for the case afterAll cannot cover — a killed process (CI timeout, Ctrl-C, OOM). Dry-run by default, `--yes` to apply. Only touches `smoke-*` names and protects the shared `smoke-data` / `smoke-aws-docs` fixture, which five suites depend on and which must survive a concurrent run. Verified end to end: created a throwaway smoke project, confirmed the dry run lists without deleting, then `--yes` removed it and left all 15 real projects untouched. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017BMXdb2qZbPSh8Q7XpTyjB
2026-08-08 18:49:42 +01:00
"typecheck:pi-ext": "tsc -p src/pi-ext/tsconfig.json",
feat(opencode): native opencode integration — /mcpctl switcher, live project switching, footer indicator Adds `mcpctl config opencode`, two opencode plugins and an `opencode` skills sync target, so an mcpctl project can be switched from inside opencode's TUI and the active one is visible at a glance. Unlike `config claude` / `config prime-agent`, this writes NO MCP entry into the host's config. opencode exposes an HTTP API for its own MCP registry (`POST /mcp`), so the project is mounted through the running app: - the token stays in ~/.mcpctl/opencode-state.json (0600) instead of a mode-0644 opencode.json users paste into bug reports; - switching projects takes effect on the next turn, with no restart. Inside opencode: /mcpctl filterable project picker; switches live /mcpctl-status active project, mount state, gateway URL /mcpctl-skills re-sync this project's skills plus a `mcpctl:<project>` indicator in the prompt footer, next to the model name and one line above the token counter. Design notes: - the MCP server is registered under a constant name, so tools keep a stable `mcpctl_*` prefix and opencode's per-request tool resolution shows the new project's tools by itself — no "your old tool names are dead" message to the model, unlike the pi extension; - an unchanged mount is never re-registered: mcp.add rebuilds the connection and mcplocal binds a gated project's unlocked state to that connection's mcp-session-id, so re-adding would re-lock a project begin_session had just opened; - the server plugin does not mount during setup — setup runs before the server accepts connections and mcp.add calls back into it, which hangs opencode on a blank screen before the TUI draws; - the switcher shells out to this CLI (--skip-plugin --skip-marker) so token minting, state and skills stay in one place; - no usable credential aborts non-zero with the state file untouched, so a failed switch leaves the previous project working rather than swapping it for a mount that 401s. `skills sync --agent opencode` installs into ~/.config/opencode/skill (XDG aware) with the same shared-tree semantics as pi and prime-agent. The credential plumbing shared with `config prime-agent` is lifted to one place and parameterised by agent rather than copied. The plugin sources are embedded in the CLI (generated, freshness-tested) so an installed binary with no source tree can provision them, and are typechecked against the real @opencode-ai/plugin types. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BVwuCjuMoA13gmzYEfcrNP
2026-08-08 21:03:53 +01:00
"typecheck:opencode-ext": "tsc -p src/opencode-ext/tsconfig.json",
fix(smoke): actually clean up smoke-test resources 341 of 356 projects on the shared mcpd were smoke-test leftovers, plus 11 never-expiring `smoke-*` mcptokens sitting on the real `mcpctl-development` and `sre` projects. Enough to make the project picker unusable. Two causes, both silent: - `delete project X --force` — `--force` is valid on `create project` but NOT on `delete`, so every cleanup call exited non-zero and deleted nothing. project-llm-ref's afterAll looked correct and had never worked, which is why there were 84 each of smoke-proj-{ok,orphan,none}-*. - mcptoken.smoke had no afterAll at all; its cleanup was an `it()` at the end of the file, so it was skipped whenever an earlier assertion failed. Cleanup now lives in `afterAll` (runs on failure too) and is no longer gated on the health probe: the project is created through mcpd, which can be up when the gateway probe is not, and deleting a project that was never created is a no-op. Adds `pnpm smoke:clean` for the case afterAll cannot cover — a killed process (CI timeout, Ctrl-C, OOM). Dry-run by default, `--yes` to apply. Only touches `smoke-*` names and protects the shared `smoke-data` / `smoke-aws-docs` fixture, which five suites depend on and which must survive a concurrent run. Verified end to end: created a throwaway smoke project, confirmed the dry run lists without deleting, then `--yes` removed it and left all 15 real projects untouched. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017BMXdb2qZbPSh8Q7XpTyjB
2026-08-08 18:49:42 +01:00
"smoke:clean": "tsx scripts/clean-smoke-resources.ts"
2026-02-21 03:10:39 +00:00
},
"engines": {
"node": ">=20.0.0",
"pnpm": ">=9.0.0"
},
"packageManager": "pnpm@9.15.0",
"devDependencies": {
fix(pi): repair the /mcpctl menu, skills target, and typecheck the extension The pi extension shipped in `src/pi-ext/` was covered by no tsconfig and no eslint config, so nothing ever checked it against pi's API. Pointing tsc at the published @earendil-works/pi-coding-agent types found the command surface to be inert. Fixes: - `/mcpctl` did nothing. `ctx.ui.select` takes `string[]` and returns the chosen string; it was called with `{value,label}` objects, so the menu rendered five `[object Object]` rows and `choice === "status"` never matched any branch. Labels are now plain strings mapped back to actions. - The headless branch returned a status string from a handler typed `Promise<void>`; pi drops it. Reports via notify instead. - "Sync skills" omitted `--agent pi`, writing into ~/.claude/skills — in an integration whose stated purpose is to not depend on ~/.claude — and said so in its own success message. It also ran execSync with `stdio: "inherit"`, painting raw output over pi's TUI, and interpolated the project name into a shell string. Now execFile with `--agent pi` and captured output. - Tool results typed `content[].type` as `string`; pi's AgentToolResult wants the `"text"` literal. - `callTool` asserted `Promise<unknown>` to `ToolCallResult`. - Sanitising MCP tool names to `[a-z0-9_]` can collide (`docs.search` vs `docs-search`). The colliding tool was silently never registered but still reported active, so its calls were forwarded to the first tool. Names are now disambiguated and tracked with the MCP tool they forward to. - `registerWithPi` rewrote settings.json even when nothing changed. Since parsing strips `//` comments, a no-op run destroyed them. Guards, so this class of bug can't return: - `src/pi-ext/tsconfig.json` checks the extension against the real published pi types (dev dependency, not a shim — a shim drifting from the published API is the exact failure being guarded). Wired into `pnpm typecheck`. - eslint now covers `src/pi-ext/*.ts` like every other source file. - A test fails if the embedded copy in `config/pi-extension.ts` is stale; editing the sources without regenerating silently shipped old code. Also: the branch added `config pi` without regenerating shell completions (the committed-completions test was failing), and the doc advertised `mcpctl pi sync-skills`, which does not exist. Both corrected, plus a note on the session-token vs `mcpctl_pat_` bearer difference that would bite against an authenticated `mcplocal serve`. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017BMXdb2qZbPSh8Q7XpTyjB
2026-08-08 17:30:42 +01:00
"@earendil-works/pi-ai": "^0.84.1",
"@earendil-works/pi-coding-agent": "0.84.1",
feat(opencode): native opencode integration — /mcpctl switcher, live project switching, footer indicator Adds `mcpctl config opencode`, two opencode plugins and an `opencode` skills sync target, so an mcpctl project can be switched from inside opencode's TUI and the active one is visible at a glance. Unlike `config claude` / `config prime-agent`, this writes NO MCP entry into the host's config. opencode exposes an HTTP API for its own MCP registry (`POST /mcp`), so the project is mounted through the running app: - the token stays in ~/.mcpctl/opencode-state.json (0600) instead of a mode-0644 opencode.json users paste into bug reports; - switching projects takes effect on the next turn, with no restart. Inside opencode: /mcpctl filterable project picker; switches live /mcpctl-status active project, mount state, gateway URL /mcpctl-skills re-sync this project's skills plus a `mcpctl:<project>` indicator in the prompt footer, next to the model name and one line above the token counter. Design notes: - the MCP server is registered under a constant name, so tools keep a stable `mcpctl_*` prefix and opencode's per-request tool resolution shows the new project's tools by itself — no "your old tool names are dead" message to the model, unlike the pi extension; - an unchanged mount is never re-registered: mcp.add rebuilds the connection and mcplocal binds a gated project's unlocked state to that connection's mcp-session-id, so re-adding would re-lock a project begin_session had just opened; - the server plugin does not mount during setup — setup runs before the server accepts connections and mcp.add calls back into it, which hangs opencode on a blank screen before the TUI draws; - the switcher shells out to this CLI (--skip-plugin --skip-marker) so token minting, state and skills stay in one place; - no usable credential aborts non-zero with the state file untouched, so a failed switch leaves the previous project working rather than swapping it for a mount that 401s. `skills sync --agent opencode` installs into ~/.config/opencode/skill (XDG aware) with the same shared-tree semantics as pi and prime-agent. The credential plumbing shared with `config prime-agent` is lifted to one place and parameterised by agent rather than copied. The plugin sources are embedded in the CLI (generated, freshness-tested) so an installed binary with no source tree can provision them, and are typechecked against the real @opencode-ai/plugin types. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BVwuCjuMoA13gmzYEfcrNP
2026-08-08 21:03:53 +01:00
"@opencode-ai/plugin": "1.18.15",
"@opentui/core": "0.4.5",
"@opentui/keymap": "0.4.5",
"@opentui/solid": "0.4.5",
"@types/node": "^25.3.0",
2026-02-21 03:10:39 +00:00
"@typescript-eslint/eslint-plugin": "^8.56.0",
"@typescript-eslint/parser": "^8.56.0",
"@vitest/coverage-v8": "^4.0.18",
"eslint": "^10.0.1",
"eslint-config-prettier": "^10.1.8",
"rimraf": "^6.1.3",
feat(opencode): native opencode integration — /mcpctl switcher, live project switching, footer indicator Adds `mcpctl config opencode`, two opencode plugins and an `opencode` skills sync target, so an mcpctl project can be switched from inside opencode's TUI and the active one is visible at a glance. Unlike `config claude` / `config prime-agent`, this writes NO MCP entry into the host's config. opencode exposes an HTTP API for its own MCP registry (`POST /mcp`), so the project is mounted through the running app: - the token stays in ~/.mcpctl/opencode-state.json (0600) instead of a mode-0644 opencode.json users paste into bug reports; - switching projects takes effect on the next turn, with no restart. Inside opencode: /mcpctl filterable project picker; switches live /mcpctl-status active project, mount state, gateway URL /mcpctl-skills re-sync this project's skills plus a `mcpctl:<project>` indicator in the prompt footer, next to the model name and one line above the token counter. Design notes: - the MCP server is registered under a constant name, so tools keep a stable `mcpctl_*` prefix and opencode's per-request tool resolution shows the new project's tools by itself — no "your old tool names are dead" message to the model, unlike the pi extension; - an unchanged mount is never re-registered: mcp.add rebuilds the connection and mcplocal binds a gated project's unlocked state to that connection's mcp-session-id, so re-adding would re-lock a project begin_session had just opened; - the server plugin does not mount during setup — setup runs before the server accepts connections and mcp.add calls back into it, which hangs opencode on a blank screen before the TUI draws; - the switcher shells out to this CLI (--skip-plugin --skip-marker) so token minting, state and skills stay in one place; - no usable credential aborts non-zero with the state file untouched, so a failed switch leaves the previous project working rather than swapping it for a mount that 401s. `skills sync --agent opencode` installs into ~/.config/opencode/skill (XDG aware) with the same shared-tree semantics as pi and prime-agent. The credential plumbing shared with `config prime-agent` is lifted to one place and parameterised by agent rather than copied. The plugin sources are embedded in the CLI (generated, freshness-tested) so an installed binary with no source tree can provision them, and are typechecked against the real @opencode-ai/plugin types. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BVwuCjuMoA13gmzYEfcrNP
2026-08-08 21:03:53 +01:00
"solid-js": "1.9.12",
2026-02-21 03:10:39 +00:00
"tsx": "^4.21.0",
fix(pi): repair the /mcpctl menu, skills target, and typecheck the extension The pi extension shipped in `src/pi-ext/` was covered by no tsconfig and no eslint config, so nothing ever checked it against pi's API. Pointing tsc at the published @earendil-works/pi-coding-agent types found the command surface to be inert. Fixes: - `/mcpctl` did nothing. `ctx.ui.select` takes `string[]` and returns the chosen string; it was called with `{value,label}` objects, so the menu rendered five `[object Object]` rows and `choice === "status"` never matched any branch. Labels are now plain strings mapped back to actions. - The headless branch returned a status string from a handler typed `Promise<void>`; pi drops it. Reports via notify instead. - "Sync skills" omitted `--agent pi`, writing into ~/.claude/skills — in an integration whose stated purpose is to not depend on ~/.claude — and said so in its own success message. It also ran execSync with `stdio: "inherit"`, painting raw output over pi's TUI, and interpolated the project name into a shell string. Now execFile with `--agent pi` and captured output. - Tool results typed `content[].type` as `string`; pi's AgentToolResult wants the `"text"` literal. - `callTool` asserted `Promise<unknown>` to `ToolCallResult`. - Sanitising MCP tool names to `[a-z0-9_]` can collide (`docs.search` vs `docs-search`). The colliding tool was silently never registered but still reported active, so its calls were forwarded to the first tool. Names are now disambiguated and tracked with the MCP tool they forward to. - `registerWithPi` rewrote settings.json even when nothing changed. Since parsing strips `//` comments, a no-op run destroyed them. Guards, so this class of bug can't return: - `src/pi-ext/tsconfig.json` checks the extension against the real published pi types (dev dependency, not a shim — a shim drifting from the published API is the exact failure being guarded). Wired into `pnpm typecheck`. - eslint now covers `src/pi-ext/*.ts` like every other source file. - A test fails if the embedded copy in `config/pi-extension.ts` is stale; editing the sources without regenerating silently shipped old code. Also: the branch added `config pi` without regenerating shell completions (the committed-completions test was failing), and the doc advertised `mcpctl pi sync-skills`, which does not exist. Both corrected, plus a note on the session-token vs `mcpctl_pat_` bearer difference that would bite against an authenticated `mcplocal serve`. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017BMXdb2qZbPSh8Q7XpTyjB
2026-08-08 17:30:42 +01:00
"typebox": "1.3.11",
2026-02-21 03:10:39 +00:00
"typescript": "^5.9.3",
"vitest": "^4.0.18"
}
}