@shardflux/mcp 0.5.1 → 0.5.2
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 +36 -18
- package/README.md +42 -49
- package/dist/errors.d.ts +4 -4
- package/dist/errors.js +17 -17
- package/dist/http.d.ts +3 -10
- package/dist/http.js +4 -10
- package/dist/server.d.ts +4 -4
- package/dist/server.js +22 -22
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,7 +1,27 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
Every tool, field and variable the README shows is available from the version named here.
|
|
4
|
-
|
|
3
|
+
Every tool, field and variable the README shows is available from the version named here. Breaking changes ship in
|
|
4
|
+
minor releases and are marked **Breaking**. Versions before 0.3.0 were not published.
|
|
5
|
+
|
|
6
|
+
## 0.5.2
|
|
7
|
+
|
|
8
|
+
Needs `@shardflux/sdk` 0.11.1 (the workspace version).
|
|
9
|
+
|
|
10
|
+
Wording: the server instructions, tool descriptions and error hints say what to do, without internals (no behaviour
|
|
11
|
+
change).
|
|
12
|
+
|
|
13
|
+
- `send_feedback` goes to the Shardflux team (`note`: `Delivered to the Shardflux team. Keep sending feedback as you
|
|
14
|
+
work.`); the instructions and description ask agents to also pass on what their user asks for (a capability, an
|
|
15
|
+
option or a smoother workflow) and their ideas. `error_code` shows `e.g. template_not_found`.
|
|
16
|
+
- The instructions: a queued start (open, resume, fork) has a deadline 15 minutes after it was created; past it, it
|
|
17
|
+
fails with code `operation_failed`, `details.error_code` `capacity_unavailable` and `retryable` true. Nothing was
|
|
18
|
+
started, so send it again. The `capacity_unavailable` message ends `The start passed its deadline and nothing was
|
|
19
|
+
started; send it again.`
|
|
20
|
+
- `exec` on a file-first workspace and the instructions say `an execution runs to completion` (before: "cannot be
|
|
21
|
+
canceled"); the timeout message says `(a file-first execution runs to completion)`.
|
|
22
|
+
- Hints: `no_execution_host`: `The execution cannot be placed right now and nothing ran; retry in a few seconds.`
|
|
23
|
+
`host_feature_unavailable`: `search_files is not available for this workspace. Search with exec instead, ...
|
|
24
|
+
Retrying does not help.` (the same for `edit_file` and other tools). `mode_not_available` drops "yet".
|
|
5
25
|
|
|
6
26
|
## 0.5.1
|
|
7
27
|
|
|
@@ -16,8 +36,7 @@ it. The result's `message` then says `the workspace is suspended as soon as it i
|
|
|
16
36
|
|
|
17
37
|
Needs `@shardflux/sdk` 0.11.0 (the workspace version).
|
|
18
38
|
|
|
19
|
-
A resume that restarted processes says so (cold boot,
|
|
20
|
-
suspend and no host can restore the memory snapshot, the cell resumes the workspace by booting its saved disk,
|
|
39
|
+
A resume that restarted processes says so (cold boot). After a platform runtime change, the cell resumes the workspace by booting its saved disk,
|
|
21
40
|
automatically, also when a workspace tool wakes it. Files are as of the suspend; every process was restarted.
|
|
22
41
|
|
|
23
42
|
- `timing.server` of results and error results adds `memory_restored` (when the API reports it: `false` for a cold
|
|
@@ -59,7 +78,7 @@ Opt-in overage with a spend cap (through `@shardflux/sdk` 0.10.0):
|
|
|
59
78
|
- A start refused with 402 `allowance_exhausted` comes back with `reason` `allowance_used`, `overage_paused` or
|
|
60
79
|
`spend_cap_reached` and `details.spend_cap` (errors already carried `details`).
|
|
61
80
|
|
|
62
|
-
Suspend when idle
|
|
81
|
+
Suspend when idle:
|
|
63
82
|
|
|
64
83
|
- `workspace_suspend` takes an optional `after_seconds` (30-3600): suspend when idle instead of now. The workspace is
|
|
65
84
|
suspended once it has been idle that long; the agent's next tool call on it cancels that, and a running command or a
|
|
@@ -69,11 +88,11 @@ Suspend when idle (contracts §20.6):
|
|
|
69
88
|
- Workspace summaries (`workspace_status`, `workspace_list`, `workspace_open`) carry `suspend_request`: the pending
|
|
70
89
|
request, or null.
|
|
71
90
|
|
|
72
|
-
## 0.4.0
|
|
91
|
+
## 0.4.0
|
|
73
92
|
|
|
74
93
|
Needs `@shardflux/sdk` 0.9.0 (the workspace version).
|
|
75
94
|
|
|
76
|
-
File-first workspaces
|
|
95
|
+
File-first workspaces:
|
|
77
96
|
|
|
78
97
|
- `workspace_open` takes `mode` (`processful` | `file_first`); `SHARDFLUX_WORKSPACE_MODE` sets the default. Workspace
|
|
79
98
|
results (`workspace_open`, `workspace_status`, `workspace_list`) carry `mode` and, for file-first workspaces,
|
|
@@ -92,7 +111,7 @@ File-first workspaces (contracts §29):
|
|
|
92
111
|
`current_tree_revision`), `outside_tree_root`, `execution_in_progress`, `execution_id_reused`, `no_execution_host`.
|
|
93
112
|
- The server instructions describe file-first workspaces.
|
|
94
113
|
|
|
95
|
-
Waking a suspended workspace is one request (
|
|
114
|
+
Waking a suspended workspace is one request (through `@shardflux/sdk` 0.9.0):
|
|
96
115
|
|
|
97
116
|
- A workspace tool on a suspended workspace wakes it with one held resume that returns the running workspace and this
|
|
98
117
|
server's tool token (its agent label), then runs the tool; the wake's `timing` is one `request(held)` phase. An API
|
|
@@ -100,7 +119,7 @@ Waking a suspended workspace is one request (contracts §22.6, through `@shardfl
|
|
|
100
119
|
- `workspace_resume` with `wait` goes through the server's workspace handle: one held request, after which the handle
|
|
101
120
|
holds the view and the token, so the next workspace tool starts without a token request.
|
|
102
121
|
|
|
103
|
-
File tools and the wake hint
|
|
122
|
+
File tools and the wake hint:
|
|
104
123
|
|
|
105
124
|
- `search_files` (read-only) and `edit_file` come from the SDK's `workspaceTools()` with its schemas, plus
|
|
106
125
|
`workspace_key` and `timeout_ms`, filtered by the `files` permission. The server instructions describe them.
|
|
@@ -108,19 +127,19 @@ File tools and the wake hint (contracts §26):
|
|
|
108
127
|
fire-and-forget; a suspended workspace starts resuming in the background, bounded like any wake by
|
|
109
128
|
`SHARDFLUX_WAKE_TIMEOUT_MS` and the call's deadline), except `read_file`, `list_files` and `search_files`.
|
|
110
129
|
- `read_file`, `list_files` and `search_files` of a suspended workspace whose disk a host still holds are answered
|
|
111
|
-
from that disk without resuming it
|
|
130
|
+
from that disk without resuming it, also as the first call after the suspend: the API now issues
|
|
112
131
|
tool tokens for suspended workspaces. Every other tool still resumes it. With an older API they resume it as before.
|
|
113
|
-
- A workspace
|
|
132
|
+
- A workspace without search and patches:
|
|
114
133
|
`search_files` and `edit_file` return `conflict`, `reason: host_feature_unavailable` (`details.feature`
|
|
115
134
|
`file_search` or `file_patch`), `retryable: false`, with a `hint` naming another way (`exec` with `grep`;
|
|
116
|
-
`read_file` then `write_file`).
|
|
135
|
+
`read_file` then `write_file`). `hostFeatureHint()` is
|
|
117
136
|
exported next to `modeHint()`.
|
|
118
137
|
- New refusals come back as error results with their `details.reason`: `revision_mismatch` (with
|
|
119
138
|
`current_revision`), `edit_not_found` / `edit_ambiguous` (with `index`), `host_capacity` (retryable). A read of a
|
|
120
139
|
sleeping workspace its disk cannot answer (`offline_unavailable`, `offline_budget`) wakes it like any
|
|
121
140
|
`workspace_not_running`; `offline_changed` (503) is retried.
|
|
122
141
|
|
|
123
|
-
Version check (
|
|
142
|
+
Version check (needs an API that serves `GET /v1/client-versions`):
|
|
124
143
|
|
|
125
144
|
- Version check at startup: after the configuration is validated, the server asks `GET /v1/client-versions` (through
|
|
126
145
|
its own fetch, the SDK's `checkClientVersion`: one request, 3 s timeout, never awaited by the protocol) whether
|
|
@@ -141,7 +160,7 @@ Version check (contracts §30.4; needs an API that serves `GET /v1/client-versio
|
|
|
141
160
|
authenticates with a project API key, which the API refuses for account actions (403 `forbidden` on the account
|
|
142
161
|
routes, 401 on the `/v1/auth` session routes); they need a person's session (`sfu_...`), which the CLI signs in.
|
|
143
162
|
|
|
144
|
-
### send_feedback: feedback straight to the
|
|
163
|
+
### send_feedback: feedback straight to the Shardflux team (needs an API with POST /v1/feedback)
|
|
145
164
|
|
|
146
165
|
- New management tool `send_feedback` (`message`, `category`: bug, confusing, missing, idea, praise or other; optional
|
|
147
166
|
`workspace`, `request_id`, `error_code`, `command`). It calls the SDK's `sendFeedback()` with `client:
|
|
@@ -149,8 +168,8 @@ Version check (contracts §30.4; needs an API that serves `GET /v1/client-versio
|
|
|
149
168
|
`SHARDFLUX_AGENT_LABEL`); a pinned server fills `workspace` with its key. Result: `{id, received_at, duplicate,
|
|
150
169
|
note}`. Always listed (any API key may send feedback).
|
|
151
170
|
- The server instructions and the tool description ask the agent to call it actively while it works: the moment a
|
|
152
|
-
call fails unexpectedly, an error or doc is confusing, something is missing
|
|
153
|
-
when its user
|
|
171
|
+
call fails unexpectedly, an error or doc is confusing, something is missing, or it needed a workaround, and
|
|
172
|
+
when its user asks for something new (paraphrased, without private
|
|
154
173
|
data; the agent tells its user it sent feedback).
|
|
155
174
|
- Failed calls carry a `feedback` field next to `error`, except the expected flow: `invalid_arguments`,
|
|
156
175
|
`workspace_pinned`, `unauthenticated`, `validation_failed`, `timeout`, and refusals whose error names the next step
|
|
@@ -198,8 +217,7 @@ Needs `@shardflux/sdk` 0.7.0 or later. New dependency: `yaml` (template.yaml).
|
|
|
198
217
|
|
|
199
218
|
Needs `@shardflux/sdk` 0.6.2 (the workspace version).
|
|
200
219
|
|
|
201
|
-
The API no longer lets a start (open, resume, fork) wait in `capacity_pending` forever: one
|
|
202
|
-
15 minutes after it began fails with `capacity_unavailable` (retryable; nothing was started, a suspended workspace
|
|
220
|
+
The API no longer lets a start (open, resume, fork) wait in `capacity_pending` forever: one still queued 15 minutes after it began fails with `capacity_unavailable` (retryable; nothing was started, a suspended workspace
|
|
203
221
|
stays suspended).
|
|
204
222
|
|
|
205
223
|
- Failed-operation errors (`code: operation_failed`) carry `retryable`, the operation error's own flag, and
|
package/README.md
CHANGED
|
@@ -14,8 +14,8 @@ with the scoped project API key you give it. Each tool call becomes one SDK requ
|
|
|
14
14
|
|
|
15
15
|
Documentation: <https://docs.shardflux.dev/reference/mcp>.
|
|
16
16
|
|
|
17
|
-
> **
|
|
18
|
-
>
|
|
17
|
+
> **Compatibility.** The API is versioned (`/v1`). Breaking changes ship only in minor releases and are marked
|
|
18
|
+
> **Breaking** in the changelog.
|
|
19
19
|
|
|
20
20
|
Requires Node.js 24 or later and a Shardflux project API key (`sfk_...`, from the Shardflux console).
|
|
21
21
|
|
|
@@ -174,7 +174,7 @@ environment `SHARDFLUX_API_KEY`.
|
|
|
174
174
|
| `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. |
|
|
175
175
|
| `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`. |
|
|
176
176
|
| `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. |
|
|
177
|
-
| `SHARDFLUX_HTTP_KEEPALIVE=1` | Reuse HTTP connections. By default every request uses a fresh connection (`Connection: close`)
|
|
177
|
+
| `SHARDFLUX_HTTP_KEEPALIVE=1` | Reuse HTTP connections. By default every request uses a fresh connection (`Connection: close`). |
|
|
178
178
|
| `SHARDFLUX_NO_UPDATE_CHECK` | (0.4.0) `1`, `true`, `yes` or `on` turns off the startup version check (see "Updates"). |
|
|
179
179
|
| `NO_UPDATE_NOTIFIER` | (0.4.0) The npm convention: any non-empty value also turns the check off. |
|
|
180
180
|
|
|
@@ -189,11 +189,9 @@ working directory: the directory your MCP client starts it in.
|
|
|
189
189
|
|
|
190
190
|
## Feedback: agents are asked to use `send_feedback` while they work
|
|
191
191
|
|
|
192
|
-
**(0.4.0+)** The `send_feedback` tool sends a message straight to the Shardflux
|
|
192
|
+
**(0.4.0+)** The `send_feedback` tool sends a message straight to the Shardflux team, who read every one. The
|
|
193
193
|
server instructions and the tool description ask the agent to call it actively during its work, not once at the end:
|
|
194
|
-
the moment a call fails unexpectedly, an error or doc is confusing, something is missing or
|
|
195
|
-
workaround; when its user complains or is frustrated about Shardflux or the workflow around it (paraphrased, with what
|
|
196
|
-
they were trying to do); and when its user asked for something Shardflux could not do, or made awkward. Short and
|
|
194
|
+
the moment a call fails unexpectedly, an error or doc is confusing, something is missing, or it needed a workaround; when its user asks for a capability, an option or a smoother workflow (paraphrased, with what they were trying to do); and when its user has an idea for how Shardflux could fit their work better. Short and
|
|
197
195
|
frequent beats polished, with the `request_id` and error code from the error. The agent tells its user it sent
|
|
198
196
|
feedback, and leaves out private data about the user and secrets or code they did not mean to share (paraphrase, never
|
|
199
197
|
transcripts). Failed calls carry a ready-made suggestion in a `feedback` field (see "Contract").
|
|
@@ -242,9 +240,7 @@ The management tools:
|
|
|
242
240
|
| `workspace_fork` | Fork into `new_key`. |
|
|
243
241
|
| `operation_wait` | Keep waiting for an operation. |
|
|
244
242
|
| `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. |
|
|
245
|
-
|
|
246
|
-
| `usage_summary` | The organization's usage summary for the current period. |
|
|
247
|
-
| `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). |
|
|
243
|
+
| `send_feedback` | (0.4.0) Feedback straight to the Shardflux team: `message`, `category`, and optional `workspace`, `request_id`, `error_code`, `command`. See [Feedback](#feedback-agents-are-asked-to-use-send_feedback-while-they-work). |
|
|
248
244
|
| `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. |
|
|
249
245
|
| `template_languages` | (0.3.0) The languages and versions a base (`<slug>@<version>`) offers `build.languages`. |
|
|
250
246
|
| `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. |
|
|
@@ -259,24 +255,23 @@ The SDK's workspace tools come from `workspaceTools()`:
|
|
|
259
255
|
- `browser_screenshot`, `browser_content`
|
|
260
256
|
|
|
261
257
|
They are published with **the SDK's JSON Schemas verbatim**, plus `workspace_key` and, where the
|
|
262
|
-
SDK schema has none, `timeout_ms`. They are filtered by the key's tool permissions
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
exact text and, without an `expected_revision`, reads the file's revision first so a concurrent change fails the
|
|
258
|
+
SDK schema has none, `timeout_ms`. They are filtered by the key's tool permissions when the server
|
|
259
|
+
can read them; otherwise it lists every workspace tool. `exec`'s `cwd` is an absolute path (default
|
|
260
|
+
`/home/user`): a relative one is an error result with `details.reason: invalid_cwd` whose message names the absolute
|
|
261
|
+
path it likely means, and a command that could not start (a `cwd` that is not a directory, a program not on `PATH`)
|
|
262
|
+
returns `exit_code: null` with `error: { code, message, reason: "exec_failed_to_start" }` naming the workspace's
|
|
263
|
+
reason (0.4.1+). `browser_screenshot` returns an MCP image content block. `search_files` is annotated read-only;
|
|
264
|
+
`edit_file` replaces exact text and, without an `expected_revision`, reads the file's revision first so a concurrent change fails the
|
|
269
265
|
edit (`conflict`, `details.reason: revision_mismatch`) instead of being overwritten.
|
|
270
266
|
|
|
271
|
-
Each workspace tool call first sends the wake hint (`POST /wake-hint`, without waiting for it): a workspace
|
|
272
|
-
|
|
273
|
-
`list_files` and `search_files` send none (0.4.0+): a suspended workspace
|
|
267
|
+
Each workspace tool call first sends the wake hint (`POST /wake-hint`, without waiting for it): a parked workspace
|
|
268
|
+
starts restoring while the call is prepared, and a suspended one starts resuming. `read_file`,
|
|
269
|
+
`list_files` and `search_files` send none (0.4.0+): a suspended workspace is read from its saved disk,
|
|
274
270
|
listed and searched there without resuming it (the state at suspension).
|
|
275
271
|
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
until the workspace runs on an upgraded host.
|
|
272
|
+
If search or patches are not available for a workspace, `search_files` and `edit_file` fail with `conflict`,
|
|
273
|
+
`reason: host_feature_unavailable` (`details.feature` `file_search` or `file_patch`), `retryable: false` and a `hint`
|
|
274
|
+
naming the fallback (`exec` with `grep`; `read_file` then `write_file`).
|
|
280
275
|
|
|
281
276
|
Deleting a workspace is deliberately not exposed to agents; use the
|
|
282
277
|
[CLI](https://www.npmjs.com/package/@shardflux/cli) or the console.
|
|
@@ -295,13 +290,13 @@ the state of the template's start commands and services (`failed` names the step
|
|
|
295
290
|
|
|
296
291
|
### File-first workspaces (0.4.0)
|
|
297
292
|
|
|
298
|
-
`workspace_open` with `mode: "file_first"` (or `SHARDFLUX_WORKSPACE_MODE=file_first`) opens a file-first workspace
|
|
299
|
-
|
|
293
|
+
`workspace_open` with `mode: "file_first"` (or `SHARDFLUX_WORKSPACE_MODE=file_first`) opens a file-first workspace:
|
|
294
|
+
a versioned file tree under /home/user with no VM between calls. It is ready at once and never
|
|
300
295
|
suspended. Workspace results carry `mode` and, for file-first workspaces, `tree_revision`.
|
|
301
296
|
|
|
302
297
|
- `exec` runs each command as an execution: a fresh VM on the workspace's files. Only files under /home/user persist
|
|
303
298
|
between calls. The result adds `execution_id`, `state`, `tree_revision` and `changed` (the paths the command added,
|
|
304
|
-
modified or deleted, up to 200, with `changed_truncated`). An execution
|
|
299
|
+
modified or deleted, up to 200, with `changed_truncated`). An execution runs to completion: when the call's
|
|
305
300
|
deadline passes first, the error carries its `execution_id` and the execution continues server side.
|
|
306
301
|
- The files tools work as on a processful workspace. Each change publishes the next tree revision.
|
|
307
302
|
- The process, terminal, git and browser tools and `workspace_suspend`, `workspace_resume`, `workspace_fork` and
|
|
@@ -311,17 +306,17 @@ suspended. Workspace results carry `mode` and, for file-first workspaces, `tree_
|
|
|
311
306
|
`notifications/tools/list_changed`. An unpinned server lists every tool; calling one the named workspace's mode
|
|
312
307
|
lacks returns an error with `reason: "not_supported_for_mode"` and a `hint`, before any request.
|
|
313
308
|
- Errors of file-first workspaces carry `reason` and a `hint`: `not_supported_for_mode`, `mode_mismatch` (a key's
|
|
314
|
-
mode never changes), `mode_not_available` (the
|
|
309
|
+
mode never changes), `mode_not_available` (the account does not have file-first workspaces), `layout_unsupported`
|
|
315
310
|
(a legacy template), `tree_revision_mismatch` (with `current_tree_revision`), `outside_tree_root`,
|
|
316
|
-
`execution_in_progress` (another execution holds the workspace), `execution_id_reused` and `no_execution_host`
|
|
317
|
-
|
|
311
|
+
`execution_in_progress` (another execution holds the workspace), `execution_id_reused` and `no_execution_host`
|
|
312
|
+
(the execution cannot be placed right now; retried with the same id; nothing ran).
|
|
318
313
|
|
|
319
314
|
```json
|
|
320
315
|
{
|
|
321
316
|
"mcpServers": {
|
|
322
317
|
"shardflux": {
|
|
323
|
-
"command": "
|
|
324
|
-
"args": ["/
|
|
318
|
+
"command": "npx",
|
|
319
|
+
"args": ["-y", "@shardflux/mcp"],
|
|
325
320
|
"env": { "SHARDFLUX_API_KEY": "sfk_...", "SHARDFLUX_WORKSPACE_KEY": "me/agent-files", "SHARDFLUX_TEMPLATE": "python-node-browser", "SHARDFLUX_WORKSPACE_MODE": "file_first" }
|
|
326
321
|
}
|
|
327
322
|
}
|
|
@@ -343,25 +338,23 @@ suspended. Workspace results carry `mode` and, for file-first workspaces, `tree_
|
|
|
343
338
|
`allowance_used` (upgrade, or turn on overage), `overage_paused` (a plan payment is past due) or
|
|
344
339
|
`spend_cap_reached` (raise the spend cap or upgrade), and `details.spend_cap`. An owner or billing member acts
|
|
345
340
|
on it in the console; retrying does not help.
|
|
346
|
-
|
|
347
341
|
- **(0.4.0+)** A failed call has a `feedback` field next to `error`: a one-sentence suggestion to call
|
|
348
342
|
`send_feedback` with category `bug`, the `request_id` and the error code. Not for `invalid_arguments`,
|
|
349
343
|
`workspace_pinned` or `unauthenticated`. A failed `send_feedback` carries a `hint` instead (when to retry, or the
|
|
350
344
|
email address).
|
|
351
|
-
- **
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
never retries it itself. The server instructions say the same.
|
|
345
|
+
- **Start deadlines (0.2.1+).** A queued open, resume or fork (`capacity_pending`) has a deadline 15 minutes after
|
|
346
|
+
it was created; a wait that ends first returns `code: timeout`, and its message names the deadline. A start still
|
|
347
|
+
pending then fails: `operation_failed`, `details.error_code: capacity_unavailable`, `retryable: true`, and a
|
|
348
|
+
message telling the agent that nothing was started and it can retry. The server instructions say the same.
|
|
356
349
|
- **Timing (0.2.0+).** A call that opens a workspace or waits says where the time went, from the SDK's lifecycle
|
|
357
350
|
timing, compact for model context:
|
|
358
351
|
|
|
359
352
|
```json
|
|
360
353
|
"timing": {
|
|
361
|
-
"action": "
|
|
362
|
-
"phases": ["request
|
|
363
|
-
"server": { "queued_ms":
|
|
364
|
-
"outside_server_ms":
|
|
354
|
+
"action": "resume", "outcome": "succeeded", "operation_id": "01a0ead6-bd61-737a-ae88-66e2d01a6e25", "total_ms": 413,
|
|
355
|
+
"phases": ["request 218 ms", "queued 195 ms"],
|
|
356
|
+
"server": { "queued_ms": 51, "run_ms": 290, "total_ms": 341, "resume_path": "local_cache" },
|
|
357
|
+
"outside_server_ms": 72, "retries": 0
|
|
365
358
|
}
|
|
366
359
|
```
|
|
367
360
|
|
|
@@ -369,7 +362,7 @@ suspended. Workspace results carry `mode` and, for file-first workspaces, `tree_
|
|
|
369
362
|
`workspace_fork` carry it with `wait: true`. Workspace tools carry it when they had to wake the workspace
|
|
370
363
|
(`action: "wake"`). Other results do not.
|
|
371
364
|
- Phases are in order, as `phase(reason) ms`. `request(held)` means the API held the open until the workspace was
|
|
372
|
-
ready. `capacity_pending(no_ready_host)` is time
|
|
365
|
+
ready. `capacity_pending(no_ready_host)` is time queued to start.
|
|
373
366
|
- `server` is the operation's own queued, run and total time, plus the start or resume path the cell reported.
|
|
374
367
|
A resume also has `memory_restored` (0.5.0+) when the API reports it, and `cold_boot_reason` when it is false.
|
|
375
368
|
- `outside_server_ms` is everything else: network, polling, reading the workspace, the tool token.
|
|
@@ -378,7 +371,7 @@ suspended. Workspace results carry `mode` and, for file-first workspaces, `tree_
|
|
|
378
371
|
with the first tool token, which the next tool call reuses.
|
|
379
372
|
- **Deadlines.** Every call has one: `timeout_ms`, clamped to the ceiling.
|
|
380
373
|
- Waits time out just before the deadline with `code: timeout` and the `operation_id`; the
|
|
381
|
-
operation continues server side (a start
|
|
374
|
+
operation continues server side (a queued start until its 15-minute deadline).
|
|
382
375
|
- Any request still in flight at the deadline is aborted.
|
|
383
376
|
- `exec`'s command timeout is capped at the deadline minus 2 s, so its output comes back.
|
|
384
377
|
- **Cancellation.** MCP `notifications/cancelled` aborts the SDK work behind the call: waits,
|
|
@@ -386,7 +379,7 @@ suspended. Workspace results carry `mode` and, for file-first workspaces, `tree_
|
|
|
386
379
|
the server stays up. The lifecycle operation itself is not canceled.
|
|
387
380
|
- **Workspace tools wake suspended workspaces, but do not open new ones.** A workspace that was
|
|
388
381
|
never opened is `not_found`: call `workspace_open` first. Reads (`read_file`, `list_files`,
|
|
389
|
-
`search_files`) of a suspended workspace
|
|
382
|
+
`search_files`) of a suspended workspace are answered from its saved disk
|
|
390
383
|
without waking it.
|
|
391
384
|
- A suspended workspace is resumed (or the resume or open already running is joined), and the
|
|
392
385
|
call runs once it is running. A call made during a suspend or resume waits for it to finish.
|
|
@@ -399,10 +392,10 @@ suspended. Workspace results carry `mode` and, for file-first workspaces, `tree_
|
|
|
399
392
|
suspended).
|
|
400
393
|
- With `SHARDFLUX_WAKE=off` the API's refusal comes back instead (`conflict`,
|
|
401
394
|
`details.reason: workspace_not_running`, or `workspace_not_running` from the cell gateway).
|
|
402
|
-
- **
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
395
|
+
- **Detecting a cold resume (0.5.0+).** A resume restores memory and running processes. After a platform runtime
|
|
396
|
+
update, a resume can boot from the saved disk instead of restoring memory (a cold boot), also when a workspace
|
|
397
|
+
tool wakes it; check `memory_restored`. The call still runs with the files as of the suspend, and processes start
|
|
398
|
+
fresh. The result (or the error result) then carries
|
|
406
399
|
`timing.server.resume_path: "cold_boot"`, `memory_restored: false`, `cold_boot_reason` (e.g. `runtime_changed`)
|
|
407
400
|
and a `notice` the agent reads: to start its dev servers, databases, watchers and background jobs again. The
|
|
408
401
|
server instructions and the `workspace_resume` description say so too.
|
package/dist/errors.d.ts
CHANGED
|
@@ -22,18 +22,18 @@ export interface ToolErrorInfo {
|
|
|
22
22
|
details?: Record<string, unknown>;
|
|
23
23
|
issues?: string[];
|
|
24
24
|
last_state?: string;
|
|
25
|
-
/** What to do instead (0.4.0): refusals that concern a workspace's mode
|
|
25
|
+
/** What to do instead (0.4.0): refusals that concern a workspace's mode or a tool that is not available for the workspace. */
|
|
26
26
|
hint?: string;
|
|
27
27
|
}
|
|
28
28
|
export declare function redact(text: string, key?: string): string;
|
|
29
29
|
/**
|
|
30
|
-
* What the model can do about a refusal that concerns a workspace's mode
|
|
30
|
+
* What the model can do about a refusal that concerns a workspace's mode, from its
|
|
31
31
|
* details.reason and details; undefined for every other refusal.
|
|
32
32
|
*/
|
|
33
33
|
export declare function modeHint(reason: string | undefined, details?: Record<string, unknown>): string | undefined;
|
|
34
34
|
/**
|
|
35
|
-
* 409 conflict host_feature_unavailable (
|
|
36
|
-
*
|
|
35
|
+
* 409 conflict host_feature_unavailable (0.4.0): the tool is not available for this workspace. Not retryable, so the
|
|
36
|
+
* hint names the fallback that works now.
|
|
37
37
|
*/
|
|
38
38
|
export declare function hostFeatureHint(reason: string | undefined, details?: Record<string, unknown>): string | undefined;
|
|
39
39
|
export interface DescribeContext {
|
package/dist/errors.js
CHANGED
|
@@ -3,8 +3,8 @@
|
|
|
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
5
|
* API and cell refusals add `reason` (their details.reason) and, for refusals
|
|
6
|
-
* that concern a workspace's mode (file-first workspaces
|
|
7
|
-
*
|
|
6
|
+
* that concern a workspace's mode (file-first workspaces) or a
|
|
7
|
+
* tool that is not available for the workspace (host_feature_unavailable), a
|
|
8
8
|
* `hint` saying what to do instead.
|
|
9
9
|
*/
|
|
10
10
|
import { OperationFailedError, OperationTimeoutError, ShardfluxApiError, ShardfluxProtocolError, TemplateUploadError, ToolArgumentError, TreeRevisionMismatchError } from '@shardflux/sdk';
|
|
@@ -31,7 +31,7 @@ const str = (v) => (typeof v === 'string' && v !== '' ? v : undefined);
|
|
|
31
31
|
/** The tools a file-first workspace has, for hints. */
|
|
32
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
33
|
/**
|
|
34
|
-
* What the model can do about a refusal that concerns a workspace's mode
|
|
34
|
+
* What the model can do about a refusal that concerns a workspace's mode, from its
|
|
35
35
|
* details.reason and details; undefined for every other refusal.
|
|
36
36
|
*/
|
|
37
37
|
export function modeHint(reason, details = {}) {
|
|
@@ -52,7 +52,7 @@ export function modeHint(reason, details = {}) {
|
|
|
52
52
|
: "A workspace's mode never changes: open this key with the mode it was created with, or use another key.";
|
|
53
53
|
}
|
|
54
54
|
case 'mode_not_available':
|
|
55
|
-
return 'This deployment does not offer file-first workspaces
|
|
55
|
+
return 'This deployment does not offer file-first workspaces: open the workspace with mode "processful" (or without mode).';
|
|
56
56
|
case 'layout_unsupported':
|
|
57
57
|
return mode === 'file_first'
|
|
58
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".'
|
|
@@ -66,26 +66,26 @@ export function modeHint(reason, details = {}) {
|
|
|
66
66
|
case 'execution_id_reused':
|
|
67
67
|
return 'That execution id was already used for a different command; call exec again (each call uses a new execution id).';
|
|
68
68
|
case 'no_execution_host':
|
|
69
|
-
return '
|
|
69
|
+
return 'The execution cannot be placed right now and nothing ran; retry in a few seconds.';
|
|
70
70
|
default:
|
|
71
71
|
return undefined;
|
|
72
72
|
}
|
|
73
73
|
}
|
|
74
74
|
/**
|
|
75
|
-
* 409 conflict host_feature_unavailable (
|
|
76
|
-
*
|
|
75
|
+
* 409 conflict host_feature_unavailable (0.4.0): the tool is not available for this workspace. Not retryable, so the
|
|
76
|
+
* hint names the fallback that works now.
|
|
77
77
|
*/
|
|
78
78
|
export function hostFeatureHint(reason, details = {}) {
|
|
79
79
|
if (reason !== 'host_feature_unavailable')
|
|
80
80
|
return undefined;
|
|
81
|
-
const
|
|
81
|
+
const retry = 'Retrying does not help.';
|
|
82
82
|
switch (details.feature) {
|
|
83
83
|
case 'file_search':
|
|
84
|
-
return `
|
|
84
|
+
return `search_files is not available for this workspace. Search with exec instead, e.g. grep -rn -- PATTERN PATH (add -i to ignore case, -E for a regular expression). ${retry}`;
|
|
85
85
|
case 'file_patch':
|
|
86
|
-
return `
|
|
86
|
+
return `edit_file is not available for this workspace. Use read_file, change the text, then write_file with the whole new content. ${retry}`;
|
|
87
87
|
default:
|
|
88
|
-
return `This
|
|
88
|
+
return `This tool is not available for this workspace. ${retry}`;
|
|
89
89
|
}
|
|
90
90
|
}
|
|
91
91
|
export function describeToolError(err, context = {}) {
|
|
@@ -101,7 +101,7 @@ export function describeToolError(err, context = {}) {
|
|
|
101
101
|
retryable: err.retryable,
|
|
102
102
|
source: err.source,
|
|
103
103
|
...(err.operationId ? { operation_id: err.operationId } : {}),
|
|
104
|
-
// No execution_id: a refusal answers before anything ran, and the execution id stays unused
|
|
104
|
+
// No execution_id: a refusal answers before anything ran, and the execution id stays unused.
|
|
105
105
|
...(err instanceof TreeRevisionMismatchError && err.currentTreeRevision !== null ? { current_tree_revision: err.currentTreeRevision } : {}),
|
|
106
106
|
...(err.details ? { details: err.details } : {}),
|
|
107
107
|
...(hint ? { hint } : {}),
|
|
@@ -115,18 +115,18 @@ export function describeToolError(err, context = {}) {
|
|
|
115
115
|
return { code: 'timeout', message: `${err.message} Call operation_wait with this operation_id to keep waiting.`, operation_id: err.operationId, last_state: err.lastState, retryable: true };
|
|
116
116
|
}
|
|
117
117
|
if (err instanceof OperationFailedError) {
|
|
118
|
-
// retryable: the operation error's own flag. capacity_unavailable (
|
|
119
|
-
//
|
|
118
|
+
// retryable: the operation error's own flag. capacity_unavailable (the start passed its deadline) is retryable:
|
|
119
|
+
// nothing was started, so the agent may send it again.
|
|
120
120
|
const opDetails = err.operation.error?.details;
|
|
121
121
|
const reason = typeof opDetails === 'object' && opDetails !== null ? opDetails.reason : undefined;
|
|
122
122
|
const details = { ...(err.errorCode ? { error_code: err.errorCode } : {}), ...(typeof reason === 'string' ? { reason } : {}) };
|
|
123
|
-
const hint = err.errorCode === 'capacity_unavailable' ? '.
|
|
123
|
+
const hint = err.errorCode === 'capacity_unavailable' ? '. The start passed its deadline and nothing was started; send it again.' : '';
|
|
124
124
|
return { code: 'operation_failed', message: `${err.message}${hint}`, operation_id: err.operationId, retryable: err.retryable, ...(Object.keys(details).length ? { details } : {}) };
|
|
125
125
|
}
|
|
126
126
|
if (named(err, 'TimeoutError')) {
|
|
127
|
-
// A file-first execution
|
|
127
|
+
// A file-first execution runs to completion server side and then publishes what it changed.
|
|
128
128
|
const continues = context.executionId
|
|
129
|
-
? `; execution ${context.executionId} continues server side (a file-first execution
|
|
129
|
+
? `; execution ${context.executionId} continues server side (a file-first execution runs to completion): the files it changes are published when it ends, and the next exec or file change on this workspace waits for it`
|
|
130
130
|
: context.operationId
|
|
131
131
|
? '; the operation continues server side (use operation_wait)'
|
|
132
132
|
: '';
|
package/dist/http.d.ts
CHANGED
|
@@ -1,13 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The fetch given to the SDK: the runtime's fetch
|
|
3
|
-
* every request
|
|
4
|
-
*
|
|
5
|
-
* Why: Node 26.7's bundled undici (8.9.0) intermittently stalls a request
|
|
6
|
-
* sent on a reused keep-alive connection until an unrelated timer fires (up to
|
|
7
|
-
* ~30 s, the SDK's request timeout, after which the SDK retries). Reproduced
|
|
8
|
-
* with bare fetch against both node:http and Fastify servers; Node 22 (undici
|
|
9
|
-
* 6.28) and `Connection: close` do not stall. A fresh connection per request
|
|
10
|
-
* costs one TCP/TLS handshake, which is negligible for these call rates.
|
|
11
|
-
* Set SHARDFLUX_HTTP_KEEPALIVE=1 to use the runtime's keep-alive pooling.
|
|
2
|
+
* The fetch given to the SDK: the runtime's fetch (API and cell gateway). By
|
|
3
|
+
* default every request uses a fresh connection (`Connection: close`);
|
|
4
|
+
* SHARDFLUX_HTTP_KEEPALIVE=1 reuses connections.
|
|
12
5
|
*/
|
|
13
6
|
export declare function makeFetch(env: Record<string, string | undefined>, base?: typeof fetch): typeof fetch;
|
package/dist/http.js
CHANGED
|
@@ -1,16 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The fetch given to the SDK: the runtime's fetch
|
|
3
|
-
* every request
|
|
4
|
-
*
|
|
5
|
-
* Why: Node 26.7's bundled undici (8.9.0) intermittently stalls a request
|
|
6
|
-
* sent on a reused keep-alive connection until an unrelated timer fires (up to
|
|
7
|
-
* ~30 s, the SDK's request timeout, after which the SDK retries). Reproduced
|
|
8
|
-
* with bare fetch against both node:http and Fastify servers; Node 22 (undici
|
|
9
|
-
* 6.28) and `Connection: close` do not stall. A fresh connection per request
|
|
10
|
-
* costs one TCP/TLS handshake, which is negligible for these call rates.
|
|
11
|
-
* Set SHARDFLUX_HTTP_KEEPALIVE=1 to use the runtime's keep-alive pooling.
|
|
2
|
+
* The fetch given to the SDK: the runtime's fetch (API and cell gateway). By
|
|
3
|
+
* default every request uses a fresh connection (`Connection: close`);
|
|
4
|
+
* SHARDFLUX_HTTP_KEEPALIVE=1 reuses connections.
|
|
12
5
|
*/
|
|
13
6
|
export function makeFetch(env, base = fetch) {
|
|
7
|
+
// Why a fresh connection by default (Node 26 keep-alive measurements): docs/progress/startup-latency.md.
|
|
14
8
|
if (env.SHARDFLUX_HTTP_KEEPALIVE === '1')
|
|
15
9
|
return base;
|
|
16
10
|
return (input, init) => {
|
package/dist/server.d.ts
CHANGED
|
@@ -3,8 +3,8 @@ import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';
|
|
|
3
3
|
import type { ClientVersionStatus, JsonSchema, LifecycleTiming, Operation, TemplateBuild, TemplateDetail, TemplateOwner, ToolName, WorkspaceMode, WorkspaceTool, WorkspaceView } from '@shardflux/sdk';
|
|
4
4
|
import type { McpConfig } from './config.js';
|
|
5
5
|
import type { ToolErrorInfo } from './errors.js';
|
|
6
|
-
export declare const MCP_SERVER_VERSION = "0.5.
|
|
7
|
-
/** The package this server is distributed as: its entry in GET /v1/client-versions
|
|
6
|
+
export declare const MCP_SERVER_VERSION = "0.5.2";
|
|
7
|
+
/** The package this server is distributed as: its entry in GET /v1/client-versions. */
|
|
8
8
|
export declare const MCP_PACKAGE = "@shardflux/mcp";
|
|
9
9
|
export declare const ALL_TOOL_PERMISSIONS: readonly ToolName[];
|
|
10
10
|
export type ObjectSchema = JsonSchema & {
|
|
@@ -185,7 +185,7 @@ export declare function summarizeOperation(o: Operation): {
|
|
|
185
185
|
/**
|
|
186
186
|
* The SDK's LifecycleTiming, compact enough for model context: where an open, a wait or a wake spent its time. Phases
|
|
187
187
|
* are `phase(reason) ms` in order (`request(held)`: the server held the open; `capacity_pending(no_ready_host)`:
|
|
188
|
-
*
|
|
188
|
+
* queued); `server` is the operation's own queued/run/total time; `outside_server_ms` is the rest
|
|
189
189
|
* (network, polling, view and token).
|
|
190
190
|
*/
|
|
191
191
|
export declare function compactTiming(t: LifecycleTiming): {
|
|
@@ -239,7 +239,7 @@ export interface ServerOptions {
|
|
|
239
239
|
versionCheck?: boolean;
|
|
240
240
|
}
|
|
241
241
|
/**
|
|
242
|
-
* The startup version check (0.4.0
|
|
242
|
+
* The startup version check (0.4.0): one GET <api>/v1/client-versions through the server's fetch
|
|
243
243
|
* (the SDK's checkClientVersion: 3 s timeout, never throws) for @shardflux/mcp at MCP_SERVER_VERSION. Outdated or
|
|
244
244
|
* unsupported: one `warn` line whose message is the notice (`@shardflux/mcp 0.4.0 is outdated: 0.5.0 is available.
|
|
245
245
|
* Update: <upgrade command>`) plus the status fields. Anything else (current; unknown: not listed, `latest` null while
|
package/dist/server.js
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* - templates (0.3.0): template_get (versions, settings, a version's recipe), template_languages and
|
|
9
9
|
* template_build (a recipe v2 object or a template.yaml path inside the
|
|
10
10
|
* server's working directory; local `from` paths are uploaded by the SDK);
|
|
11
|
-
* - send_feedback (0.4.0): feedback straight to the Shardflux
|
|
11
|
+
* - send_feedback (0.4.0): feedback straight to the Shardflux team; the instructions, its description and
|
|
12
12
|
* the `feedback` field of error results ask agents to use it while they work;
|
|
13
13
|
* - the SDK's workspace tools (`workspaceTools()`: exec, files, processes,
|
|
14
14
|
* terminal, git, browser), published with the SDK's own JSON Schemas plus
|
|
@@ -21,14 +21,14 @@
|
|
|
21
21
|
* call first sends the wake hint (the SDK tool runner's `workspace.hint()`,
|
|
22
22
|
* fire-and-forget) so a parked workspace restores while the call is prepared,
|
|
23
23
|
* except read_file, list_files and search_files, which a sleeping workspace
|
|
24
|
-
* answers from its disk without waking
|
|
24
|
+
* answers from its disk without waking. Each call has a deadline
|
|
25
25
|
* (`timeout_ms`, clamped to SHARDFLUX_MCP_TOOL_TIMEOUT_MS) and MCP cancellation
|
|
26
26
|
* (notifications/cancelled -> `extra.signal`) aborts the underlying SDK
|
|
27
27
|
* request or wait. Failures come back as `isError` tool results carrying the
|
|
28
28
|
* API error code. stdout is the protocol; logs go to stderr. Opens, waits and
|
|
29
29
|
* wakes carry the SDK's lifecycle timing, compacted (`compactTiming`).
|
|
30
30
|
*
|
|
31
|
-
* File-first workspaces (0.4.0
|
|
31
|
+
* File-first workspaces (0.4.0: `workspace_open` `mode:
|
|
32
32
|
* "file_first"`) have files and executions only. A pinned server lists the
|
|
33
33
|
* tools of its workspace's mode (the key's mode once it resolves, else
|
|
34
34
|
* SHARDFLUX_WORKSPACE_MODE, else processful) and sends
|
|
@@ -53,8 +53,8 @@ import { parse as parseYaml } from 'yaml';
|
|
|
53
53
|
import { DEFAULT_WAKE_TIMEOUT_MS } from "./config.js";
|
|
54
54
|
import { ToolError, describeToolError, redact } from "./errors.js";
|
|
55
55
|
import { makeFetch } from "./http.js";
|
|
56
|
-
export const MCP_SERVER_VERSION = '0.5.
|
|
57
|
-
/** The package this server is distributed as: its entry in GET /v1/client-versions
|
|
56
|
+
export const MCP_SERVER_VERSION = '0.5.2';
|
|
57
|
+
/** The package this server is distributed as: its entry in GET /v1/client-versions. */
|
|
58
58
|
export const MCP_PACKAGE = '@shardflux/mcp';
|
|
59
59
|
const USER_AGENT = `shardflux-mcp/${MCP_SERVER_VERSION} shardflux-sdk-ts/${SDK_VERSION}`;
|
|
60
60
|
export const ALL_TOOL_PERMISSIONS = ['exec', 'files', 'pty', 'process', 'git', 'browser'];
|
|
@@ -202,7 +202,7 @@ function isObject(v) {
|
|
|
202
202
|
/**
|
|
203
203
|
* The SDK's LifecycleTiming, compact enough for model context: where an open, a wait or a wake spent its time. Phases
|
|
204
204
|
* are `phase(reason) ms` in order (`request(held)`: the server held the open; `capacity_pending(no_ready_host)`:
|
|
205
|
-
*
|
|
205
|
+
* queued); `server` is the operation's own queued/run/total time; `outside_server_ms` is the rest
|
|
206
206
|
* (network, polling, view and token).
|
|
207
207
|
*/
|
|
208
208
|
export function compactTiming(t) {
|
|
@@ -268,7 +268,7 @@ const FEEDBACK_EMAIL = 'shardflux@heliosone.fi';
|
|
|
268
268
|
* When to use send_feedback, for the agent: the same words as `shard feedback --help`, in the server instructions and
|
|
269
269
|
* the tool description. It has to make an agent actually call it while it works.
|
|
270
270
|
*/
|
|
271
|
-
const FEEDBACK_USE = 'Call it actively while you work, not once at the end. Send a short note the moment: something fails unexpectedly, an error or doc is unclear, an option is missing, or you needed a workaround; your user
|
|
271
|
+
const FEEDBACK_USE = 'Call it actively while you work, not once at the end. Send a short note the moment: something fails unexpectedly, an error or doc is unclear, an option is missing, or you needed a workaround; your user asks for a capability, an option or a smoother workflow: paraphrase it, with what they were trying to do; your user has an idea for how Shardflux could fit their work better. Short and frequent beats polished; include the request_id and error code (failed calls carry a ready-made "feedback" field). No need to ask first, but tell your user you sent it. Leave out private data about the user and secrets or code they did not mean to share; paraphrase, never paste transcripts.';
|
|
272
272
|
/**
|
|
273
273
|
* No send_feedback suggestion for the expected flow: argument, configuration or credential problems of the call itself,
|
|
274
274
|
* a wait that gave up while the work continues, and refusals whose error names the exact next step (the same list as
|
|
@@ -302,18 +302,18 @@ const INSTRUCTIONS = [
|
|
|
302
302
|
'Call workspace_open first (it creates the workspace on first use and reconnects afterwards, never resetting it), then use exec, read_file, write_file and the other workspace tools with the same workspace_key.',
|
|
303
303
|
'Lifecycle calls return operations; operation_wait keeps waiting. Workspace tools resume a suspended workspace on use (read_file, list_files and search_files read its disk without resuming it when they can); if that takes too long the error (code timeout) names the operation to pass to operation_wait. Errors are tool results with error.code from the Shardflux API.',
|
|
304
304
|
'When you finish your work on a workspace, call workspace_suspend with after_seconds (e.g. 60): it is suspended once idle that long, so it stops using RAM; your next tool call on it cancels that.',
|
|
305
|
-
'A start (open, resume, fork)
|
|
305
|
+
'A queued start (open, resume, fork) has a deadline 15 minutes after it was created; past it, it fails with code operation_failed, details.error_code capacity_unavailable and retryable true. Nothing was started, so send it again. retryable false means retrying will not help.',
|
|
306
306
|
'Opens, waits and wakes add timing (phases, server queued/run time) saying where the time went.',
|
|
307
307
|
'A resume (also the automatic one when a tool call finds the workspace suspended) normally restores memory and running processes. If a result carries notice and timing.server.memory_restored false (resume_path cold_boot), the workspace booted from its saved disk instead: files are kept, but every process was restarted, so start your dev servers and background jobs again.',
|
|
308
308
|
'Templates: template_get shows a template’s versions and settings (the inputs workspace_open takes); template_languages lists what a base offers build.languages; template_build builds a new version from a recipe v2 (unpublished unless publish is true).',
|
|
309
309
|
'search_files finds text in files under a directory; edit_file replaces exact text in a file (each old_text must occur once) and returns the new revision, which you can pass as expected_revision to the next edit_file of that file so a change made by someone else is detected.',
|
|
310
310
|
'File-first workspaces (workspace_open mode "file_first"; results show mode and tree_revision) keep only files: there is no VM between calls, each exec runs in a fresh VM, and only files under /home/user persist between exec calls (install dependencies there, e.g. a virtualenv, and start servers within the command that needs them).',
|
|
311
|
-
'An exec result on a file-first workspace carries the paths it changed (changed) and the new tree_revision; an execution
|
|
311
|
+
'An exec result on a file-first workspace carries the paths it changed (changed) and the new tree_revision; an execution runs to completion. Process, terminal, git and browser tools and workspace_suspend, workspace_resume and workspace_fork do not apply to them (error reason not_supported_for_mode).',
|
|
312
312
|
'Account-level actions are not tools of this server: it works inside one project with a project API key, which the API refuses for them. Registering, signing in, organizations, projects, API keys, members, billing and plan upgrades, spend alerts and audit export are done with the shard CLI: `npx @shardflux/cli@latest --help` (for example `shard auth login`, `shard setup`, `shard billing upgrade <plan>`). A person still opens the verification email and pays on the Checkout page the CLI prints.',
|
|
313
|
-
`Feedback: send_feedback goes straight to the Shardflux
|
|
313
|
+
`Feedback: send_feedback goes straight to the Shardflux team, who read every message. ${FEEDBACK_USE}`,
|
|
314
314
|
].join(' ');
|
|
315
315
|
/**
|
|
316
|
-
* The startup version check (0.4.0
|
|
316
|
+
* The startup version check (0.4.0): one GET <api>/v1/client-versions through the server's fetch
|
|
317
317
|
* (the SDK's checkClientVersion: 3 s timeout, never throws) for @shardflux/mcp at MCP_SERVER_VERSION. Outdated or
|
|
318
318
|
* unsupported: one `warn` line whose message is the notice (`@shardflux/mcp 0.4.0 is outdated: 0.5.0 is available.
|
|
319
319
|
* Update: <upgrade command>`) plus the status fields. Anything else (current; unknown: not listed, `latest` null while
|
|
@@ -421,7 +421,7 @@ export function createShardfluxMcpServer(config, opts = {}) {
|
|
|
421
421
|
* Wake on use for a workspace's tools: `workspace.wake()` within the SDK's transition budget (SHARDFLUX_WAKE_TIMEOUT_MS)
|
|
422
422
|
* and 250 ms short of the call's deadline, so a wake that runs out reports the operation (OperationTimeoutError) rather
|
|
423
423
|
* than a bare deadline. null when SHARDFLUX_WAKE=off: the refusal (workspace_not_running) comes back instead. The
|
|
424
|
-
* wake's held resume
|
|
424
|
+
* wake's held resume returns the token of this server's tools (its agent label), so the woken call
|
|
425
425
|
* runs at once with it.
|
|
426
426
|
*/
|
|
427
427
|
const wakeFor = (ws) => config.wake === false
|
|
@@ -480,7 +480,7 @@ export function createShardfluxMcpServer(config, opts = {}) {
|
|
|
480
480
|
/**
|
|
481
481
|
* The workspace a key names, across every lifetime and purpose (sessions, drafts and test instances are hidden from
|
|
482
482
|
* the default list), preferring the live workspace over tombstones: an ended session leaves a tombstone with the same
|
|
483
|
-
* key, and the key then opens a new workspace
|
|
483
|
+
* key, and the key then opens a new workspace. Null when the key names none.
|
|
484
484
|
*/
|
|
485
485
|
const lookup = async (key, opts2) => {
|
|
486
486
|
const cached = handles.get(key);
|
|
@@ -544,7 +544,7 @@ export function createShardfluxMcpServer(config, opts = {}) {
|
|
|
544
544
|
{
|
|
545
545
|
name: 'workspace_open',
|
|
546
546
|
title: 'Open workspace',
|
|
547
|
-
description: 'Open a persistent workspace by key: creates it from the template on first use, reconnects (or resumes) it afterwards, never resets it. Waits until it is ready unless wait is false; on timeout the start continues server side (operation_wait)
|
|
547
|
+
description: 'Open a persistent workspace by key: creates it from the template on first use, reconnects (or resumes) it afterwards, never resets it. Waits until it is ready unless wait is false; on timeout the start continues server side (operation_wait); a queued start has a deadline 15 minutes after it was created. The result’s timing says where the time went. mode "file_first" opens a file-first workspace (files only, ready at once; see mode).',
|
|
548
548
|
inputSchema: obj({
|
|
549
549
|
workspace_key: workspaceKeyProp(pinned),
|
|
550
550
|
template: {
|
|
@@ -687,7 +687,7 @@ export function createShardfluxMcpServer(config, opts = {}) {
|
|
|
687
687
|
},
|
|
688
688
|
},
|
|
689
689
|
lifecycleTool('workspace_resume', 'Resume workspace', 'Resume a suspended workspace. Returns the resume operation (with wait: once finished, and its timing). A notice in the result (timing.server.memory_restored false) means it booted from its saved disk: files kept, processes restarted. Not for file-first workspaces (never suspended).', 'resume',
|
|
690
|
-
// Through the server's handle: a waited resume is held until the workspace runs
|
|
690
|
+
// Through the server's handle: a waited resume is held until the workspace runs and the handle
|
|
691
691
|
// keeps the view and this server's tool token, so the next workspace tool starts at once.
|
|
692
692
|
(ws, o) => ws.resume({ ...o, agentLabel: config.agentLabel })),
|
|
693
693
|
{
|
|
@@ -838,13 +838,13 @@ export function createShardfluxMcpServer(config, opts = {}) {
|
|
|
838
838
|
management.push({
|
|
839
839
|
name: 'send_feedback',
|
|
840
840
|
title: 'Send feedback',
|
|
841
|
-
description: `Send feedback straight to the Shardflux
|
|
841
|
+
description: `Send feedback straight to the Shardflux team, who read every message. ${FEEDBACK_USE} Categories: bug (something failed or behaved wrongly), confusing (an error, doc, name or output was unclear or misleading), missing (a capability, option or template you needed does not exist), idea, praise (something worked well), other. Rate limited per API key (rate_limited with details.retry_after_seconds); the same message within 24 hours is recorded once (duplicate: true).`,
|
|
842
842
|
inputSchema: obj({
|
|
843
843
|
message: { type: 'string', minLength: 1, maxLength: FEEDBACK_MESSAGE_MAX_LENGTH, description: 'What you did, what happened and what you expected (1-8000 characters). Leave secrets out.' },
|
|
844
844
|
category: { type: 'string', enum: [...FEEDBACK_CATEGORIES], description: 'bug, confusing, missing, idea, praise or other.' },
|
|
845
845
|
workspace: { type: 'string', minLength: 1, maxLength: 200, description: pinned ? `Workspace key or id it is about (default "${pinned}").` : 'Workspace key or id it is about.' },
|
|
846
|
-
request_id: { type: 'string', minLength: 1, maxLength: 200, description: 'request_id from the error, so the
|
|
847
|
-
error_code: { type: 'string', minLength: 1, maxLength: 100, description: 'The error code seen (error.code, or details.error_code of a failed operation), e.g.
|
|
846
|
+
request_id: { type: 'string', minLength: 1, maxLength: 200, description: 'request_id from the error, so the team can find the logs.' },
|
|
847
|
+
error_code: { type: 'string', minLength: 1, maxLength: 100, description: 'The error code seen (error.code, or details.error_code of a failed operation), e.g. template_not_found.' },
|
|
848
848
|
command: { type: 'string', minLength: 1, maxLength: 2000, description: 'The tool call (name and arguments) or command that led to it.' },
|
|
849
849
|
}, ['message', 'category']),
|
|
850
850
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
@@ -870,7 +870,7 @@ export function createShardfluxMcpServer(config, opts = {}) {
|
|
|
870
870
|
id: r.id,
|
|
871
871
|
received_at: r.receivedAt,
|
|
872
872
|
duplicate: r.duplicate,
|
|
873
|
-
note: r.duplicate ? 'The same message was already received in the last 24 hours; it was not emailed again.' : 'Delivered to the Shardflux
|
|
873
|
+
note: r.duplicate ? 'The same message was already received in the last 24 hours; it was not emailed again.' : 'Delivered to the Shardflux team. Keep sending feedback as you work.',
|
|
874
874
|
};
|
|
875
875
|
},
|
|
876
876
|
});
|
|
@@ -883,7 +883,7 @@ export function createShardfluxMcpServer(config, opts = {}) {
|
|
|
883
883
|
*/
|
|
884
884
|
const execDescription = (sdkDescription, listing) => {
|
|
885
885
|
if (listing === 'file_first')
|
|
886
|
-
return `${sdkDescription}${execDeadline} An execution
|
|
886
|
+
return `${sdkDescription}${execDeadline} An execution runs to completion: if the deadline passes first it continues server side (the error names its execution_id) and the next exec waits for it.`;
|
|
887
887
|
if (listing === 'processful')
|
|
888
888
|
return `${sdkDescription}${execDeadline}`;
|
|
889
889
|
return `${sdkDescription} On a file-first workspace (workspace_open mode "file_first") each call instead runs in a fresh VM on the workspace’s files: only files under /home/user persist between calls, and the result adds execution_id, state, tree_revision and changed (the paths the command added, modified or deleted).${execDeadline}`;
|
|
@@ -921,7 +921,7 @@ export function createShardfluxMcpServer(config, opts = {}) {
|
|
|
921
921
|
}
|
|
922
922
|
const ws = await resolve(key, { signal: call.signal });
|
|
923
923
|
// The SDK's tools for the workspace's mode: a file-first workspace has exec (as executions) and the file tools.
|
|
924
|
-
// A file-first execution
|
|
924
|
+
// A file-first execution runs to completion; its id is kept so a deadline error can name it.
|
|
925
925
|
const tool = workspaceTools(ws, {
|
|
926
926
|
agentLabel: config.agentLabel,
|
|
927
927
|
tools: [...ALL_TOOL_PERMISSIONS],
|
|
@@ -1045,7 +1045,7 @@ export function createShardfluxMcpServer(config, opts = {}) {
|
|
|
1045
1045
|
...(info.request_id ? { request_id: info.request_id } : {}),
|
|
1046
1046
|
...(info.execution_id ? { execution_id: info.execution_id } : {}),
|
|
1047
1047
|
});
|
|
1048
|
-
// A failure suggests send_feedback; a failed send_feedback says how to still reach the
|
|
1048
|
+
// A failure suggests send_feedback; a failed send_feedback says how to still reach the Shardflux team.
|
|
1049
1049
|
const s = info.details?.retry_after_seconds;
|
|
1050
1050
|
const siblings = name === 'send_feedback'
|
|
1051
1051
|
? info.code === 'invalid_arguments'
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shardflux/mcp",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.2",
|
|
4
4
|
"mcpName": "dev.shardflux/mcp",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "Shardflux MCP server (stdio): give Claude, Cursor, Codex or any MCP client persistent cloud workspaces to run commands, edit files, use git and a browser in. Uses your scoped Shardflux API key.",
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
"@modelcontextprotocol/sdk": "1.30.0",
|
|
47
47
|
"yaml": "2.9.1",
|
|
48
48
|
"zod": "4.6.5",
|
|
49
|
-
"@shardflux/sdk": "^0.11.
|
|
49
|
+
"@shardflux/sdk": "^0.11.1"
|
|
50
50
|
},
|
|
51
51
|
"devDependencies": {
|
|
52
52
|
"@eslint/js": "10.0.1",
|