@shardflux/mcp 0.4.0 → 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 +29 -0
- package/README.md +13 -2
- package/dist/server.d.ts +7 -1
- package/dist/server.js +53 -5
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,35 @@
|
|
|
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
|
+
|
|
6
35
|
## 0.4.0 (not yet published; npm `latest` is 0.3.1)
|
|
7
36
|
|
|
8
37
|
Needs `@shardflux/sdk` 0.9.0 (the workspace version).
|
package/README.md
CHANGED
|
@@ -227,8 +227,11 @@ The management tools:
|
|
|
227
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. |
|
|
228
228
|
| `workspace_status` | One workspace plus its five most recent operations. |
|
|
229
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. |
|
|
230
231
|
| `workspace_fork` | Fork into `new_key`. |
|
|
231
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
|
+
|
|
232
235
|
| `usage_summary` | The organization's usage summary for the current period. |
|
|
233
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). |
|
|
234
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. |
|
|
@@ -238,7 +241,7 @@ The management tools:
|
|
|
238
241
|
The SDK's workspace tools come from `workspaceTools()`:
|
|
239
242
|
|
|
240
243
|
- `exec`
|
|
241
|
-
- `read_file`, `write_file`, `list_files`, `search_files`, `edit_file` (0.
|
|
244
|
+
- `read_file`, `write_file`, `list_files`, and `search_files`, `edit_file` (0.4.0+)
|
|
242
245
|
- `list_processes`, `signal_process`
|
|
243
246
|
- `terminal_open`, `terminal_send`, `terminal_read`, `terminal_close`
|
|
244
247
|
- `git_clone`, `git_status`, `git_commit`
|
|
@@ -247,7 +250,10 @@ The SDK's workspace tools come from `workspaceTools()`:
|
|
|
247
250
|
They are published with **the SDK's JSON Schemas verbatim**, plus `workspace_key` and, where the
|
|
248
251
|
SDK schema has none, `timeout_ms`. They are filtered by the key's tool permissions (if the server
|
|
249
252
|
cannot read them, for example because the API is unreachable, it lists every workspace tool and logs
|
|
250
|
-
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
|
|
251
257
|
exact text and, without an `expected_revision`, reads the file's revision first so a concurrent change fails the
|
|
252
258
|
edit (`conflict`, `details.reason: revision_mismatch`) instead of being overwritten.
|
|
253
259
|
|
|
@@ -322,6 +328,11 @@ suspended. Workspace results carry `mode` and, for file-first workspaces, `tree_
|
|
|
322
328
|
- A failed operation is `code: operation_failed` with `details.error_code` (and `details.reason` when the
|
|
323
329
|
operation error has one) and, from 0.2.1, `retryable`: the operation error's own flag.
|
|
324
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
|
+
|
|
325
336
|
- **(0.4.0+)** A failed call has a `feedback` field next to `error`: a one-sentence suggestion to call
|
|
326
337
|
`send_feedback` with category `bug`, the `request_id` and the error code. Not for `invalid_arguments`,
|
|
327
338
|
`workspace_pinned` or `unauthenticated`. A failed `send_feedback` carries a `hint` instead (when to retry, or the
|
package/dist/server.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ 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.4.
|
|
6
|
+
export declare const MCP_SERVER_VERSION = "0.4.1";
|
|
7
7
|
/** The package this server is distributed as: its entry in GET /v1/client-versions (contracts §30.4). */
|
|
8
8
|
export declare const MCP_PACKAGE = "@shardflux/mcp";
|
|
9
9
|
export declare const ALL_TOOL_PERMISSIONS: readonly ToolName[];
|
|
@@ -75,6 +75,12 @@ export declare function summarizeWorkspace(v: WorkspaceView): {
|
|
|
75
75
|
operation_id: string | null;
|
|
76
76
|
at: string | null;
|
|
77
77
|
} | null;
|
|
78
|
+
/** A pending suspend-when-idle request (workspace_suspend with after_seconds); null when none. */
|
|
79
|
+
suspend_request: {
|
|
80
|
+
requested_at: string;
|
|
81
|
+
after_seconds: number;
|
|
82
|
+
not_before: string;
|
|
83
|
+
} | null;
|
|
78
84
|
created_at: string;
|
|
79
85
|
deleted_at: string | null;
|
|
80
86
|
tree_revision?: number | undefined;
|
package/dist/server.js
CHANGED
|
@@ -2,8 +2,9 @@
|
|
|
2
2
|
* Local stdio MCP server for Shardflux workspaces. It translates MCP tool
|
|
3
3
|
* calls into @shardflux/sdk calls made with the supplied scoped API key:
|
|
4
4
|
*
|
|
5
|
-
* - workspace management: workspace_open / _list / _status / _suspend
|
|
6
|
-
*
|
|
5
|
+
* - workspace management: workspace_open / _list / _status / _suspend
|
|
6
|
+
* (now, or with after_seconds once idle: suspend-when-idle) / _resume /
|
|
7
|
+
* _fork, operation_wait, usage_summary;
|
|
7
8
|
* - templates (0.3.0): template_get (versions, settings, a version's recipe), template_languages and
|
|
8
9
|
* template_build (a recipe v2 object or a template.yaml path inside the
|
|
9
10
|
* server's working directory; local `from` paths are uploaded by the SDK);
|
|
@@ -52,7 +53,7 @@ import { parse as parseYaml } from 'yaml';
|
|
|
52
53
|
import { DEFAULT_WAKE_TIMEOUT_MS } from "./config.js";
|
|
53
54
|
import { ToolError, describeToolError, redact } from "./errors.js";
|
|
54
55
|
import { makeFetch } from "./http.js";
|
|
55
|
-
export const MCP_SERVER_VERSION = '0.4.
|
|
56
|
+
export const MCP_SERVER_VERSION = '0.4.1';
|
|
56
57
|
/** The package this server is distributed as: its entry in GET /v1/client-versions (contracts §30.4). */
|
|
57
58
|
export const MCP_PACKAGE = '@shardflux/mcp';
|
|
58
59
|
const USER_AGENT = `shardflux-mcp/${MCP_SERVER_VERSION} shardflux-sdk-ts/${SDK_VERSION}`;
|
|
@@ -134,6 +135,8 @@ export function summarizeWorkspace(v) {
|
|
|
134
135
|
ended_reason: v.ended_reason,
|
|
135
136
|
/** Start commands and services of the template version; null when it has none. */
|
|
136
137
|
startup: v.startup ?? null,
|
|
138
|
+
/** A pending suspend-when-idle request (workspace_suspend with after_seconds); null when none. */
|
|
139
|
+
suspend_request: v.idle?.suspend_request ?? null,
|
|
137
140
|
created_at: v.created_at,
|
|
138
141
|
deleted_at: v.deleted_at,
|
|
139
142
|
};
|
|
@@ -278,6 +281,7 @@ const INSTRUCTIONS = [
|
|
|
278
281
|
'Shardflux persistent remote workspaces (Linux computers that keep files, packages and processes between sessions).',
|
|
279
282
|
'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.',
|
|
280
283
|
'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.',
|
|
284
|
+
'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.',
|
|
281
285
|
'A start (open, resume, fork) that no host can admit waits for capacity for at most 15 minutes, then fails with code operation_failed, details.error_code capacity_unavailable and retryable true: nothing was started; retry later if you still need it. retryable false means retrying will not help.',
|
|
282
286
|
'Opens, waits and wakes add timing (phases, server queued/run time) saying where the time went.',
|
|
283
287
|
'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).',
|
|
@@ -616,7 +620,51 @@ export function createShardfluxMcpServer(config, opts = {}) {
|
|
|
616
620
|
return { workspace: summarizeWorkspace(ws.data), recent_operations: ops.data.map(summarizeOperation) };
|
|
617
621
|
},
|
|
618
622
|
},
|
|
619
|
-
|
|
623
|
+
{
|
|
624
|
+
name: 'workspace_suspend',
|
|
625
|
+
title: 'Suspend workspace',
|
|
626
|
+
description: 'Suspend a running workspace (durable full-state checkpoint; processes stop, files and state are kept, and the next tool call resumes it). Returns the suspend operation (with wait: once finished, and its timing). ' +
|
|
627
|
+
'With after_seconds the suspend is deferred instead: the workspace is suspended once it has been idle that long. Use it when you finish your work (the end of your turn), e.g. after_seconds 60, so the workspace stops using RAM soon after instead of waiting for its idle timeout. ' +
|
|
628
|
+
'Your next tool call on the workspace cancels it; a command still running or a keepalive postpones it until after_seconds after it ends. Returns suspend_request (not_before: the earliest suspend). Not for file-first workspaces (never suspended).',
|
|
629
|
+
inputSchema: obj({
|
|
630
|
+
workspace_key: workspaceKeyProp(pinned),
|
|
631
|
+
after_seconds: {
|
|
632
|
+
type: 'integer',
|
|
633
|
+
minimum: 30,
|
|
634
|
+
maximum: 3600,
|
|
635
|
+
description: 'Suspend once the workspace has been idle this many seconds (30-3600) instead of now. Omit to suspend now.',
|
|
636
|
+
},
|
|
637
|
+
wait: { type: 'boolean', description: 'Wait until the suspend finishes (default false). Not with after_seconds.' },
|
|
638
|
+
}, keyRequired),
|
|
639
|
+
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false },
|
|
640
|
+
run: async (args, call) => {
|
|
641
|
+
const afterSeconds = typeof args.after_seconds === 'number' ? args.after_seconds : undefined;
|
|
642
|
+
if (afterSeconds !== undefined && args.wait === true) {
|
|
643
|
+
throw new ToolError('invalid_arguments', 'wait does not apply with after_seconds: the suspend happens later, once the workspace has been idle. Omit wait, or omit after_seconds to suspend now.');
|
|
644
|
+
}
|
|
645
|
+
const key = keyOf(args);
|
|
646
|
+
const ws = await resolve(key, { signal: call.signal });
|
|
647
|
+
if (ws.mode === 'file_first') {
|
|
648
|
+
throw notForMode(ws, 'suspend', 'api', `workspace_suspend is not available for the file-first workspace "${key}": nothing runs between its exec calls, so it is never suspended; its files persist as they are.`);
|
|
649
|
+
}
|
|
650
|
+
if (afterSeconds === undefined) {
|
|
651
|
+
const op = await cloud.workspaces.suspend(ws.id, lifecycleOpts(args, call));
|
|
652
|
+
call.operationId = op.id;
|
|
653
|
+
return { operation: summarizeOperation(op) };
|
|
654
|
+
}
|
|
655
|
+
const r = await ws.suspendWhenIdle({ afterSeconds });
|
|
656
|
+
if (r.operation)
|
|
657
|
+
call.operationId = r.operation.id;
|
|
658
|
+
return {
|
|
659
|
+
suspend_request: r.suspendRequest,
|
|
660
|
+
operation: r.operation ? summarizeOperation(r.operation) : null,
|
|
661
|
+
workspace: summarizeWorkspace(ws.data),
|
|
662
|
+
message: r.suspendRequest
|
|
663
|
+
? `Suspend scheduled: the workspace is suspended once it has been idle for ${r.suspendRequest.after_seconds} s (not before ${r.suspendRequest.not_before}). Your next tool call on it cancels this; a running command or keepalive postpones it.`
|
|
664
|
+
: `A suspend is already in progress (operation ${r.operation.id}, ${r.operation.state}); nothing was scheduled.`,
|
|
665
|
+
};
|
|
666
|
+
},
|
|
667
|
+
},
|
|
620
668
|
lifecycleTool('workspace_resume', 'Resume workspace', 'Resume a suspended workspace. Returns the resume operation (with wait: once finished, and its timing). Not for file-first workspaces (never suspended).', 'resume',
|
|
621
669
|
// Through the server's handle: a waited resume is held until the workspace runs (contracts §22.6) and the handle
|
|
622
670
|
// keeps the view and this server's tool token, so the next workspace tool starts at once.
|
|
@@ -728,7 +776,7 @@ export function createShardfluxMcpServer(config, opts = {}) {
|
|
|
728
776
|
{
|
|
729
777
|
name: 'usage_summary',
|
|
730
778
|
title: 'Usage summary',
|
|
731
|
-
description: 'Usage of the organization in the current billing period: each meter’s raw
|
|
779
|
+
description: 'Usage of the organization in the current billing period: each meter’s raw and billable quantity; each allowance’s included, used and remaining amount and cap_state (overage: past the allowance while opt-in overage is on, charged under the spend cap); allowance_exhausted with exhausted_reason; and spend_cap (opt-in overage: state, cap_minor, effective_cap_minor, charges_minor, remaining_minor, lines per allowance, projected_reached_at; amounts in cents). While an allowance is used up, opens and resumes fail with code allowance_exhausted and reason allowance_used (upgrade, or turn on overage), overage_paused (a plan payment is past due) or spend_cap_reached (raise the cap or upgrade): an owner or billing member acts in the console, and retrying does not help.',
|
|
732
780
|
inputSchema: obj({ organization_id: { type: 'string', minLength: 36, maxLength: 36, description: 'Organization id (default: the API key’s organization).' } }),
|
|
733
781
|
annotations: { readOnlyHint: true, openWorldHint: false },
|
|
734
782
|
run: async (args, call) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shardflux/mcp",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.1",
|
|
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.",
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
"@modelcontextprotocol/sdk": "1.30.0",
|
|
43
43
|
"yaml": "2.9.1",
|
|
44
44
|
"zod": "4.6.5",
|
|
45
|
-
"@shardflux/sdk": "^0.
|
|
45
|
+
"@shardflux/sdk": "^0.10.0"
|
|
46
46
|
},
|
|
47
47
|
"devDependencies": {
|
|
48
48
|
"@eslint/js": "10.0.1",
|