mcp-server-opencode 1.1.5 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (94) hide show
  1. package/CHANGELOG.md +127 -0
  2. package/README.md +34 -5
  3. package/build/src/index.js +32 -15
  4. package/build/src/index.js.map +1 -1
  5. package/build/src/modules/shared/execution-report.d.ts +66 -0
  6. package/build/src/modules/shared/execution-report.d.ts.map +1 -0
  7. package/build/src/modules/shared/execution-report.js +407 -0
  8. package/build/src/modules/shared/execution-report.js.map +1 -0
  9. package/build/src/modules/shared/execution-usage.d.ts +50 -0
  10. package/build/src/modules/shared/execution-usage.d.ts.map +1 -0
  11. package/build/src/modules/shared/execution-usage.js +89 -0
  12. package/build/src/modules/shared/execution-usage.js.map +1 -0
  13. package/build/src/modules/shared/git-workspace.d.ts +34 -0
  14. package/build/src/modules/shared/git-workspace.d.ts.map +1 -0
  15. package/build/src/modules/shared/git-workspace.js +193 -0
  16. package/build/src/modules/shared/git-workspace.js.map +1 -0
  17. package/build/src/modules/shared/instructions.d.ts +8 -1
  18. package/build/src/modules/shared/instructions.d.ts.map +1 -1
  19. package/build/src/modules/shared/instructions.js +32 -27
  20. package/build/src/modules/shared/instructions.js.map +1 -1
  21. package/build/src/modules/shared/message-id.d.ts +3 -0
  22. package/build/src/modules/shared/message-id.d.ts.map +1 -0
  23. package/build/src/modules/shared/message-id.js +6 -0
  24. package/build/src/modules/shared/message-id.js.map +1 -0
  25. package/build/src/modules/shared/opencode-client.d.ts +42 -4
  26. package/build/src/modules/shared/opencode-client.d.ts.map +1 -1
  27. package/build/src/modules/shared/opencode-client.js +214 -38
  28. package/build/src/modules/shared/opencode-client.js.map +1 -1
  29. package/build/src/modules/shared/permission-scopes.d.ts +6 -0
  30. package/build/src/modules/shared/permission-scopes.d.ts.map +1 -0
  31. package/build/src/modules/shared/permission-scopes.js +60 -0
  32. package/build/src/modules/shared/permission-scopes.js.map +1 -0
  33. package/build/src/modules/shared/permissions.d.ts +110 -0
  34. package/build/src/modules/shared/permissions.d.ts.map +1 -0
  35. package/build/src/modules/shared/permissions.js +214 -0
  36. package/build/src/modules/shared/permissions.js.map +1 -0
  37. package/build/src/modules/shared/prompt-dispatch.d.ts +13 -0
  38. package/build/src/modules/shared/prompt-dispatch.d.ts.map +1 -0
  39. package/build/src/modules/shared/prompt-dispatch.js +34 -0
  40. package/build/src/modules/shared/prompt-dispatch.js.map +1 -0
  41. package/build/src/modules/shared/run-observer.d.ts +5 -0
  42. package/build/src/modules/shared/run-observer.d.ts.map +1 -0
  43. package/build/src/modules/shared/run-observer.js +421 -0
  44. package/build/src/modules/shared/run-observer.js.map +1 -0
  45. package/build/src/modules/shared/server-registry.d.ts +4 -0
  46. package/build/src/modules/shared/server-registry.d.ts.map +1 -1
  47. package/build/src/modules/shared/server-registry.js.map +1 -1
  48. package/build/src/modules/shared/task-registry.d.ts +75 -0
  49. package/build/src/modules/shared/task-registry.d.ts.map +1 -1
  50. package/build/src/modules/shared/task-registry.js +85 -0
  51. package/build/src/modules/shared/task-registry.js.map +1 -1
  52. package/build/src/modules/shared/usage-limits.d.ts +70 -0
  53. package/build/src/modules/shared/usage-limits.d.ts.map +1 -0
  54. package/build/src/modules/shared/usage-limits.js +296 -0
  55. package/build/src/modules/shared/usage-limits.js.map +1 -0
  56. package/build/src/modules/shared/workspace-manager.d.ts +25 -0
  57. package/build/src/modules/shared/workspace-manager.d.ts.map +1 -0
  58. package/build/src/modules/shared/workspace-manager.js +105 -0
  59. package/build/src/modules/shared/workspace-manager.js.map +1 -0
  60. package/build/src/modules/tools/cancel_task.d.ts.map +1 -1
  61. package/build/src/modules/tools/cancel_task.js +30 -1
  62. package/build/src/modules/tools/cancel_task.js.map +1 -1
  63. package/build/src/modules/tools/cleanup_workspace.d.ts +3 -0
  64. package/build/src/modules/tools/cleanup_workspace.d.ts.map +1 -0
  65. package/build/src/modules/tools/cleanup_workspace.js +82 -0
  66. package/build/src/modules/tools/cleanup_workspace.js.map +1 -0
  67. package/build/src/modules/tools/continue_task.d.ts.map +1 -1
  68. package/build/src/modules/tools/continue_task.js +116 -6
  69. package/build/src/modules/tools/continue_task.js.map +1 -1
  70. package/build/src/modules/tools/get_task_result.d.ts.map +1 -1
  71. package/build/src/modules/tools/get_task_result.js +125 -22
  72. package/build/src/modules/tools/get_task_result.js.map +1 -1
  73. package/build/src/modules/tools/get_task_status.d.ts.map +1 -1
  74. package/build/src/modules/tools/get_task_status.js +69 -6
  75. package/build/src/modules/tools/get_task_status.js.map +1 -1
  76. package/build/src/modules/tools/index.d.ts.map +1 -1
  77. package/build/src/modules/tools/index.js +2 -0
  78. package/build/src/modules/tools/index.js.map +1 -1
  79. package/build/src/modules/tools/list_agents.d.ts.map +1 -1
  80. package/build/src/modules/tools/list_agents.js +48 -7
  81. package/build/src/modules/tools/list_agents.js.map +1 -1
  82. package/build/src/modules/tools/start_server.d.ts.map +1 -1
  83. package/build/src/modules/tools/start_server.js +21 -1
  84. package/build/src/modules/tools/start_server.js.map +1 -1
  85. package/build/src/modules/tools/start_task.d.ts.map +1 -1
  86. package/build/src/modules/tools/start_task.js +218 -13
  87. package/build/src/modules/tools/start_task.js.map +1 -1
  88. package/build/src/modules/tools/stop_server.d.ts.map +1 -1
  89. package/build/src/modules/tools/stop_server.js +17 -0
  90. package/build/src/modules/tools/stop_server.js.map +1 -1
  91. package/build/src/modules/tools/wait_for_task.d.ts.map +1 -1
  92. package/build/src/modules/tools/wait_for_task.js +110 -24
  93. package/build/src/modules/tools/wait_for_task.js.map +1 -1
  94. package/package.json +4 -2
