@shardflux/mcp 0.4.3 → 0.5.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 CHANGED
@@ -3,7 +3,34 @@
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.3 (not yet published)
6
+ ## 0.5.1
7
+
8
+ Needs `@shardflux/sdk` 0.11.0 (the workspace version).
9
+
10
+ `workspace_suspend` takes `after_seconds` from 0 to 3600 (before: 30 to 3600): 0 suspends the workspace as soon as it
11
+ is idle (a few seconds of activity flush grace after its last work); a running command or a keepalive still postpones
12
+ it. The result's `message` then says `the workspace is suspended as soon as it is idle`. Out of range is still
13
+ `invalid_arguments` before any request.
14
+
15
+ ## 0.5.0
16
+
17
+ Needs `@shardflux/sdk` 0.11.0 (the workspace version).
18
+
19
+ A resume that restarted processes says so (cold boot, contracts §12). When the platform's VM runtime changed after a
20
+ suspend and no host can restore the memory snapshot, the cell resumes the workspace by booting its saved disk,
21
+ automatically, also when a workspace tool wakes it. Files are as of the suspend; every process was restarted.
22
+
23
+ - `timing.server` of results and error results adds `memory_restored` (when the API reports it: `false` for a cold
24
+ boot, `true` for a memory resume) and `cold_boot_reason` (e.g. `runtime_changed`), next to `resume_path`
25
+ (`cold_boot`).
26
+ - Such a result (`workspace_resume` or `operation_wait` of the resume, `workspace_open`, or any workspace tool whose
27
+ call woke the workspace) carries a top-level `notice` in plain language: the workspace was resumed from its saved
28
+ disk, files are kept, every process was restarted, so start dev servers, databases, watchers and background jobs
29
+ again. An error result carries it next to `error` when the wake before the failure did that.
30
+ - The server instructions and the `workspace_resume` description tell the agent what `notice` and
31
+ `memory_restored: false` mean. `processRestartNotice()` is exported with `compactTiming()`.
32
+
33
+ ## 0.4.3
7
34
 
8
35
  Fix: the server (MCP initialize, User-Agent, update check) reports 0.4.3. 0.4.2 was published reporting 0.4.1, its previous version; the build now
9
36
  fails when `MCP_SERVER_VERSION` differs from package.json.
package/README.md CHANGED
@@ -238,7 +238,7 @@ The management tools:
238
238
  | `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. |
239
239
  | `workspace_status` | One workspace plus its five most recent operations. |
240
240
  | `workspace_suspend`, `workspace_resume` | Lifecycle operation. Returns at once unless `wait: true` (then once it finished, through the SDK's own `wait`). |
241
- | `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. |
241
+ | `workspace_suspend` with `after_seconds` | (0.4.1) Suspend when idle: the workspace is suspended once it has been idle for `after_seconds` (0-3600; 0 = as soon as it is idle, 0.5.1+), 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. |
242
242
  | `workspace_fork` | Fork into `new_key`. |
243
243
  | `operation_wait` | Keep waiting for an operation. |
244
244
  | `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. |
@@ -371,6 +371,7 @@ suspended. Workspace results carry `mode` and, for file-first workspaces, `tree_
371
371
  - Phases are in order, as `phase(reason) ms`. `request(held)` means the API held the open until the workspace was
372
372
  ready. `capacity_pending(no_ready_host)` is time spent waiting for a host.
373
373
  - `server` is the operation's own queued, run and total time, plus the start or resume path the cell reported.
374
+ A resume also has `memory_restored` (0.5.0+) when the API reports it, and `cold_boot_reason` when it is false.
374
375
  - `outside_server_ms` is everything else: network, polling, reading the workspace, the tool token.
375
376
  - A failed or timed-out call carries it too, next to `error` (`{error, timing}`).
376
377
  - `workspace_open` uses the SDK's waited open: the API holds the request until the workspace is ready and answers
