@shardflux/mcp 0.3.1 → 0.4.1
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.
- package/CHANGELOG.md +120 -0
- package/README.md +122 -5
- package/dist/config.d.ts +6 -0
- package/dist/config.js +5 -5
- package/dist/errors.d.ts +26 -2
- package/dist/errors.js +89 -6
- package/dist/index.d.ts +3 -3
- package/dist/index.js +2 -2
- package/dist/main.js +3 -0
- package/dist/server.d.ts +52 -13
- package/dist/server.js +407 -55
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,126 @@
|
|
|
3
3
|
Every tool, field and variable the README shows is available from the version named here. Below 1.0, a minor release
|
|
4
4
|
may break compatibility; breaking changes are marked **Breaking**. Versions before 0.3.0 were not published.
|
|
5
5
|
|
|
6
|
+
## 0.4.1 (not yet published; npm `latest` is 0.4.0)
|
|
7
|
+
|
|
8
|
+
A command that could not start (through `@shardflux/sdk` 0.10.0, `ExecStartError`):
|
|
9
|
+
|
|
10
|
+
- `exec` on a processful workspace: a command that could not start (a `cwd` that is not a directory, a program not on
|
|
11
|
+
`PATH`) returns `error: { code: "conflict", message, reason: "exec_failed_to_start" }` with `exit_code: null`, the
|
|
12
|
+
message carrying the workspace's reason (`The command could not start: working directory "/home/user/app" is not a
|
|
13
|
+
directory`), as a failed file-first execution does. It returned `exit_code: null` and empty output with no reason.
|
|
14
|
+
- A relative `cwd` is refused by the API before anything runs: an error result with `code` `validation_failed`,
|
|
15
|
+
`details.reason` `invalid_cwd` and a message naming the absolute path it likely means (`use "/home/user/app"`).
|
|
16
|
+
|
|
17
|
+
Opt-in overage with a spend cap (through `@shardflux/sdk` 0.10.0):
|
|
18
|
+
|
|
19
|
+
- `usage_summary` returns the summary's `spend_cap` (overage state, cap, charges, lines per allowance, projected date)
|
|
20
|
+
and `exhausted_reason`, and allowances past `included` while overage is on have `cap_state: "overage"`. Its
|
|
21
|
+
description says so and names the 402 reasons.
|
|
22
|
+
- A start refused with 402 `allowance_exhausted` comes back with `reason` `allowance_used`, `overage_paused` or
|
|
23
|
+
`spend_cap_reached` and `details.spend_cap` (errors already carried `details`).
|
|
24
|
+
|
|
25
|
+
Suspend when idle (contracts §20.6):
|
|
26
|
+
|
|
27
|
+
- `workspace_suspend` takes an optional `after_seconds` (30-3600): suspend when idle instead of now. The workspace is
|
|
28
|
+
suspended once it has been idle that long; the agent's next tool call on it cancels that, and a running command or a
|
|
29
|
+
keepalive postpones it. The tool description and the server instructions tell an agent to use it when it finishes
|
|
30
|
+
its work. The result has `suspend_request`, `operation` (a suspend already in progress, else null) and a `message`
|
|
31
|
+
saying what happens. `after_seconds` with `wait: true` is refused (`invalid_arguments`).
|
|
32
|
+
- Workspace summaries (`workspace_status`, `workspace_list`, `workspace_open`) carry `suspend_request`: the pending
|
|
33
|
+
request, or null.
|
|
34
|
+
|
|
35
|
+
## 0.4.0 (not yet published; npm `latest` is 0.3.1)
|
|
36
|
+
|
|
37
|
+
Needs `@shardflux/sdk` 0.9.0 (the workspace version).
|
|
38
|
+
|
|
39
|
+
File-first workspaces (contracts §29):
|
|
40
|
+
|
|
41
|
+
- `workspace_open` takes `mode` (`processful` | `file_first`); `SHARDFLUX_WORKSPACE_MODE` sets the default. Workspace
|
|
42
|
+
results (`workspace_open`, `workspace_status`, `workspace_list`) carry `mode` and, for file-first workspaces,
|
|
43
|
+
`tree_revision`.
|
|
44
|
+
- `exec` on a file-first workspace runs an execution (the SDK's file-first tool): the result adds `execution_id`,
|
|
45
|
+
`state`, `tree_revision` and `changed`. A call whose deadline passes before the execution ended returns an error
|
|
46
|
+
naming the `execution_id` (it cannot be canceled and continues server side).
|
|
47
|
+
- A pinned server lists only the tools its workspace's mode has (file-first: the files tools and `exec`, no process,
|
|
48
|
+
terminal, git or browser tools, no `workspace_suspend`, `workspace_resume`, `workspace_fork` or `operation_wait`)
|
|
49
|
+
and sends `notifications/tools/list_changed` (capability `tools.listChanged`) when the known mode changes what it
|
|
50
|
+
listed. An unpinned server lists every tool; a tool the named workspace's mode lacks is refused before any request
|
|
51
|
+
(`conflict`, `reason: not_supported_for_mode`, with a `hint`), and so are suspend, resume and fork of a file-first
|
|
52
|
+
workspace.
|
|
53
|
+
- Error results carry `reason` (the refusal's `details.reason`) and, for the file-first refusals, a `hint`:
|
|
54
|
+
`not_supported_for_mode`, `mode_mismatch`, `mode_not_available`, `layout_unsupported`, `tree_revision_mismatch` (with
|
|
55
|
+
`current_tree_revision`), `outside_tree_root`, `execution_in_progress`, `execution_id_reused`, `no_execution_host`.
|
|
56
|
+
- The server instructions describe file-first workspaces.
|
|
57
|
+
|
|
58
|
+
Waking a suspended workspace is one request (contracts §22.6, through `@shardflux/sdk` 0.9.0):
|
|
59
|
+
|
|
60
|
+
- A workspace tool on a suspended workspace wakes it with one held resume that returns the running workspace and this
|
|
61
|
+
server's tool token (its agent label), then runs the tool; the wake's `timing` is one `request(held)` phase. An API
|
|
62
|
+
without the held resume works as before.
|
|
63
|
+
- `workspace_resume` with `wait` goes through the server's workspace handle: one held request, after which the handle
|
|
64
|
+
holds the view and the token, so the next workspace tool starts without a token request.
|
|
65
|
+
|
|
66
|
+
File tools and the wake hint (contracts §26):
|
|
67
|
+
|
|
68
|
+
- `search_files` (read-only) and `edit_file` come from the SDK's `workspaceTools()` with its schemas, plus
|
|
69
|
+
`workspace_key` and `timeout_ms`, filtered by the `files` permission. The server instructions describe them.
|
|
70
|
+
- Every workspace tool call first sends the wake hint through the SDK's tool runner (`workspace.hint()`,
|
|
71
|
+
fire-and-forget; a suspended workspace starts resuming in the background, bounded like any wake by
|
|
72
|
+
`SHARDFLUX_WAKE_TIMEOUT_MS` and the call's deadline), except `read_file`, `list_files` and `search_files`.
|
|
73
|
+
- `read_file`, `list_files` and `search_files` of a suspended workspace whose disk a host still holds are answered
|
|
74
|
+
from that disk without resuming it (contracts §26.4), also as the first call after the suspend: the API now issues
|
|
75
|
+
tool tokens for suspended workspaces. Every other tool still resumes it. With an older API they resume it as before.
|
|
76
|
+
- A workspace on a host that predates search and patches (contracts §26.7, while the fleet is upgraded):
|
|
77
|
+
`search_files` and `edit_file` return `conflict`, `reason: host_feature_unavailable` (`details.feature`
|
|
78
|
+
`file_search` or `file_patch`), `retryable: false`, with a `hint` naming another way (`exec` with `grep`;
|
|
79
|
+
`read_file` then `write_file`). They work once the workspace runs on an upgraded host. `hostFeatureHint()` is
|
|
80
|
+
exported next to `modeHint()`.
|
|
81
|
+
- New refusals come back as error results with their `details.reason`: `revision_mismatch` (with
|
|
82
|
+
`current_revision`), `edit_not_found` / `edit_ambiguous` (with `index`), `host_capacity` (retryable). A read of a
|
|
83
|
+
sleeping workspace its disk cannot answer (`offline_unavailable`, `offline_budget`) wakes it like any
|
|
84
|
+
`workspace_not_running`; `offline_changed` (503) is retried.
|
|
85
|
+
|
|
86
|
+
Version check (contracts §30.4; needs an API that serves `GET /v1/client-versions`):
|
|
87
|
+
|
|
88
|
+
- Version check at startup: after the configuration is validated, the server asks `GET /v1/client-versions` (through
|
|
89
|
+
its own fetch, the SDK's `checkClientVersion`: one request, 3 s timeout, never awaited by the protocol) whether
|
|
90
|
+
`@shardflux/mcp` at its version is current. Outdated or unsupported: one `warn` JSON line on stderr with the notice
|
|
91
|
+
(`@shardflux/mcp 0.4.0 is outdated: 0.5.0 is available. Update: npm install -g @shardflux/mcp@latest`) and
|
|
92
|
+
`status`, `package`, `current`, `latest`, `minimum_supported`, `upgrade_command`, `release_notes_url`. Everything
|
|
93
|
+
else is a `debug` line: current, not listed, `latest: null`, or the request failed. Off with `SHARDFLUX_NO_UPDATE_CHECK=1` (also `true`, `yes`, `on`),
|
|
94
|
+
`NO_UPDATE_NOTIFIER=1`, or `createShardfluxMcpServer(config, { versionCheck: false })`. `checkServerVersion()` and
|
|
95
|
+
`MCP_PACKAGE` are exported.
|
|
96
|
+
- The notice is not added to the server instructions: they are fixed when the server is built and the handshake does
|
|
97
|
+
not wait for the check, so over stdio the client's `initialize` is answered before the check could finish.
|
|
98
|
+
- The SDK client the server builds passes `versionCheck: false`: the SDK ships inside the server, so its own warning
|
|
99
|
+
(about `@shardflux/sdk`, with an `npm install @shardflux/sdk@latest` hint a server user cannot act on) would be
|
|
100
|
+
noise, and `process.emitWarning` would put a non-JSON line on stderr.
|
|
101
|
+
- Server instructions: account-level actions (registering, signing in, organizations, projects, API keys, members,
|
|
102
|
+
billing and plan upgrades, spend alerts, audit export) are done with the `shard` CLI (`npx @shardflux/cli@latest
|
|
103
|
+
--help`; `shard auth login`, `shard setup`, `shard billing upgrade <plan>`). The server gets no account tools: it
|
|
104
|
+
authenticates with a project API key, which the API refuses for account actions (403 `forbidden` on the account
|
|
105
|
+
routes, 401 on the `/v1/auth` session routes); they need a person's session (`sfu_...`), which the CLI signs in.
|
|
106
|
+
|
|
107
|
+
### send_feedback: feedback straight to the founder (needs an API with POST /v1/feedback)
|
|
108
|
+
|
|
109
|
+
- New management tool `send_feedback` (`message`, `category`: bug, confusing, missing, idea, praise or other; optional
|
|
110
|
+
`workspace`, `request_id`, `error_code`, `command`). It calls the SDK's `sendFeedback()` with `client:
|
|
111
|
+
shardflux-mcp/0.4.0` and, as `agent`, the MCP client's name/version from `initialize` (else a non-default
|
|
112
|
+
`SHARDFLUX_AGENT_LABEL`); a pinned server fills `workspace` with its key. Result: `{id, received_at, duplicate,
|
|
113
|
+
note}`. Always listed (any API key may send feedback).
|
|
114
|
+
- The server instructions and the tool description ask the agent to call it actively while it works: the moment a
|
|
115
|
+
call fails unexpectedly, an error or doc is confusing, something is missing or slow, or it needed a workaround, and
|
|
116
|
+
when its user is frustrated about Shardflux or asked for something it could not do (paraphrased, without private
|
|
117
|
+
data; the agent tells its user it sent feedback).
|
|
118
|
+
- Failed calls carry a `feedback` field next to `error`, except the expected flow: `invalid_arguments`,
|
|
119
|
+
`workspace_pinned`, `unauthenticated`, `validation_failed`, `timeout`, and refusals whose error names the next step
|
|
120
|
+
(a stale revision, an edit that does not match, a mode the workspace lacks, a reused execution id). The field is a
|
|
121
|
+
suggestion to call `send_feedback` with category `bug`, the request id and the error code. A failed
|
|
122
|
+
`send_feedback` carries a `hint` (when to retry after `rate_limited`, or email shardflux@heliosone.fi). Additive:
|
|
123
|
+
`error` and `timing` are unchanged.
|
|
124
|
+
- `tools/list` has 12 management tools (was 11). `server.json` names 0.4.0.
|
|
125
|
+
|
|
6
126
|
## 0.3.1 (2026-09-28)
|
|
7
127
|
|
|
8
128
|
Needs `@shardflux/sdk` 0.8.0 or later. No tool changes: the server moves to the SDK's 0.8.0 (a types-only release whose
|
package/README.md
CHANGED
|
@@ -157,12 +157,15 @@ environment `SHARDFLUX_API_KEY`.
|
|
|
157
157
|
| `SHARDFLUX_API_URL` | Default `https://api.shardflux.dev`. Plain `http://` is allowed only for loopback hosts. |
|
|
158
158
|
| `SHARDFLUX_WORKSPACE_KEY` | Pins one workspace. `workspace_key` becomes optional on every tool, and a different key is refused with `workspace_pinned`. |
|
|
159
159
|
| `SHARDFLUX_TEMPLATE` | Default template for `workspace_open`. |
|
|
160
|
+
| `SHARDFLUX_WORKSPACE_MODE` | (0.4.0) `processful` or `file_first`: the `mode` `workspace_open` sends when the call names none (unset: the API's default, processful for a new key). A pinned server also lists the tools of this mode until its workspace exists. |
|
|
160
161
|
| `SHARDFLUX_MCP_TOOL_TIMEOUT_MS` | Default **and maximum** deadline per tool call, 1000-3600000 ms. Default 120000. A call's `timeout_ms` can only shorten it. |
|
|
161
162
|
| `SHARDFLUX_WAKE` | `on` (default) or `off`. On: workspace tools resume a suspended workspace and then run (see "Contract"). Off: they return the `workspace_not_running` refusal. |
|
|
162
163
|
| `SHARDFLUX_WAKE_TIMEOUT_MS` | Longest wait per tool call for a workspace to wake or finish a transition, 1000-3600000 ms. Default 120000. It is clamped to `SHARDFLUX_MCP_TOOL_TIMEOUT_MS`, and each wake also ends 250 ms before the call's own deadline. |
|
|
163
164
|
| `SHARDFLUX_AGENT_LABEL` | Attribution label of every tool token this server obtains. Default `mcp`. It appears as an agent session in `GET /v1/workspaces/{id}/agent-sessions`, the console and `shard ws sessions`. |
|
|
164
165
|
| `SHARDFLUX_MCP_LOG_LEVEL` | `debug`, `info` (default), `warn` or `error`. Logs are JSON lines on **stderr**; stdout is the protocol. The key is never logged, and anything key-shaped is redacted. |
|
|
165
166
|
| `SHARDFLUX_HTTP_KEEPALIVE=1` | Reuse HTTP connections. By default every request uses a fresh connection (`Connection: close`), because on some Node.js releases a request sent on a reused keep-alive connection can stall until the request timeout. |
|
|
167
|
+
| `SHARDFLUX_NO_UPDATE_CHECK` | (0.4.0) `1`, `true`, `yes` or `on` turns off the startup version check (see "Updates"). |
|
|
168
|
+
| `NO_UPDATE_NOTIFIER` | (0.4.0) The npm convention: any non-empty value also turns the check off. |
|
|
166
169
|
|
|
167
170
|
Invalid configuration exits with code 2 before serving, with one JSON line on stderr, for example:
|
|
168
171
|
|
|
@@ -173,19 +176,64 @@ Invalid configuration exits with code 2 before serving, with one JSON line on st
|
|
|
173
176
|
`template_build` reads local files (`file`, and `from` paths in a recipe) only inside the server's
|
|
174
177
|
working directory: the directory your MCP client starts it in.
|
|
175
178
|
|
|
179
|
+
## Feedback: agents are asked to use `send_feedback` while they work
|
|
180
|
+
|
|
181
|
+
**(0.4.0+)** The `send_feedback` tool sends a message straight to the Shardflux founder, who reads every one. The
|
|
182
|
+
server instructions and the tool description ask the agent to call it actively during its work, not once at the end:
|
|
183
|
+
the moment a call fails unexpectedly, an error or doc is confusing, something is missing or slow, or it needed a
|
|
184
|
+
workaround; when its user complains or is frustrated about Shardflux or the workflow around it (paraphrased, with what
|
|
185
|
+
they were trying to do); and when its user asked for something Shardflux could not do, or made awkward. Short and
|
|
186
|
+
frequent beats polished, with the `request_id` and error code from the error. The agent tells its user it sent
|
|
187
|
+
feedback, and leaves out private data about the user and secrets or code they did not mean to share (paraphrase, never
|
|
188
|
+
transcripts). Failed calls carry a ready-made suggestion in a `feedback` field (see "Contract").
|
|
189
|
+
|
|
190
|
+
- Arguments: `message` (1-8000 characters) and `category` (`bug`, `confusing`, `missing`, `idea`, `praise`, `other`),
|
|
191
|
+
plus optional `workspace` (default: the pinned key), `request_id`, `error_code` and `command`.
|
|
192
|
+
- The server adds `client: shardflux-mcp/<version>` and, as `agent`, the MCP client's name and version from
|
|
193
|
+
`initialize` (e.g. `claude-code/2.1.0`; without one, `SHARDFLUX_AGENT_LABEL` when it is not the default `mcp`).
|
|
194
|
+
- Result: `{id, received_at, duplicate, note}`. `duplicate: true`: the same message from this key in the last 24 hours;
|
|
195
|
+
it is not emailed again.
|
|
196
|
+
- Any API key may send it (no tool permission), so it is always listed. It is rate limited per key: `rate_limited`
|
|
197
|
+
with `details.retry_after_seconds`, and a `hint` naming the wait and the fallback address, shardflux@heliosone.fi.
|
|
198
|
+
|
|
199
|
+
## Updates (0.4.0+)
|
|
200
|
+
|
|
201
|
+
At startup, after the configuration is validated, the server asks the API's `GET /v1/client-versions` whether
|
|
202
|
+
`@shardflux/mcp` at its version is current. It sends one request (3 s timeout) in the background: the MCP handshake
|
|
203
|
+
and tool calls never wait for it, and any failure is ignored. When the server is outdated, or below the oldest version
|
|
204
|
+
the API still supports, it logs one `warn` line on stderr:
|
|
205
|
+
|
|
206
|
+
```json
|
|
207
|
+
{"time":"2026-10-01T09:00:00.000Z","level":"warn","service":"shardflux-mcp","message":"@shardflux/mcp 0.4.0 is outdated: 0.5.0 is available. Update: npm install -g @shardflux/mcp@latest","status":"outdated","package":"@shardflux/mcp","current":"0.4.0","latest":"0.5.0","minimum_supported":"0.1.0","upgrade_command":"npm install -g @shardflux/mcp@latest","release_notes_url":"https://www.npmjs.com/package/@shardflux/mcp?activeTab=versions"}
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
- An unsupported server says `... is no longer supported by the Shardflux API (minimum <version>). Update: ...`
|
|
211
|
+
(`"status":"unsupported"`). It keeps running.
|
|
212
|
+
- Otherwise it stays silent (one `debug` line), also when the API does not answer.
|
|
213
|
+
- The server instructions do not carry the notice: they are fixed when the server is built, and the handshake does not
|
|
214
|
+
wait for the check.
|
|
215
|
+
- `SHARDFLUX_NO_UPDATE_CHECK=1` or `NO_UPDATE_NOTIFIER=1` turns the check off; `createShardfluxMcpServer(config,
|
|
216
|
+
{ versionCheck: false })` does too.
|
|
217
|
+
- The `@shardflux/sdk` client inside the server runs no check of its own (`versionCheck: false`). The SDK ships inside
|
|
218
|
+
the server, so the update to install is the server's.
|
|
219
|
+
|
|
176
220
|
## Tools
|
|
177
221
|
|
|
178
222
|
The management tools:
|
|
179
223
|
|
|
180
224
|
| Tool | Does |
|
|
181
225
|
| --- | --- |
|
|
182
|
-
| `workspace_open` | Open by key: create on first use, reconnect or resume afterwards, never reset. Waits until the workspace is ready (its template's start commands and services included) unless `wait: false`. `lifetime: "session"` opens a workspace that is discarded after its idle timeout. `inputs` (0.3.0) passes the template's text inputs, `{"NAME": "value"}`. |
|
|
226
|
+
| `workspace_open` | Open by key: create on first use, reconnect or resume afterwards, never reset. Waits until the workspace is ready (its template's start commands and services included) unless `wait: false`. `lifetime: "session"` opens a workspace that is discarded after its idle timeout. `inputs` (0.3.0) passes the template's text inputs, `{"NAME": "value"}`. `mode` (0.4.0): `processful` or `file_first` (see "File-first workspaces"). |
|
|
183
227
|
| `workspace_list` | List the project's workspaces, with `prefix`, `state`, `lifetime`, `purpose`, `include_deleted`, `limit` and `cursor`. By default only persistent standard workspaces are listed. |
|
|
184
228
|
| `workspace_status` | One workspace plus its five most recent operations. |
|
|
185
229
|
| `workspace_suspend`, `workspace_resume` | Lifecycle operation. Returns at once unless `wait: true` (then once it finished, through the SDK's own `wait`). |
|
|
230
|
+
| `workspace_suspend` with `after_seconds` | (0.4.1) Suspend when idle: the workspace is suspended once it has been idle for `after_seconds` (30-3600), instead of now. The description tells the agent to use it when it finishes its work, so the workspace stops using RAM soon after. Its next tool call on the workspace cancels it; a running command or a keepalive postpones it. Returns `suspend_request` (`not_before`: the earliest suspend) and a `message`; `operation` when a suspend was already in progress. Not with `wait`. `workspace_status` shows a pending request as `workspace.suspend_request`. Not for file-first workspaces. |
|
|
186
231
|
| `workspace_fork` | Fork into `new_key`. |
|
|
187
232
|
| `operation_wait` | Keep waiting for an operation. |
|
|
233
|
+
| `usage_summary` | The organization's usage summary for the current period: meters, allowances with their cap state, `allowance_exhausted` with `exhausted_reason`, and (0.4.1+, an API with opt-in overage) `spend_cap`: the overage state, cap, charges, lines per allowance and the date the cap is projected to be reached. |
|
|
234
|
+
|
|
188
235
|
| `usage_summary` | The organization's usage summary for the current period. |
|
|
236
|
+
| `send_feedback` | (0.4.0) Feedback straight to the Shardflux founder: `message`, `category`, and optional `workspace`, `request_id`, `error_code`, `command`. See [Feedback](#feedback-agents-are-asked-to-use-send_feedback-while-they-work). |
|
|
189
237
|
| `template_get` | (0.3.0) A template's versions with their settings (env, inputs, start commands, services, defaults); with `version`, that version's recipe in request form. |
|
|
190
238
|
| `template_languages` | (0.3.0) The languages and versions a base (`<slug>@<version>`) offers `build.languages`. |
|
|
191
239
|
| `template_build` | (0.3.0) Build a version of an organization template from a recipe v2: `recipe` (the document) or `file` (a template.yaml or .json path). File entries may name local `from` paths, uploaded first (folders as a tar). **Every local path must resolve inside the server's working directory**; others are refused before any request. Unpublished unless `publish: true`; `wait: true` follows the build within the call's deadline. |
|
|
@@ -193,7 +241,7 @@ The management tools:
|
|
|
193
241
|
The SDK's workspace tools come from `workspaceTools()`:
|
|
194
242
|
|
|
195
243
|
- `exec`
|
|
196
|
-
- `read_file`, `write_file`, `list_files`
|
|
244
|
+
- `read_file`, `write_file`, `list_files`, and `search_files`, `edit_file` (0.4.0+)
|
|
197
245
|
- `list_processes`, `signal_process`
|
|
198
246
|
- `terminal_open`, `terminal_send`, `terminal_read`, `terminal_close`
|
|
199
247
|
- `git_clone`, `git_status`, `git_commit`
|
|
@@ -202,16 +250,73 @@ The SDK's workspace tools come from `workspaceTools()`:
|
|
|
202
250
|
They are published with **the SDK's JSON Schemas verbatim**, plus `workspace_key` and, where the
|
|
203
251
|
SDK schema has none, `timeout_ms`. They are filtered by the key's tool permissions (if the server
|
|
204
252
|
cannot read them, for example because the API is unreachable, it lists every workspace tool and logs
|
|
205
|
-
a warning). `
|
|
253
|
+
a warning). `exec`'s `cwd` is an absolute path (default `/home/user`): a relative one is an error result with
|
|
254
|
+
`details.reason: invalid_cwd` whose message names the absolute path it likely means, and a command that could not
|
|
255
|
+
start (a `cwd` that is not a directory, a program not on `PATH`) returns `exit_code: null` with `error: { code,
|
|
256
|
+
message, reason: "exec_failed_to_start" }` naming the workspace's reason (0.4.1+). `browser_screenshot` returns an MCP image content block. `search_files` is annotated read-only; `edit_file` replaces
|
|
257
|
+
exact text and, without an `expected_revision`, reads the file's revision first so a concurrent change fails the
|
|
258
|
+
edit (`conflict`, `details.reason: revision_mismatch`) instead of being overwritten.
|
|
259
|
+
|
|
260
|
+
Each workspace tool call first sends the wake hint (`POST /wake-hint`, without waiting for it): a workspace its host
|
|
261
|
+
has parked starts restoring while the call is prepared, and a suspended one starts resuming. `read_file`,
|
|
262
|
+
`list_files` and `search_files` send none (0.4.0+): a suspended workspace whose disk its host still holds is read,
|
|
263
|
+
listed and searched there without resuming it (the state at suspension).
|
|
264
|
+
|
|
265
|
+
While the fleet is being upgraded a workspace may run on a host that predates `search_files` and `edit_file`: they
|
|
266
|
+
fail with `conflict`, `reason: host_feature_unavailable` (`details.feature` `file_search` or `file_patch`),
|
|
267
|
+
`retryable: false` and a `hint` naming another way (`exec` with `grep`; `read_file` then `write_file`). It lasts
|
|
268
|
+
until the workspace runs on an upgraded host.
|
|
206
269
|
|
|
207
270
|
Deleting a workspace is deliberately not exposed to agents; use the
|
|
208
271
|
[CLI](https://www.npmjs.com/package/@shardflux/cli) or the console.
|
|
209
272
|
|
|
273
|
+
Account-level actions are not tools either: registering, signing in, organizations, projects, API keys, members,
|
|
274
|
+
invitations, billing and plan upgrades, spend alerts and the audit export. The server authenticates with a project API
|
|
275
|
+
key, and the API refuses a project API key for them: 403 `forbidden` ("API keys cannot perform this action.") on the
|
|
276
|
+
account routes, 401 on the `/v1/auth` session routes. They need a person's session. From 0.4.0 the server instructions tell the agent to use the `shard` CLI for them
|
|
277
|
+
(`npx @shardflux/cli@latest --help`; for example `shard auth login`, `shard setup`, `shard billing upgrade <plan>`),
|
|
278
|
+
which signs a person in with a CLI session.
|
|
279
|
+
|
|
210
280
|
Every tool finds its workspace by exact key across every lifetime and purpose, so sessions, template drafts and test
|
|
211
281
|
instances resolve too. The live workspace wins over tombstones that ended sessions left with the same key. Workspace
|
|
212
282
|
results include `lifetime`, `purpose`, `disk_layout`, `idle_timeout_seconds`, `ended_reason` and (0.3.0) `startup`:
|
|
213
283
|
the state of the template's start commands and services (`failed` names the step, its exit code and output tail).
|
|
214
284
|
|
|
285
|
+
### File-first workspaces (0.4.0)
|
|
286
|
+
|
|
287
|
+
`workspace_open` with `mode: "file_first"` (or `SHARDFLUX_WORKSPACE_MODE=file_first`) opens a file-first workspace
|
|
288
|
+
(contracts §29): a versioned file tree under /home/user with no VM between calls. It is ready at once and never
|
|
289
|
+
suspended. Workspace results carry `mode` and, for file-first workspaces, `tree_revision`.
|
|
290
|
+
|
|
291
|
+
- `exec` runs each command as an execution: a fresh VM on the workspace's files. Only files under /home/user persist
|
|
292
|
+
between calls. The result adds `execution_id`, `state`, `tree_revision` and `changed` (the paths the command added,
|
|
293
|
+
modified or deleted, up to 200, with `changed_truncated`). An execution cannot be canceled: when the call's
|
|
294
|
+
deadline passes first, the error carries its `execution_id` and the execution continues server side.
|
|
295
|
+
- The files tools work as on a processful workspace. Each change publishes the next tree revision.
|
|
296
|
+
- The process, terminal, git and browser tools and `workspace_suspend`, `workspace_resume`, `workspace_fork` and
|
|
297
|
+
`operation_wait` do not apply. A **pinned** server lists only the tools of its workspace's mode: the mode of the
|
|
298
|
+
pinned key once it is known, else `SHARDFLUX_WORKSPACE_MODE`, else processful. When the known mode changes what it
|
|
299
|
+
listed (for example the pinned key is opened file-first after the first `tools/list`), it sends
|
|
300
|
+
`notifications/tools/list_changed`. An unpinned server lists every tool; calling one the named workspace's mode
|
|
301
|
+
lacks returns an error with `reason: "not_supported_for_mode"` and a `hint`, before any request.
|
|
302
|
+
- Errors of file-first workspaces carry `reason` and a `hint`: `not_supported_for_mode`, `mode_mismatch` (a key's
|
|
303
|
+
mode never changes), `mode_not_available` (the deployment does not offer file-first workspaces), `layout_unsupported`
|
|
304
|
+
(a legacy template), `tree_revision_mismatch` (with `current_tree_revision`), `outside_tree_root`,
|
|
305
|
+
`execution_in_progress` (another execution holds the workspace), `execution_id_reused` and `no_execution_host` (no
|
|
306
|
+
host had room, also after the SDK's retries with the same execution id; nothing ran).
|
|
307
|
+
|
|
308
|
+
```json
|
|
309
|
+
{
|
|
310
|
+
"mcpServers": {
|
|
311
|
+
"shardflux": {
|
|
312
|
+
"command": "node",
|
|
313
|
+
"args": ["/path/to/packages/mcp/src/bin.ts"],
|
|
314
|
+
"env": { "SHARDFLUX_API_KEY": "sfk_...", "SHARDFLUX_WORKSPACE_KEY": "me/agent-files", "SHARDFLUX_TEMPLATE": "python-node-browser", "SHARDFLUX_WORKSPACE_MODE": "file_first" }
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
```
|
|
319
|
+
|
|
215
320
|
## Contract
|
|
216
321
|
|
|
217
322
|
- **Arguments.** They are validated against the published schema with the SDK's `validateArgs`
|
|
@@ -223,6 +328,15 @@ the state of the template's start commands and services (`failed` names the step
|
|
|
223
328
|
- A failed operation is `code: operation_failed` with `details.error_code` (and `details.reason` when the
|
|
224
329
|
operation error has one) and, from 0.2.1, `retryable`: the operation error's own flag.
|
|
225
330
|
- An API or cell gateway the server cannot reach is `code: network_error`, `retryable: true`.
|
|
331
|
+
- A start refused because an allowance is used up is `code: allowance_exhausted` (402) with `reason` (0.4.1+):
|
|
332
|
+
`allowance_used` (upgrade, or turn on overage), `overage_paused` (a plan payment is past due) or
|
|
333
|
+
`spend_cap_reached` (raise the spend cap or upgrade), and `details.spend_cap`. An owner or billing member acts
|
|
334
|
+
on it in the console; retrying does not help.
|
|
335
|
+
|
|
336
|
+
- **(0.4.0+)** A failed call has a `feedback` field next to `error`: a one-sentence suggestion to call
|
|
337
|
+
`send_feedback` with category `bug`, the `request_id` and the error code. Not for `invalid_arguments`,
|
|
338
|
+
`workspace_pinned` or `unauthenticated`. A failed `send_feedback` carries a `hint` instead (when to retry, or the
|
|
339
|
+
email address).
|
|
226
340
|
- **Starts wait for capacity for at most 15 minutes (0.2.1+).** An open, resume or fork that no host can admit waits
|
|
227
341
|
in `capacity_pending`; a wait that ends first returns `code: timeout`, and its message names when the start gives
|
|
228
342
|
up. A start still pending then fails: `operation_failed`, `details.error_code: capacity_unavailable`,
|
|
@@ -259,10 +373,13 @@ the state of the template's start commands and services (`failed` names the step
|
|
|
259
373
|
HTTP requests to the API and to the cell gateway, and backoff sleeps. No response is sent, and
|
|
260
374
|
the server stays up. The lifecycle operation itself is not canceled.
|
|
261
375
|
- **Workspace tools wake suspended workspaces, but do not open new ones.** A workspace that was
|
|
262
|
-
never opened is `not_found`: call `workspace_open` first.
|
|
376
|
+
never opened is `not_found`: call `workspace_open` first. Reads (`read_file`, `list_files`,
|
|
377
|
+
`search_files`) of a suspended workspace whose disk a host still holds are answered from that disk
|
|
378
|
+
without waking it.
|
|
263
379
|
- A suspended workspace is resumed (or the resume or open already running is joined), and the
|
|
264
380
|
call runs once it is running. A call made during a suspend or resume waits for it to finish.
|
|
265
|
-
The call never runs twice: the refused attempt did nothing.
|
|
381
|
+
The call never runs twice: the refused attempt did nothing. The resume is one request held until
|
|
382
|
+
the workspace runs, answered with this server's tool token (0.4.0+, an API with the held resume).
|
|
266
383
|
- The wait is bounded by `SHARDFLUX_WAKE_TIMEOUT_MS` and the call's deadline. Past it the
|
|
267
384
|
result is `code: timeout` with the `operation_id` and `last_state` of the resume or open. That
|
|
268
385
|
operation continues server side: `operation_wait` keeps waiting. A resume or open that fails
|
package/dist/config.d.ts
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
* Environment configuration of the local MCP server (README "Configuration").
|
|
3
3
|
* The API key is validated for shape only here; it is never logged or echoed.
|
|
4
4
|
*/
|
|
5
|
+
import type { WorkspaceMode } from '@shardflux/sdk';
|
|
5
6
|
export interface McpConfig {
|
|
6
7
|
apiKey: string;
|
|
7
8
|
apiUrl: string;
|
|
@@ -9,6 +10,11 @@ export interface McpConfig {
|
|
|
9
10
|
workspaceKey: string | undefined;
|
|
10
11
|
/** Template used by workspace_open when the call names none. */
|
|
11
12
|
template: string | undefined;
|
|
13
|
+
/**
|
|
14
|
+
* SHARDFLUX_WORKSPACE_MODE (0.4.0): the mode workspace_open sends when the call names none (unset: none, the API's
|
|
15
|
+
* default), and the mode tools/list assumes for a pinned key that does not exist yet. Optional for embedders.
|
|
16
|
+
*/
|
|
17
|
+
workspaceMode?: WorkspaceMode | undefined;
|
|
12
18
|
/** Default and maximum per-call deadline (ms). */
|
|
13
19
|
toolTimeoutMs: number;
|
|
14
20
|
/** Workspace tools resume a suspended workspace on use (SHARDFLUX_WAKE, default on). */
|
package/dist/config.js
CHANGED
|
@@ -1,7 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Environment configuration of the local MCP server (README "Configuration").
|
|
3
|
-
* The API key is validated for shape only here; it is never logged or echoed.
|
|
4
|
-
*/
|
|
5
1
|
export class ConfigError extends Error {
|
|
6
2
|
name = 'ConfigError';
|
|
7
3
|
}
|
|
@@ -42,6 +38,10 @@ export function loadConfig(env) {
|
|
|
42
38
|
const template = env.SHARDFLUX_TEMPLATE?.trim() || undefined;
|
|
43
39
|
if (template !== undefined && !SLUG.test(template))
|
|
44
40
|
throw new ConfigError('SHARDFLUX_TEMPLATE must be a template slug (lowercase letters, digits, . _ -)');
|
|
41
|
+
const modeText = env.SHARDFLUX_WORKSPACE_MODE?.trim().toLowerCase() || undefined;
|
|
42
|
+
if (modeText !== undefined && modeText !== 'processful' && modeText !== 'file_first')
|
|
43
|
+
throw new ConfigError('SHARDFLUX_WORKSPACE_MODE must be processful or file_first');
|
|
44
|
+
const workspaceMode = modeText;
|
|
45
45
|
let toolTimeoutMs = DEFAULT_TOOL_TIMEOUT_MS;
|
|
46
46
|
const t = env.SHARDFLUX_MCP_TOOL_TIMEOUT_MS?.trim();
|
|
47
47
|
if (t) {
|
|
@@ -71,5 +71,5 @@ export function loadConfig(env) {
|
|
|
71
71
|
const level = (env.SHARDFLUX_MCP_LOG_LEVEL?.trim() || 'info');
|
|
72
72
|
if (!['debug', 'info', 'warn', 'error'].includes(level))
|
|
73
73
|
throw new ConfigError('SHARDFLUX_MCP_LOG_LEVEL must be debug, info, warn or error');
|
|
74
|
-
return { apiKey, apiUrl: apiUrlFrom(env.SHARDFLUX_API_URL), workspaceKey, template, toolTimeoutMs, wake: wakeText === 'on', wakeTimeoutMs, agentLabel, logLevel: level };
|
|
74
|
+
return { apiKey, apiUrl: apiUrlFrom(env.SHARDFLUX_API_URL), workspaceKey, template, workspaceMode, toolTimeoutMs, wake: wakeText === 'on', wakeTimeoutMs, agentLabel, logLevel: level };
|
|
75
75
|
}
|
package/dist/errors.d.ts
CHANGED
|
@@ -8,17 +8,41 @@ export declare class ToolError extends Error {
|
|
|
8
8
|
export interface ToolErrorInfo {
|
|
9
9
|
code: string;
|
|
10
10
|
message: string;
|
|
11
|
+
/** details.reason of an API or cell refusal (0.3.0). */
|
|
12
|
+
reason?: string;
|
|
11
13
|
status?: number;
|
|
12
14
|
request_id?: string;
|
|
13
15
|
retryable?: boolean;
|
|
14
16
|
operation_id?: string;
|
|
17
|
+
/** A file-first exec whose call ended before its execution did: the execution continues server side (0.4.0). */
|
|
18
|
+
execution_id?: string;
|
|
19
|
+
/** tree_revision_mismatch: the revision the workspace's file tree is at (0.4.0). */
|
|
20
|
+
current_tree_revision?: number;
|
|
15
21
|
source?: 'api' | 'cell';
|
|
16
22
|
details?: Record<string, unknown>;
|
|
17
23
|
issues?: string[];
|
|
18
24
|
last_state?: string;
|
|
25
|
+
/** What to do instead (0.4.0): refusals that concern a workspace's mode (contracts §29) or a host that predates a tool (§26.7). */
|
|
26
|
+
hint?: string;
|
|
19
27
|
}
|
|
20
28
|
export declare function redact(text: string, key?: string): string;
|
|
21
|
-
|
|
29
|
+
/**
|
|
30
|
+
* What the model can do about a refusal that concerns a workspace's mode (contracts §29.7, §29.8), from its
|
|
31
|
+
* details.reason and details; undefined for every other refusal.
|
|
32
|
+
*/
|
|
33
|
+
export declare function modeHint(reason: string | undefined, details?: Record<string, unknown>): string | undefined;
|
|
34
|
+
/**
|
|
35
|
+
* 409 conflict host_feature_unavailable (contracts §26.7; 0.4.0): the workspace runs on a host that predates the tool,
|
|
36
|
+
* until it runs on an upgraded host (minutes to hours). Not retryable, so the hint names another way to do it now.
|
|
37
|
+
*/
|
|
38
|
+
export declare function hostFeatureHint(reason: string | undefined, details?: Record<string, unknown>): string | undefined;
|
|
39
|
+
export interface DescribeContext {
|
|
22
40
|
operationId?: string | undefined;
|
|
23
41
|
timeoutMs?: number;
|
|
24
|
-
|
|
42
|
+
/**
|
|
43
|
+
* The execution id a file-first exec call sent (the SDK's onExecution). Named in deadline, network and protocol
|
|
44
|
+
* errors, after which the execution may be running server side; an API refusal leaves the id unused and omits it.
|
|
45
|
+
*/
|
|
46
|
+
executionId?: string | undefined;
|
|
47
|
+
}
|
|
48
|
+
export declare function describeToolError(err: unknown, context?: DescribeContext): ToolErrorInfo;
|
package/dist/errors.js
CHANGED
|
@@ -2,8 +2,12 @@
|
|
|
2
2
|
* Tool errors are returned as MCP tool results (`isError: true`) whose
|
|
3
3
|
* content is `{ "error": { code, message, ... } }` with the API's closed error
|
|
4
4
|
* code, never as protocol failures, so the calling model can react to them.
|
|
5
|
+
* API and cell refusals add `reason` (their details.reason) and, for refusals
|
|
6
|
+
* that concern a workspace's mode (file-first workspaces, contracts §29) or a
|
|
7
|
+
* host that predates a tool (host_feature_unavailable, contracts §26.7), a
|
|
8
|
+
* `hint` saying what to do instead.
|
|
5
9
|
*/
|
|
6
|
-
import { OperationFailedError, OperationTimeoutError, ShardfluxApiError, ShardfluxProtocolError, TemplateUploadError, ToolArgumentError } from '@shardflux/sdk';
|
|
10
|
+
import { OperationFailedError, OperationTimeoutError, ShardfluxApiError, ShardfluxProtocolError, TemplateUploadError, ToolArgumentError, TreeRevisionMismatchError } from '@shardflux/sdk';
|
|
7
11
|
/** A refusal decided by this server (argument/pinning problems, unknown workspace key). */
|
|
8
12
|
export class ToolError extends Error {
|
|
9
13
|
name = 'ToolError';
|
|
@@ -23,17 +27,84 @@ export function redact(text, key) {
|
|
|
23
27
|
return out;
|
|
24
28
|
}
|
|
25
29
|
const named = (err, name) => typeof err === 'object' && err !== null && err.name === name;
|
|
30
|
+
const str = (v) => (typeof v === 'string' && v !== '' ? v : undefined);
|
|
31
|
+
/** The tools a file-first workspace has, for hints. */
|
|
32
|
+
const FILE_FIRST_TOOLS_TEXT = 'Run commands with exec and use read_file, write_file, list_files, search_files and edit_file for files; only files under /home/user persist between exec calls.';
|
|
33
|
+
/**
|
|
34
|
+
* What the model can do about a refusal that concerns a workspace's mode (contracts §29.7, §29.8), from its
|
|
35
|
+
* details.reason and details; undefined for every other refusal.
|
|
36
|
+
*/
|
|
37
|
+
export function modeHint(reason, details = {}) {
|
|
38
|
+
const mode = str(details.mode);
|
|
39
|
+
switch (reason) {
|
|
40
|
+
case 'not_supported_for_mode':
|
|
41
|
+
if (mode === 'processful')
|
|
42
|
+
return 'This workspace is processful: exec runs commands in its own VM, where files, packages and processes persist; executions and tree revisions exist only for file-first workspaces.';
|
|
43
|
+
if (details.field === 'lifetime')
|
|
44
|
+
return 'A file-first workspace is persistent: open it without lifetime "session", or use mode "processful" for a session workspace.';
|
|
45
|
+
if (details.field === 'idle_policy')
|
|
46
|
+
return 'A file-first workspace is never suspended, so it has no idle policy: open it without one.';
|
|
47
|
+
return `This workspace is file-first: it has no VM between exec calls, so processes, terminals, version control sessions and browsers do not outlive one exec call, and it is never suspended, resumed or forked. ${FILE_FIRST_TOOLS_TEXT} Start servers, run git or a headless browser within the exec command that needs them.`;
|
|
48
|
+
case 'mode_mismatch': {
|
|
49
|
+
const requested = str(details.requested_mode);
|
|
50
|
+
return mode
|
|
51
|
+
? `A workspace's mode never changes: this key belongs to a ${mode} workspace. Open it with mode "${mode}"${requested ? `, or use another key for a ${requested} workspace` : ''}.`
|
|
52
|
+
: "A workspace's mode never changes: open this key with the mode it was created with, or use another key.";
|
|
53
|
+
}
|
|
54
|
+
case 'mode_not_available':
|
|
55
|
+
return 'This deployment does not offer file-first workspaces yet: open the workspace with mode "processful" (or without mode).';
|
|
56
|
+
case 'layout_unsupported':
|
|
57
|
+
return mode === 'file_first'
|
|
58
|
+
? 'File-first workspaces run on layered template versions and this one is not: pick another template (template_get lists its versions and disk_layouts) or open with mode "processful".'
|
|
59
|
+
: 'This template version needs a disk layout this deployment does not offer: pick another template version (template_get lists them with their disk_layouts).';
|
|
60
|
+
case 'tree_revision_mismatch':
|
|
61
|
+
return `The workspace's files changed since that tree revision${typeof details.current_tree_revision === 'number' ? ` (the tree is at revision ${details.current_tree_revision})` : ''}: read the files again, then retry.`;
|
|
62
|
+
case 'outside_tree_root':
|
|
63
|
+
return `Only paths under ${str(details.tree_root) ?? '/home/user'} exist in a file-first workspace (/ and /home cannot change): use a path under it.`;
|
|
64
|
+
case 'execution_in_progress':
|
|
65
|
+
return `Another exec${str(details.execution_id) ? ` (execution ${str(details.execution_id)})` : ''} is running on this file-first workspace; file changes and further exec calls wait until it ended. Retry then.`;
|
|
66
|
+
case 'execution_id_reused':
|
|
67
|
+
return 'That execution id was already used for a different command; call exec again (each call uses a new execution id).';
|
|
68
|
+
case 'no_execution_host':
|
|
69
|
+
return 'No host has room to run the command now and nothing ran; retry in a few seconds.';
|
|
70
|
+
default:
|
|
71
|
+
return undefined;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* 409 conflict host_feature_unavailable (contracts §26.7; 0.4.0): the workspace runs on a host that predates the tool,
|
|
76
|
+
* until it runs on an upgraded host (minutes to hours). Not retryable, so the hint names another way to do it now.
|
|
77
|
+
*/
|
|
78
|
+
export function hostFeatureHint(reason, details = {}) {
|
|
79
|
+
if (reason !== 'host_feature_unavailable')
|
|
80
|
+
return undefined;
|
|
81
|
+
const later = 'It will work once the workspace runs on an upgraded host; retrying now does not help.';
|
|
82
|
+
switch (details.feature) {
|
|
83
|
+
case 'file_search':
|
|
84
|
+
return `This workspace's host does not support search_files yet. Search with exec instead, e.g. grep -rn -- PATTERN PATH (add -i to ignore case, -E for a regular expression). ${later}`;
|
|
85
|
+
case 'file_patch':
|
|
86
|
+
return `This workspace's host does not support edit_file yet. Use read_file, change the text, then write_file with the whole new content. ${later}`;
|
|
87
|
+
default:
|
|
88
|
+
return `This workspace's host does not support this tool yet. ${later}`;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
26
91
|
export function describeToolError(err, context = {}) {
|
|
27
92
|
if (err instanceof ShardfluxApiError) {
|
|
93
|
+
const hint = modeHint(err.reason, err.details ?? {}) ?? hostFeatureHint(err.reason, err.details ?? {});
|
|
28
94
|
return {
|
|
29
95
|
code: err.code,
|
|
30
96
|
message: err.message,
|
|
97
|
+
...(err.reason ? { reason: err.reason } : {}),
|
|
31
98
|
status: err.status,
|
|
32
|
-
|
|
99
|
+
// A refusal the SDK made itself (NotSupportedForModeError.local: no request) has no request id.
|
|
100
|
+
...(err.requestId ? { request_id: err.requestId } : {}),
|
|
33
101
|
retryable: err.retryable,
|
|
34
102
|
source: err.source,
|
|
35
103
|
...(err.operationId ? { operation_id: err.operationId } : {}),
|
|
104
|
+
// No execution_id: a refusal answers before anything ran, and the execution id stays unused (contracts §29.8).
|
|
105
|
+
...(err instanceof TreeRevisionMismatchError && err.currentTreeRevision !== null ? { current_tree_revision: err.currentTreeRevision } : {}),
|
|
36
106
|
...(err.details ? { details: err.details } : {}),
|
|
107
|
+
...(hint ? { hint } : {}),
|
|
37
108
|
};
|
|
38
109
|
}
|
|
39
110
|
if (err instanceof ToolArgumentError)
|
|
@@ -53,20 +124,32 @@ export function describeToolError(err, context = {}) {
|
|
|
53
124
|
return { code: 'operation_failed', message: `${err.message}${hint}`, operation_id: err.operationId, retryable: err.retryable, ...(Object.keys(details).length ? { details } : {}) };
|
|
54
125
|
}
|
|
55
126
|
if (named(err, 'TimeoutError')) {
|
|
127
|
+
// A file-first execution cannot be canceled: it runs to its end server side and then publishes what it changed.
|
|
128
|
+
const continues = context.executionId
|
|
129
|
+
? `; execution ${context.executionId} continues server side (a file-first execution cannot be canceled): the files it changes are published when it ends, and the next exec or file change on this workspace waits for it`
|
|
130
|
+
: context.operationId
|
|
131
|
+
? '; the operation continues server side (use operation_wait)'
|
|
132
|
+
: '';
|
|
56
133
|
return {
|
|
57
134
|
code: 'timeout',
|
|
58
|
-
message: `The tool call did not finish within ${context.timeoutMs ?? 'its'} ms${
|
|
135
|
+
message: `The tool call did not finish within ${context.timeoutMs ?? 'its'} ms${continues}.`,
|
|
59
136
|
retryable: true,
|
|
60
|
-
...(context.operationId ? { operation_id: context.operationId } : {}),
|
|
137
|
+
...(context.operationId && !context.executionId ? { operation_id: context.operationId } : {}),
|
|
138
|
+
...(context.executionId ? { execution_id: context.executionId } : {}),
|
|
61
139
|
};
|
|
62
140
|
}
|
|
63
141
|
if (err instanceof TemplateUploadError)
|
|
64
142
|
return { code: 'upload_failed', message: err.message, ...(err.status ? { status: err.status } : {}), details: { sha256: err.sha256, ...(err.code ? { storage_code: err.code } : {}) } };
|
|
65
143
|
if (err instanceof ShardfluxProtocolError)
|
|
66
|
-
return { code: 'protocol_error', message: err.message, status: err.status };
|
|
144
|
+
return { code: 'protocol_error', message: err.message, status: err.status, ...(context.executionId ? { execution_id: context.executionId } : {}) };
|
|
67
145
|
if (err instanceof TypeError && err.message === 'fetch failed') {
|
|
68
146
|
const cause = err.cause;
|
|
69
|
-
return {
|
|
147
|
+
return {
|
|
148
|
+
code: 'network_error',
|
|
149
|
+
message: `Cannot reach the Shardflux API or cell gateway (${cause?.code ?? cause?.message ?? 'network error'}).${context.executionId ? ` Execution ${context.executionId} may still be running server side; the next exec on this workspace waits for it.` : ''}`,
|
|
150
|
+
retryable: true,
|
|
151
|
+
...(context.executionId ? { execution_id: context.executionId } : {}),
|
|
152
|
+
};
|
|
70
153
|
}
|
|
71
154
|
return { code: 'internal_error', message: err instanceof Error ? err.message : String(err) };
|
|
72
155
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -4,10 +4,10 @@
|
|
|
4
4
|
* `shardflux-mcp` (bin.ts); `createShardfluxMcpServer()` builds the same server for any
|
|
5
5
|
* MCP transport.
|
|
6
6
|
*/
|
|
7
|
-
export { ALL_TOOL_PERMISSIONS, MCP_SERVER_VERSION, compactTiming, createShardfluxMcpServer, errorResult, okResult, sdkToolDefinitions, summarizeBuild, summarizeOperation, summarizeTemplate, summarizeWorkspace } from './server.js';
|
|
7
|
+
export { ALL_TOOL_PERMISSIONS, MCP_PACKAGE, MCP_SERVER_VERSION, checkServerVersion, compactTiming, createShardfluxMcpServer, errorResult, feedbackSuggestion, okResult, sdkToolDefinitions, summarizeBuild, summarizeOperation, summarizeTemplate, summarizeWorkspace } from './server.js';
|
|
8
8
|
export type { Logger, ObjectSchema, ServerOptions } from './server.js';
|
|
9
9
|
export { ConfigError, DEFAULT_TOOL_TIMEOUT_MS, DEFAULT_WAKE_TIMEOUT_MS, MAX_TOOL_TIMEOUT_MS, apiUrlFrom, loadConfig } from './config.js';
|
|
10
10
|
export type { McpConfig } from './config.js';
|
|
11
|
-
export { ToolError, describeToolError, redact } from './errors.js';
|
|
12
|
-
export type { ToolErrorInfo } from './errors.js';
|
|
11
|
+
export { ToolError, describeToolError, hostFeatureHint, modeHint, redact } from './errors.js';
|
|
12
|
+
export type { DescribeContext, ToolErrorInfo } from './errors.js';
|
|
13
13
|
export { main, stderrLogger } from './main.js';
|
package/dist/index.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* `shardflux-mcp` (bin.ts); `createShardfluxMcpServer()` builds the same server for any
|
|
5
5
|
* MCP transport.
|
|
6
6
|
*/
|
|
7
|
-
export { ALL_TOOL_PERMISSIONS, MCP_SERVER_VERSION, compactTiming, createShardfluxMcpServer, errorResult, okResult, sdkToolDefinitions, summarizeBuild, summarizeOperation, summarizeTemplate, summarizeWorkspace } from "./server.js";
|
|
7
|
+
export { ALL_TOOL_PERMISSIONS, MCP_PACKAGE, MCP_SERVER_VERSION, checkServerVersion, compactTiming, createShardfluxMcpServer, errorResult, feedbackSuggestion, okResult, sdkToolDefinitions, summarizeBuild, summarizeOperation, summarizeTemplate, summarizeWorkspace } from "./server.js";
|
|
8
8
|
export { ConfigError, DEFAULT_TOOL_TIMEOUT_MS, DEFAULT_WAKE_TIMEOUT_MS, MAX_TOOL_TIMEOUT_MS, apiUrlFrom, loadConfig } from "./config.js";
|
|
9
|
-
export { ToolError, describeToolError, redact } from "./errors.js";
|
|
9
|
+
export { ToolError, describeToolError, hostFeatureHint, modeHint, redact } from "./errors.js";
|
|
10
10
|
export { main, stderrLogger } from "./main.js";
|
package/dist/main.js
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
* Process wiring for the stdio MCP server: configuration from the
|
|
3
3
|
* environment, a stderr logger (stdout carries the protocol), the stdio
|
|
4
4
|
* transport, and shutdown when the client closes stdin or on SIGINT/SIGTERM.
|
|
5
|
+
* The server's startup version check (0.4.0) reads its opt-outs
|
|
6
|
+
* (SHARDFLUX_NO_UPDATE_CHECK, NO_UPDATE_NOTIFIER) from the same environment.
|
|
5
7
|
*/
|
|
6
8
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
7
9
|
import { ConfigError, loadConfig } from "./config.js";
|
|
@@ -51,6 +53,7 @@ export async function main(env = process.env) {
|
|
|
51
53
|
api_url: config.apiUrl,
|
|
52
54
|
pinned_workspace_key: config.workspaceKey ?? null,
|
|
53
55
|
default_template: config.template ?? null,
|
|
56
|
+
workspace_mode: config.workspaceMode ?? null,
|
|
54
57
|
agent_label: config.agentLabel,
|
|
55
58
|
tool_timeout_ms: config.toolTimeoutMs,
|
|
56
59
|
wake: config.wake,
|