package/CHANGELOG.md ADDED
@@ -0,0 +1,127 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project are documented here.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ Releases before `1.2.0` predate this file and are not documented here.
9
+
10
+ ## Unreleased
11
+
12
+ ## [1.3.0] — 2026-10-19
13
+
14
+ ### Added
15
+
16
+ - Caller-provided expected workspace changes and bounded executable checks, with additive per-run `workspace_delta` reports and verification states.
17
+ - Workspace-wide overlapping-directory leases, report-aware waits, and an independent lifecycle observer for accepted runs.
18
+ - Prompt dispatch now distinguishes definite HTTP 4xx rejection from ambiguous ACK failures. Ambiguous runs remain tracked with their writer lease held and return `dispatch_uncertain`; definite rejection rolls back only the provisional run.
19
+ - Caller message IDs use OpenCode's required `msg_` namespace; the real smoke includes a non-billable bare-UUID runtime probe.
20
+ - Bounded filesystem inventory, check output, and direct `shell: false` execution; report diagnostics disclose incomplete evidence and shared-workspace attribution uncertainty.
21
+ - Historical `run_id` status/result retrieval and cached final text/progress/report; tool getters do not execute checks again.
22
+ - Opt-in real OpenCode stdio smoke command (`pnpm smoke`) using only the exact `openai/gpt-6-luna` model, including directory-scoped permission handling and independent managed worktrees.
23
+ - Each accepted prompt has an additive `run_id` and run-history record. Explicit user-message IDs scope status, progress, and results to the active prompt; earlier assistant turns cannot appear as a pending continuation's output.
24
+ - Continuation dispatches are reserved per task, cancellation state is recorded only after an accepted abort, and accepted continuations clear prior cancellation state.
25
+ - Status and wait share terminal-state rules; `empty` and `cancelled` now finish long polls.
26
+ - SDK HTTP requests use a 30-second abort deadline; long-polling bounds each status read by the remaining total wait deadline.
27
+ - Server/task records optionally retain the canonical server working directory.
28
+ - Status, wait, and result payloads report direct assistant INFO usage with run/session snapshots, provider/model breakdowns, completeness metadata, and immutable per-run historical snapshots; missing metrics remain unknown and cost currency is unspecified.
29
+ - Optional task directories and managed detached Git worktrees bind SDK requests and per-session permission responders to the selected project. Worktree IDs/metadata appear in task history, and `opencode_cleanup_workspace` performs explicit serialized cleanup while preserving cached reports and usage.
30
+
31
+ ### Fixed
32
+
33
+ - SDK response-level errors are surfaced for prompt dispatch, abort, status/message reads, and permission responses, including HTTP failures returned without throwing.
34
+ - Permission responder diagnostics go to stderr rather than MCP stdio.
35
+ - `opencode_wait_for_task` progress enrichment now shares the absolute wait deadline; a slow enrichment is aborted and returns the last observed task status/progress rather than extending the call.
36
+
37
+ ## [1.2.0] — 2026-08-28
38
+
39
+ Delegated tasks could hang forever, or report `completed` after doing nothing.
40
+ Both are fixed, and the result payload now carries evidence of what a task
41
+ actually did. See `docs/incident-2026-08-21-silent-noop-tasks.md` for the full
42
+ report this release closes out.
43
+
44
+ ### Behavior changes callers must handle
45
+
46
+ - `opencode_get_task_status` and `opencode_get_task_result` can now return two
47
+ new statuses: **`empty`** and **`cancelled`**. Both were previously reported
48
+ as `completed`. Code that switches exhaustively on task status needs a branch
49
+ for them; code that only special-cases `completed` will now correctly stop
50
+ treating a no-op or an aborted task as a success.
51
+ - `opencode_get_task_result` now includes a `progress` object. Previously it
52
+ returned only `status` and `result`.
53
+ - `opencode_start_server` now reports its permission posture under
54
+ `permissions` (`{ external_directory, auto_approved }`).
55
+
56
+ ### Added
57
+
58
+ - **Permission responder for headless servers.** A headless `opencode serve`
59
+ has no interactive approver, so any permission resolved to `ask` blocked the
60
+ agent forever — the session stayed genuinely busy and the task neither
61
+ completed nor failed. `opencode_start_server` now passes a permission config
62
+ to the OpenCode server *and* subscribes to its event stream to answer every
63
+ request.
64
+ - **`OPENCODE_MCP_EXTERNAL_DIR`** selects how the delegated agent may touch
65
+ paths outside the directory the server was launched in. Precedence: env var >
66
+ `OPENCODE_MCP_EXTERNAL_DIR=<policy>` CLI arg > default.
67
+ - `read-only` (default) — reads outside the cwd are approved, writes rejected.
68
+ - `allow` — no boundary.
69
+ - `deny` — nothing outside the cwd is reachable.
70
+
71
+ Inside the cwd everything is auto-approved; the caller already owns that
72
+ directory. The config is deep-merged per permission key, so existing
73
+ `read` / `edit` / `bash` deny globs (secrets, keys, `.env`) survive untouched.
74
+ - **Side-effect evidence in `TaskProgress`**: `mutating_tool_calls` and
75
+ `files_touched`, collected across every assistant turn in the session.
76
+ `completed` with an empty `files_touched` on a task whose whole purpose was
77
+ writing files is the signal that catches a silent no-op.
78
+ - **Per-model quota annotations.** `opencode_list_agents` now returns each model
79
+ as `{ id, quota }` with a `per_5h` estimate and a tier (`high-volume`,
80
+ `balanced`, `scarce`), scraped from the OpenCode Go docs and cached on disk
81
+ for 24h. A sibling `models.quota_snapshot` distinguishes "this model has no
82
+ quota row" from "we never got the table". Overridable with
83
+ `OPENCODE_MCP_CACHE_DIR` and `OPENCODE_MCP_LIMITS_TTL_MS`; a failed fetch
84
+ falls back to the stale snapshot, so startup never depends on the network.
85
+
86
+ ### Fixed
87
+
88
+ - **Tasks no longer hang on a permission prompt.** Previously, any task reading
89
+ or writing a path outside the server cwd wedged indefinitely with no way for a
90
+ caller to tell "working" from "stuck".
91
+ - **`completed` is now a work check, not a timestamp check.** An assistant turn
92
+ that ended with no text and no tool calls reports `empty` instead of
93
+ `completed` with an empty result. A completed timestamp says the turn ended,
94
+ not that it did anything.
95
+ - **Evidence is collected across the whole session, not just the last turn.** An
96
+ agent's edits land in earlier turns while the final turn is usually just
97
+ "DONE", so reading evidence off the last message alone reported
98
+ `files_touched: []` for a task that had rewritten the workspace.
99
+ - **Cancelled tasks report `cancelled`.** `opencode_cancel_task` now records the
100
+ cancellation, and status derivation checks it first. An aborted session keeps
101
+ its completed timestamp, so a deliberately killed task was previously
102
+ indistinguishable from one that finished on its own. A cancelled task still
103
+ returns whatever it produced before the abort, `files_touched` included.
104
+ - **The stall guard covers the empty-turn case.** `PENDING_STALL_MS` was only
105
+ reachable when no assistant message existed at all; once any message existed —
106
+ including an empty or aborted one — the guard was bypassed permanently.
107
+
108
+ ### Notes for contributors
109
+
110
+ - The generated `@opencode-ai/sdk` types lag the running binary. Verified
111
+ against opencode `1.18.18` with SDK `1.17.20`: the live permission event is
112
+ `permission.asked` (the SDK declares only `permission.updated`), its kind
113
+ arrives as `properties.permission` (the SDK says `type`), and the call id is
114
+ nested under `properties.tool.callID` (the SDK puts it at the top level).
115
+ Verify wire shapes against a live server before trusting a generated type — a
116
+ mismatch here fails silently, because a permission that is never answered just
117
+ hangs.
118
+ - Two bugs in this release passed a green unit suite and were caught only by
119
+ running against a live server. Mocks reproduce the contract you believe in,
120
+ not the one the system implements.
121
+
122
+ ### Not addressed
123
+
124
+ - **F3** from the incident report: there is still no staleness signal on the
125
+ `running` path. `buildProgress` reports `current_tool` and
126
+ `current_tool_status` but no timing, so a caller has no field to compare
127
+ across polls to tell a slow task from a wedged one.
package/README.md CHANGED
@@ -27,15 +27,16 @@ See [Installation](#installation) for manual config, and from-source options.
27
27
 
28
28
  | Tool | Description |
29
29
  | --------------------------- | -------------------------------------------------------------------------------------------------------- |
30
- | `opencode_start_server` | Start (or attach to) an OpenCode server instance |
30
+ | `opencode_start_server` | Start a new OpenCode server instance in the MCP process working directory |
31
31
  | `opencode_stop_server` | Stop a running OpenCode server instance |
32
- | `opencode_list_agents` | List agents/models available on a server instance |
32
+ | `opencode_list_agents` | List agents/models available on a server instance (optional `directory` selects a project) |
33
33
  | `opencode_start_task` | Delegate a task to an agent by starting a new session and prompt (optional `agent` / `model` override) |
34
34
  | `opencode_continue_task` | Send a follow-up prompt to an existing task's session for iterative back-and-forth with the subagent |
35
35
  | `opencode_cancel_task` | Abort a running delegated task by cancelling its session |
36
- | `opencode_get_task_status` | Poll the status of a delegated task (`pending` / `running` / `completed` / `failed`); optional `include_progress` adds a partial output snippet and the currently running tool while it's still running |
36
+ | `opencode_cleanup_workspace` | Explicitly remove an owned Git worktree after task reports finalize; dirty worktrees require opt-in discard |
37
+ | `opencode_get_task_status` | Poll the status of a delegated task (`pending` / `running` / `completed` / `failed` / `empty` / `cancelled`); optional `include_progress` adds a partial output snippet and the currently running tool while it's still running |
37
38
  | `opencode_get_task_result` | Fetch the final result of a completed task |
38
- | `opencode_wait_for_task` | Long-poll one or more delegated tasks until they finish (`mode: "all"` or `"any"`) or the timeout elapses; optional `include_progress` enriches any still-unfinished tasks in the final result with a partial output snippet and the currently running tool |
39
+ | `opencode_wait_for_task` | Long-poll one or more delegated tasks until they finish (`mode: "all"` or `"any"`) or the timeout elapses; optional `include_progress` best-effort enriches unfinished tasks within the same deadline, falling back to the last observed status/progress when time runs out |
39
40
 
40
41
  | Prompt | Description |
41
42
  | --------------- | ---------------------------------------------------------------------------------------------------- |
@@ -44,7 +45,35 @@ See [Installation](#installation) for manual config, and from-source options.
44
45
  ## How it works
45
46
  ![opencode mcp architecture](./opencode-architecture.svg)
46
47
 
47
- Task delegation is **asynchronous**: starting a task returns immediately with a `task_id` instead of blocking until the subagent finishes. This lets Claude Code fire multiple `opencode_start_task` calls in parallel — each one opens an isolated OpenCode `Session` — without hitting MCP client timeouts on long-running work. Status and results are fetched separately via polling.
48
+ Task delegation is **asynchronous**: starting a task returns immediately with a `task_id` instead of blocking until the subagent finishes. This lets hosts fire multiple `opencode_start_task` calls in parallel — each opens an isolated OpenCode `Session` — without hitting MCP client timeouts on long-running work. Tasks use the selected directory directly by default; managed Git worktrees are an explicit opt-in. Status and results are fetched separately via polling. If the prompt ACK transport is lost after OpenCode may have accepted the request, the tool returns `dispatch_uncertain` with task/run IDs and retains its lease; poll that task or cancel it rather than retrying the prompt as a new task. Definite HTTP 4xx rejection rolls back provisional run state and lease.
49
+
50
+ Each accepted start or continuation prompt also receives a `run_id`. Status, progress, and results are scoped to that prompt's explicit message ID, while progress evidence remains cumulative across turns in the same run. `empty` and `cancelled` are terminal outcomes. A cancelled task can be continued; the accepted continuation becomes the current run and clears the previous cancellation state. Concurrent continuation dispatches for the same task are rejected with `dispatch_in_progress`.
51
+
52
+ OpenCode 1.18.34 requires caller message IDs to start with `msg`; the MCP uses `msg_${randomUUID()}` for both start and continuation prompts. The opt-in harness also probes a bare UUID through the installed SDK/runtime using an invalid provider/model so the identifier contract can be checked without a paid request.
53
+
54
+ ### Execution reports and caller checks
55
+
56
+ `opencode_start_task` and `opencode_continue_task` accept optional `expected_changes` (`[{ path, change }]`, where `change` is `created`, `modified`, or `deleted`) and `checks` (`[{ executable, argv, cwd?, timeout_ms? }]`). Paths and check working directories are relative to the server workspace; checks run directly with `shell: false`. Reports are additive on status/result as `run_id`, `changes`, `verification`, `diagnostics`, and `evidence`. `opencode_get_task_result` and `opencode_get_task_status` accept an optional `run_id` to retrieve a historical run; omitted means the current run.
57
+
58
+ ### Shared directories and managed worktrees
59
+
60
+ `opencode_start_task` accepts `directory` (relative to the server directory, defaulting to it), `workspace_mode` (`shared` by default or `worktree`), and `base_ref` (worktree mode only, default `HEAD`). The selected directory must already exist. Shared mode runs directly in that canonical directory. Worktree mode creates a unique detached Git worktree at the selected committed ref; source modifications and untracked files are neither copied nor altered. When the selected base does not contain the requested nested source directory, the request fails instead of silently changing the execution directory.
61
+
62
+ Start, status, result, wait, and historical-run payloads expose a `workspace` object with source and execution directories, mode, managed state, and (for worktrees) `workspace_id`, base commit, source dirty state, and lifecycle state. SDK requests are bound to the selected directory through OpenCode's directory routing. Permission responders are keyed by server and canonical directory and answer only sessions registered to that scope.
63
+
64
+ Accepted worktrees are retained after task completion. Call `opencode_cleanup_workspace` with the returned `workspace_id` after reports are finalized. Cleanup serializes against task continuation/dispatch, rejects unfinished reports and dirty worktrees, and removes only worktrees provisioned by this MCP process. Set `discard_changes: true` only when changes inside that owned worktree should be discarded. Cleanup never removes source files; cached results, usage, and historical runs remain readable after the worktree is removed.
65
+
66
+ ### Usage reporting
67
+
68
+ Status, wait, and result payloads include an additive `usage` snapshot. Its `run` summary selects direct assistant messages whose `parentID` matches that run's user-message ID; `session` summarizes all direct assistant messages observed in the session snapshot. Neither includes descendants or separate subagent sessions. Per-provider/model breakdowns preserve raw input, output, reasoning, cache-read, cache-write, and OpenCode-reported cost independently; token categories are not combined into a potentially overlapping total. Step-finish part metrics are excluded.
69
+
70
+ Each metric has a nullable `value`, `complete` flag, and `missing_messages` count. Missing, invalid, or negative values remain unknown; explicit zero is known. Empty snapshots do not claim zero consumption. Cost is labeled as OpenCode-reported, with currency unspecified; it is not an invoice total. `captured_at` and `snapshot_kind` identify the observation. A finalized run caches its own run and session snapshot, so retrieving an older `run_id` after a continuation returns the historical session snapshot from that run's finalization—not a current cumulative total. Failed message reads return `report: null` with a diagnostic.
71
+
72
+ The MCP server captures a bounded workspace inventory before accepted dispatch and after execution. The inventory recursively hashes workspace files except `.git` and `node_modules`; caller-declared expected paths are also checked explicitly, including paths inside otherwise skipped directories. Created/untracked, modified (including pre-existing dirty files changed since baseline), deleted, binary, and symlink entries are reported. Changes are labeled `workspace_delta`: concurrent or unmanaged writers can still make attribution uncertain. A verified run holds an exclusive lease across the canonical workspace and overlapping directories; it blocks verified and ordinary MCP task writers and conflicts fail fast with `workspace_busy`. Ordinary unverified runs may share a workspace. `session.diff` is not used as task provenance. Capture errors, missing expected changes, and scope limits are disclosed. `opencode_wait_for_task` supports `until: "execution"` (default) or `until: "report"`; report waits end on finalized failed, unavailable, or cancelled reports while respecting the whole-call timeout.
73
+
74
+ Each accepted run retains its own baseline, criteria, lease, result, report, and finalized usage snapshot. Checks are executed once by the background lifecycle observer after terminal execution. Their result includes exit code/signal, duration, bounded output and exact truncation flags; timeout/cancellation terminates the child process group. Verification may be `not_requested`, `pending`, `running`, `passed`, `failed`, `unavailable`, or `cancelled`; execution success alone does not mean verification passed. Final task text/progress, reports, and usage snapshots are cached, so result queries do not rerun checks and historical results remain available after a continuation or backend removal. Workspace inventories are capped at 20,000 entries and 32 MiB of file content; check output is capped at 16 KiB per stream. These limits and shared-workspace attribution mean the report is evidence, not proof of exclusive authorship.
75
+
76
+ Run the opt-in real OpenCode stdio smoke harness with `pnpm smoke`. It uses only `openai/gpt-6-luna` for its paid prompts, creates and removes temporary fixtures, and checks real reports/usage, historical retrieval, no-change and cancellation continuations, two detached worktrees editing the same relative file independently, an external read permission in a scoped worktree session, continuation in that worktree, dirty-cleanup refusal, explicit discard, and cached history after removal. The no-model identifier probe is also runnable with `node --experimental-strip-types scripts/smoke.ts --message-id-only`. The paid smoke is intentionally not part of default CI because it requires configured credentials.
48
77
 
49
78
  ## Installation
50
79
 
@@ -1,23 +1,37 @@
1
1
  #!/usr/bin/env node
2
2
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
3
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
- import { DELEGATE_TASK_INSTRUCTIONS } from "./modules/shared/instructions.js";
4
+ import { createDelegateTaskInstructions } from "./modules/shared/instructions.js";
5
+ import { stopRunObservers } from "./modules/shared/run-observer.js";
5
6
  import { killAllServers } from "./modules/shared/server-registry.js";
6
7
  import { registerTools } from "./modules/tools/index.js";
7
8
  // Create server instance
8
- const server = new McpServer({
9
- name: "opencode-mcp",
10
- version: "1.0.0",
11
- title: "OpenCode MCP",
12
- description: "MCP server that drives an OpenCode instance and delegates work asynchronously to its agents",
13
- websiteUrl: "https://opencode-mcp.alejandrotechnology.com",
14
- }, {
15
- instructions: DELEGATE_TASK_INSTRUCTIONS,
16
- });
17
- function shutdown(signal) {
9
+ async function createServer() {
10
+ const instructions = await createDelegateTaskInstructions();
11
+ return new McpServer({
12
+ name: "opencode-mcp",
13
+ version: "1.2.0",
14
+ title: "OpenCode MCP",
15
+ description: "MCP server that drives an OpenCode instance and delegates work asynchronously to its agents",
16
+ websiteUrl: "https://opencode-mcp.alejandrotechnology.com",
17
+ }, {
18
+ instructions,
19
+ });
20
+ }
21
+ let shuttingDown = false;
22
+ async function shutdown(signal, exitCode = 0) {
23
+ if (shuttingDown)
24
+ return;
25
+ shuttingDown = true;
18
26
  console.error(`Received ${signal}, stopping tracked OpenCode servers...`);
27
+ const checksDrained = await stopRunObservers();
28
+ if (!checksDrained) {
29
+ console.error("One or more check process groups could not be confirmed stopped; OpenCode servers remain active and shutdown can be retried.");
30
+ shuttingDown = false;
31
+ return;
32
+ }
19
33
  killAllServers();
20
- process.exit(0);
34
+ process.exit(exitCode);
21
35
  }
22
36
  // Terminal closed (SIGHUP), Ctrl+C (SIGINT), or a normal kill (SIGTERM):
23
37
  // make sure no `opencode serve` child keeps running after we die.
@@ -25,15 +39,18 @@ process.on("SIGHUP", shutdown);
25
39
  process.on("SIGINT", shutdown);
26
40
  process.on("SIGTERM", shutdown);
27
41
  // Safety net for any other exit path (uncaught exception, process.exit elsewhere).
28
- process.on("exit", killAllServers);
42
+ process.on("exit", () => {
43
+ killAllServers();
44
+ });
29
45
  async function main() {
46
+ const server = await createServer();
30
47
  registerTools(server);
31
48
  const transport = new StdioServerTransport();
32
49
  await server.connect(transport);
33
50
  console.error("OpenCode MCP Server running on stdio");
34
51
  }
35
- main().catch((error) => {
52
+ main().catch(async (error) => {
36
53
  console.error("Fatal error in main():", error);
37
- process.exit(1);
54
+ await shutdown("SIGTERM", 1);
38
55
  });
39
56
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,0BAA0B,EAAE,MAAM,kCAAkC,CAAC;AAC9E,OAAO,EAAE,cAAc,EAAE,MAAM,qCAAqC,CAAC;AACrE,OAAO,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAEzD,yBAAyB;AACzB,MAAM,MAAM,GAAG,IAAI,SAAS,CAC1B;IACE,IAAI,EAAE,cAAc;IACpB,OAAO,EAAE,OAAO;IAChB,KAAK,EAAE,cAAc;IACrB,WAAW,EACT,6FAA6F;IAC/F,UAAU,EAAE,8CAA8C;CAC3D,EACD;IACE,YAAY,EAAE,0BAA0B;CACzC,CACF,CAAC;AAEF,SAAS,QAAQ,CAAC,MAAsB;IACtC,OAAO,CAAC,KAAK,CAAC,YAAY,MAAM,wCAAwC,CAAC,CAAC;IAC1E,cAAc,EAAE,CAAC;IACjB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC;AAED,yEAAyE;AACzE,kEAAkE;AAClE,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;AAC/B,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;AAC/B,OAAO,CAAC,EAAE,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC;AAChC,mFAAmF;AACnF,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;AAEnC,KAAK,UAAU,IAAI;IACjB,aAAa,CAAC,MAAM,CAAC,CAAC;IACtB,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;IAC7C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IAChC,OAAO,CAAC,KAAK,CAAC,sCAAsC,CAAC,CAAC;AACxD,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE;IACrB,OAAO,CAAC,KAAK,CAAC,wBAAwB,EAAE,KAAK,CAAC,CAAC;IAC/C,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,8BAA8B,EAAE,MAAM,kCAAkC,CAAC;AAClF,OAAO,EAAE,gBAAgB,EAAE,MAAM,kCAAkC,CAAC;AACpE,OAAO,EAAE,cAAc,EAAE,MAAM,qCAAqC,CAAC;AACrE,OAAO,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAEzD,yBAAyB;AACzB,KAAK,UAAU,YAAY;IACzB,MAAM,YAAY,GAAG,MAAM,8BAA8B,EAAE,CAAC;IAC5D,OAAO,IAAI,SAAS,CAClB;QACE,IAAI,EAAE,cAAc;QACpB,OAAO,EAAE,OAAO;QAChB,KAAK,EAAE,cAAc;QACrB,WAAW,EACT,6FAA6F;QAC/F,UAAU,EAAE,8CAA8C;KAC3D,EACD;QACE,YAAY;KACb,CACF,CAAC;AACJ,CAAC;AAED,IAAI,YAAY,GAAG,KAAK,CAAC;AAEzB,KAAK,UAAU,QAAQ,CAAC,MAAsB,EAAE,QAAQ,GAAG,CAAC;IAC1D,IAAI,YAAY;QAAE,OAAO;IACzB,YAAY,GAAG,IAAI,CAAC;IACpB,OAAO,CAAC,KAAK,CAAC,YAAY,MAAM,wCAAwC,CAAC,CAAC;IAC1E,MAAM,aAAa,GAAG,MAAM,gBAAgB,EAAE,CAAC;IAC/C,IAAI,CAAC,aAAa,EAAE,CAAC;QACnB,OAAO,CAAC,KAAK,CACX,8HAA8H,CAC/H,CAAC;QACF,YAAY,GAAG,KAAK,CAAC;QACrB,OAAO;IACT,CAAC;IACD,cAAc,EAAE,CAAC;IACjB,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;AACzB,CAAC;AAED,yEAAyE;AACzE,kEAAkE;AAClE,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;AAC/B,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;AAC/B,OAAO,CAAC,EAAE,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC;AAChC,mFAAmF;AACnF,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,GAAG,EAAE;IACtB,cAAc,EAAE,CAAC;AACnB,CAAC,CAAC,CAAC;AAEH,KAAK,UAAU,IAAI;IACjB,MAAM,MAAM,GAAG,MAAM,YAAY,EAAE,CAAC;IACpC,aAAa,CAAC,MAAM,CAAC,CAAC;IACtB,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;IAC7C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IAChC,OAAO,CAAC,KAAK,CAAC,sCAAsC,CAAC,CAAC;AACxD,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE;IAC3B,OAAO,CAAC,KAAK,CAAC,wBAAwB,EAAE,KAAK,CAAC,CAAC;IAC/C,MAAM,QAAQ,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC;AAC/B,CAAC,CAAC,CAAC"}
@@ -0,0 +1,66 @@
1
+ import { type ChildProcess, spawn } from "node:child_process";
2
+ export type ExpectedChange = {
3
+ path: string;
4
+ change: "created" | "modified" | "deleted";
5
+ };
6
+ export type VerificationCheck = {
7
+ executable: string;
8
+ argv: string[];
9
+ cwd?: string;
10
+ timeout_ms?: number;
11
+ };
12
+ export type WorkspaceSnapshot = {
13
+ root: string;
14
+ files: Map<string, string>;
15
+ };
16
+ export type FileChange = ExpectedChange & {
17
+ evidence: "workspace_delta";
18
+ };
19
+ export type VerificationState = "not_requested" | "pending" | "running" | "passed" | "failed" | "unavailable" | "cancelled";
20
+ export type CheckResult = {
21
+ executable: string;
22
+ argv: string[];
23
+ exit_code: number | null;
24
+ signal: NodeJS.Signals | null;
25
+ duration_ms: number;
26
+ stdout: string;
27
+ stderr: string;
28
+ stdout_truncated: boolean;
29
+ stderr_truncated: boolean;
30
+ timed_out?: boolean;
31
+ termination_confirmed?: boolean;
32
+ error?: string;
33
+ };
34
+ export type ExecutionReport = {
35
+ changes: FileChange[];
36
+ verification: {
37
+ state: VerificationState;
38
+ checks: CheckResult[];
39
+ execution_state?: string;
40
+ };
41
+ diagnostics: string[];
42
+ evidence: {
43
+ source: "workspace_delta";
44
+ inventory_scope: "recursive_except_git_and_node_modules_plus_explicit_paths";
45
+ complete: boolean;
46
+ errors: string[];
47
+ attribution: "uncertain_shared_workspace";
48
+ };
49
+ };
50
+ /** @internal Signal a direct check child and its descendants without invoking a shell. */
51
+ export declare function signalCheckProcess(child: Pick<ChildProcess, "pid" | "kill">, force: boolean, platform?: NodeJS.Platform, spawnKiller?: typeof spawn): Promise<boolean>;
52
+ /** Abort in-flight checks and wait until every check process has drained or the deadline expires. */
53
+ export declare function abortAndDrainChecks(controller: AbortController, timeoutMs?: number): Promise<boolean>;
54
+ /** @internal Confirm process-group termination, with an injectable state probe for tests. */
55
+ export declare function waitForProcessGroupExit(pid: number, timeoutMs?: number, readStates?: (groupId: number) => Promise<string[] | undefined>): Promise<boolean>;
56
+ /** @internal Read process-group states, with an injectable spawner for failure-path tests. */
57
+ export declare function processGroupStates(groupId: number, spawnProcess?: typeof spawn): Promise<string[] | undefined>;
58
+ export declare function validateRelativePath(value: string): boolean;
59
+ export declare function validateRelativeCwd(value: string): boolean;
60
+ export declare function captureWorkspace(directory: string, explicitPaths?: string[]): Promise<WorkspaceSnapshot>;
61
+ export declare function compareWorkspace(before: Awaited<ReturnType<typeof captureWorkspace>>, after: Awaited<ReturnType<typeof captureWorkspace>>): FileChange[];
62
+ export declare function runCheck(check: VerificationCheck, workspace: string, signal?: AbortSignal): Promise<CheckResult>;
63
+ export declare function reserveWorkspace(directory: string, exclusive: boolean): Promise<symbol | undefined>;
64
+ export declare function releaseWorkspace(token: symbol): void;
65
+ export declare function captureReport(before: Awaited<ReturnType<typeof captureWorkspace>>, expected: ExpectedChange[], checks: VerificationCheck[], execution: string, signal?: AbortSignal): Promise<ExecutionReport>;
66
+ //# sourceMappingURL=execution-report.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"execution-report.d.ts","sourceRoot":"","sources":["../../../../src/modules/shared/execution-report.ts"],"names":[],"mappings":"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"}