@@ -398,6 +399,13 @@ suspended. Workspace results carry `mode` and, for file-first workspaces, `tree_
398
399
  suspended).
399
400
  - With `SHARDFLUX_WAKE=off` the API's refusal comes back instead (`conflict`,
400
401
  `details.reason: workspace_not_running`, or `workspace_not_running` from the cell gateway).
402
+ - **A resume can restart processes (0.5.0+).** A resume normally restores memory and running processes. When the
403
+ platform's VM runtime changed after the suspend and no host can restore that memory snapshot, the resume boots
404
+ the workspace's saved disk instead (a cold boot), also when a workspace tool wakes it. The call still runs, and
405
+ files are as of the suspend, but every process was restarted. The result (or the error result) then carries
406
+ `timing.server.resume_path: "cold_boot"`, `memory_restored: false`, `cold_boot_reason` (e.g. `runtime_changed`)
407
+ and a `notice` the agent reads: to start its dev servers, databases, watchers and background jobs again. The
408
+ server instructions and the `workspace_resume` description say so too.
401
409
  - Tool tokens are never returned to the model.
402
410
  - **Keys, not ids.** Tools take workspace keys, resolved by exact match within the key's project.
403
411
 
package/dist/index.d.ts 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_PACKAGE, MCP_SERVER_VERSION, checkServerVersion, compactTiming, createShardfluxMcpServer, errorResult, feedbackSuggestion, 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, processRestartNotice, 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';
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_PACKAGE, MCP_SERVER_VERSION, checkServerVersion, compactTiming, createShardfluxMcpServer, errorResult, feedbackSuggestion, 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, processRestartNotice, 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
9
  export { ToolError, describeToolError, hostFeatureHint, modeHint, redact } from "./errors.js";
10
10
  export { main, stderrLogger } from "./main.js";
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.3";
6
+ export declare const MCP_SERVER_VERSION = "0.5.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[];
@@ -192,6 +192,8 @@ export declare function compactTiming(t: LifecycleTiming): {
192
192
  total_ms: number;
193
193
  phases: string[];
194
194
  server: {
195
+ cold_boot_reason?: string | undefined;
196
+ memory_restored?: boolean | undefined;
195
197
  resume_path?: string | undefined;
196
198
  warm_fallback?: string | undefined;
197
199
  start_path?: string | undefined;
@@ -205,6 +207,12 @@ export declare function compactTiming(t: LifecycleTiming): {
205
207
  action: import("@shardflux/sdk").LifecycleAction;
206
208
  outcome: import("@shardflux/sdk").TimingOutcome;
207
209
  };
210
+ /**
211
+ * (0.5.0) The `notice` of a result whose resume or wake booted the workspace's saved disk instead of restoring its memory
212
+ * (`resume_path` `cold_boot`, `memory_restored` false): plain language an agent acts on, since the dev servers, watchers
213
+ * and background jobs it started are gone. Undefined for any other timing, including one whose memory_restored is unknown.
214
+ */
215
+ export declare function processRestartNotice(t: LifecycleTiming): string | undefined;
208
216
  export declare function okResult(value: unknown): CallToolResult;
209
217
  /**
210
218
  * `timing` (optional): where the failed open/wait/wake spent its time, next to `error`. `extra` (0.4.0): more sibling
package/dist/server.js CHANGED
@@ -53,7 +53,7 @@ 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.4.3';
56
+ export const MCP_SERVER_VERSION = '0.5.1';
57
57
  /** The package this server is distributed as: its entry in GET /v1/client-versions (contracts §30.4). */
58
58
  export const MCP_PACKAGE = '@shardflux/mcp';
59
59
  const USER_AGENT = `shardflux-mcp/${MCP_SERVER_VERSION} shardflux-sdk-ts/${SDK_VERSION}`;
@@ -222,12 +222,32 @@ export function compactTiming(t) {
222
222
  ...(s.startPath ? { start_path: s.startPath } : {}),
223
223
  ...(s.warmFallback ? { warm_fallback: s.warmFallback } : {}),
224
224
  ...(s.resumePath ? { resume_path: s.resumePath } : {}),
225
+ // 0.5.0: false when the resume booted the saved disk (processes restarted); absent when the API did not say.
226
+ ...(typeof s.memoryRestored === 'boolean' ? { memory_restored: s.memoryRestored } : {}),
227
+ ...(s.coldBootReason ? { cold_boot_reason: s.coldBootReason } : {}),
225
228
  }
226
229
  : null,
227
230
  outside_server_ms: ms(t.outsideServerMs),
228
231
  retries: t.retries.length,
229
232
  };
230
233
  }
234
+ /** What a cold boot's reason means, for the agent (the cell's cold_boot_reason values). */
235
+ const COLD_BOOT_REASONS = {
236
+ runtime_changed: "the platform's VM runtime changed since the workspace was suspended, so its memory snapshot could not be restored",
237
+ };
238
+ /**
239
+ * (0.5.0) The `notice` of a result whose resume or wake booted the workspace's saved disk instead of restoring its memory
240
+ * (`resume_path` `cold_boot`, `memory_restored` false): plain language an agent acts on, since the dev servers, watchers
241
+ * and background jobs it started are gone. Undefined for any other timing, including one whose memory_restored is unknown.
242
+ */
243
+ export function processRestartNotice(t) {
244
+ const s = t.server;
245
+ if (s?.resumePath !== 'cold_boot' || s.memoryRestored !== false)
246
+ return undefined;
247
+ const why = s.coldBootReason ? ` (${s.coldBootReason}: ${COLD_BOOT_REASONS[s.coldBootReason] ?? 'the memory snapshot could not be restored'})` : '';
248
+ return (`The workspace was resumed from its saved disk, not from memory${why}. Its files are as they were when it was suspended, ` +
249
+ 'but every process was restarted, as after a reboot: start again any dev server, database, watcher or background job you had running, and open new terminals.');
250
+ }
231
251
  export function okResult(value) {
232
252
  if (isObject(value) && value.mime_type === 'image/png' && typeof value.data_base64 === 'string') {
233
253
  const { data_base64: data, ...meta } = value;
@@ -284,6 +304,7 @@ const INSTRUCTIONS = [
284
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.',
285
305
  '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.',
286
306
  'Opens, waits and wakes add timing (phases, server queued/run time) saying where the time went.',
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.',
287
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).',
288
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.',
289
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).',
@@ -630,9 +651,9 @@ export function createShardfluxMcpServer(config, opts = {}) {
630
651
  workspace_key: workspaceKeyProp(pinned),
631
652
  after_seconds: {
632
653
  type: 'integer',
633
- minimum: 30,
654
+ minimum: 0,
634
655
  maximum: 3600,
635
- description: 'Suspend once the workspace has been idle this many seconds (30-3600) instead of now. Omit to suspend now.',
656
+ description: 'Suspend once the workspace has been idle this many seconds (0-3600; 0 = as soon as it is idle) instead of now. Omit to suspend now.',
636
657
  },
637
658
  wait: { type: 'boolean', description: 'Wait until the suspend finishes (default false). Not with after_seconds.' },
638
659
  }, keyRequired),
@@ -660,12 +681,12 @@ export function createShardfluxMcpServer(config, opts = {}) {
660
681
  operation: r.operation ? summarizeOperation(r.operation) : null,
661
682
  workspace: summarizeWorkspace(ws.data),
662
683
  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.`
684
+ ? `Suspend scheduled: the workspace is suspended ${r.suspendRequest.after_seconds === 0 ? 'as soon as it is idle' : `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
685
  : `A suspend is already in progress (operation ${r.operation.id}, ${r.operation.state}); nothing was scheduled.`,
665
686
  };
666
687
  },
667
688
  },
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',
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',
669
690
  // Through the server's handle: a waited resume is held until the workspace runs (contracts §22.6) and the handle
670
691
  // keeps the view and this server's tool token, so the next workspace tool starts at once.
671
692
  (ws, o) => ws.resume({ ...o, agentLabel: config.agentLabel })),
@@ -994,7 +1015,8 @@ export function createShardfluxMcpServer(config, opts = {}) {
994
1015
  try {
995
1016
  const value = await Promise.race([running, aborted]);
996
1017
  log('info', 'tool call', { tool: name, outcome: 'ok', ms: Date.now() - started });
997
- return okResult(call.timed && call.timing && isObject(value) ? { ...value, timing: compactTiming(call.timing) } : value);
1018
+ const notice = call.timed && call.timing ? processRestartNotice(call.timing) : undefined;
1019
+ return okResult(call.timed && call.timing && isObject(value) ? { ...value, timing: compactTiming(call.timing), ...(notice ? { notice } : {}) } : value);
998
1020
  }
999
1021
  catch (err) {
1000
1022
  if (extra.signal.aborted) {
@@ -1032,7 +1054,9 @@ export function createShardfluxMcpServer(config, opts = {}) {
1032
1054
  : suggestsFeedback(info)
1033
1055
  ? { feedback: feedbackSuggestion(info) }
1034
1056
  : {};
1035
- return errorResult(info, call.timed && call.timing ? compactTiming(call.timing) : undefined, siblings);
1057
+ // A wake that booted the saved disk before the failure still restarted every process: said next to the error.
1058
+ const notice = call.timed && call.timing ? processRestartNotice(call.timing) : undefined;
1059
+ return errorResult(info, call.timed && call.timing ? compactTiming(call.timing) : undefined, { ...siblings, ...(notice ? { notice } : {}) });
1036
1060
  }
1037
1061
  });
1038
1062
  return server;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shardflux/mcp",
3
- "version": "0.4.3",
3
+ "version": "0.5.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.",
@@ -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.10.2"
49
+ "@shardflux/sdk": "^0.11.0"
50
50
  },
51
51
  "devDependencies": {
52
52
  "@eslint/js": "10.0.1",