@foldkit/devtools-mcp 0.10.0 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -7,6 +7,7 @@ With it attached, agents can:
7
7
  - Read the current Model, or any historical Model by history index
8
8
  - Narrow reads with dot-string paths and summarized payloads to fit token budgets
9
9
  - List and inspect the Message history, with Command and Mount lifecycle, diffs, and submodel chains
10
+ - Query history server-side: filter entries by changed Model paths, count Messages by tag, tail the latest entries, and diff the Models at two indices
10
11
  - Read the recorded init Model, the Commands returned from `init`, and the Mounts that fired during the first render
11
12
  - Inspect runtime state: current index, retained history bounds, pause status
12
13
  - Replay to any past state and resume
@@ -48,10 +49,10 @@ export default defineConfig({
48
49
  })
49
50
  ```
50
51
 
51
- In your `Runtime.makeProgram` call, pass your `Message` Schema. The runtime decodes every dispatched payload against it, returning a clean error if the shape does not match before it reaches your update function:
52
+ In your `Runtime.makeApplication` call, pass your `Message` Schema. The runtime decodes every dispatched payload against it, returning a clean error if the shape does not match before it reaches your update function:
52
53
 
53
54
  ```typescript
54
- Runtime.makeProgram({
55
+ Runtime.makeApplication({
55
56
  devTools: {
56
57
  // Rest of your DevTools config
57
58
  Message,
@@ -67,20 +68,22 @@ The browser bridge runs inside your app, so the MCP server only sees a runtime w
67
68
 
68
69
  Each tool accepts an optional `runtime_id`. When omitted, the most recently connected runtime is used.
69
70
 
70
- | Tool | Description |
71
- | ---------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
72
- | `foldkit_list_runtimes` | Returns metadata for every connected browser tab. Agents call this first to discover which runtime to target. |
73
- | `foldkit_get_model` | Snapshots the current Model. Accepts an optional `path` to narrow to a subtree and `expand` to control summarization. |
74
- | `foldkit_get_model_at` | Snapshots a historical Model after a given history entry. Pass `index: N - 1` to read the Model just before message `N`. Same `path`/`expand` semantics as `foldkit_get_model`. For the initial Model (and the init Commands and Mounts), use `foldkit_get_init`. |
75
- | `foldkit_get_init` | Reads the recorded initial Model, the Commands returned from the application's `init` function, and the Mounts that fired during the first render. Each Command and Mount carries its declared args. Equivalent to selecting the synthetic "init" row in the DevTools panel. |
76
- | `foldkit_get_runtime_state` | Snapshots the runtime's DevTools state: history bounds, current paused/live status, and whether init is recorded. Useful for understanding what `foldkit_list_messages` and `foldkit_get_message` will see and detecting whether the runtime is paused. |
77
- | `foldkit_list_messages` | Lists recent Message history entries with pagination. Each entry carries the Message body, Commands triggered (with args), Mounts that started or ended during the resulting render (with args), timestamp, an `isModelChanged` flag, the diff path lists (`changedPaths` / `affectedPaths`), and any extracted Submodel chain. |
78
- | `foldkit_get_message` | Reads one entry at a given index. The response carries the SerializedEntry only; to inspect the Model around the entry, call `foldkit_get_model_at` with `index - 1` (before) and `index` (after). Use `foldkit_get_init` for the synthetic init entry. |
79
- | `foldkit_list_keyframes` | Returns the indices Foldkit can replay back to. Index `-1` is the initial Model. |
80
- | `foldkit_replay_to_keyframe` | Time-travels the runtime to a previous state. The runtime is paused at that snapshot until `foldkit_resume` is called. |
81
- | `foldkit_resume` | Resumes normal execution after a replay. |
82
- | `foldkit_get_message_schema` | Describes the runtime's Message Schema so agents can construct valid Messages without reading the application source. With no arguments, returns a small variant index (top-level tag names plus payload fields). With `variant_tag` set to a dot-separated path of variant tags (e.g. `"GotChildMessage.Opened"`), narrows the JSON Schema along the chain and collapses deeper unions to summary placeholders. Returns `maybeResult: None` when the runtime hasn't configured `DevToolsConfig.Message`. |
83
- | `foldkit_dispatch_message` | Enqueues a Message into the runtime as if your application produced it. The runtime decodes the payload against your Schema and returns a clean error if it does not match. |
71
+ | Tool | Description |
72
+ | ------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
73
+ | `foldkit_list_runtimes` | Returns metadata for every connected browser tab. Agents call this first to discover which runtime to target. |
74
+ | `foldkit_get_model` | Snapshots the current Model. Accepts an optional `path` to narrow to a subtree and `expand` to control summarization. |
75
+ | `foldkit_get_model_at` | Snapshots a historical Model after a given history entry. Pass `index: N - 1` to read the Model just before message `N`. Same `path`/`expand` semantics as `foldkit_get_model`. Indices outside the readable range (older entries are evicted past the rolling buffer) are rejected with the valid bounds. For the initial Model (and the init Commands and Mounts), use `foldkit_get_init`. |
76
+ | `foldkit_get_init` | Reads the recorded initial Model, the Commands returned from the application's `init` function, and the Mounts that fired during the first render. Each Command and Mount carries its declared args. Equivalent to selecting the synthetic "init" row in the DevTools panel. |
77
+ | `foldkit_get_runtime_state` | Snapshots the runtime's DevTools state: history bounds, current paused/live status, and whether init is recorded. Useful for understanding what `foldkit_list_messages` and `foldkit_get_message` will see and detecting whether the runtime is paused. |
78
+ | `foldkit_list_messages` | Lists Message history entries. Each entry carries the Message body, Commands triggered (with args), Mounts that started or ended during the resulting render (with args), timestamp, an `isModelChanged` flag, the diff path lists (`changedPaths` / `affectedPaths`), and any extracted Submodel chain. Filter server-side with `changed_paths_match`, read the latest entries with `from_end`, and paginate forward with `since_index`. |
79
+ | `foldkit_count_messages_by_tag` | Counts retained history entries by Message tag, without payloads, sorted by count descending. A cheap reconnaissance call before paging through history: it surfaces the high-frequency Messages worth filtering out, and with `changed_paths_match` it answers which Message tags touch a Model subtree. |
80
+ | `foldkit_diff_models` | Diffs the Models at two history indices server-side, returning path-level changes `{ path, before, after }` with summarized values. Each side is `{ _tag: 'Present', value }`, or `{ _tag: 'Absent' }` when the path does not exist on that side. Pass `changed_paths_match` to narrow the diff to a subtree. |
81
+ | `foldkit_get_message` | Reads one entry at a given index. The response carries the SerializedEntry only; to inspect the Model around the entry, call `foldkit_get_model_at` with `index - 1` (before) and `index` (after). Use `foldkit_get_init` for the synthetic init entry. |
82
+ | `foldkit_list_keyframes` | Returns the indices Foldkit can replay back to. Index `-1` is the initial Model. |
83
+ | `foldkit_replay_to_keyframe` | Time-travels the runtime to a previous state. The runtime is paused at that snapshot until `foldkit_resume` is called. |
84
+ | `foldkit_resume` | Resumes normal execution after a replay. |
85
+ | `foldkit_get_message_schema` | Describes the runtime's Message Schema so agents can construct valid Messages without reading the application source. With no arguments, returns a small variant index (top-level tag names plus payload fields). With `variant_tag` set to a dot-separated path of variant tags (e.g. `"GotChildMessage.Opened"`), narrows the JSON Schema along the chain and collapses deeper unions to summary placeholders. Returns `maybeResult: None` when the runtime hasn't configured `DevToolsConfig.Message`. |
86
+ | `foldkit_dispatch_message` | Enqueues a Message into the runtime as if your application produced it. The runtime decodes the payload against your Schema and returns a clean error if it does not match. |
84
87
 
85
88
  ### Reading the Model efficiently
86
89
 
@@ -89,6 +92,15 @@ Each tool accepts an optional `runtime_id`. When omitted, the most recently conn
89
92
  - **`path`** is a dot-string anchored at `root` that narrows the response to a subtree. The alphabet matches the `changedPaths` array on each `SerializedEntry`, so a path observed in `foldkit_list_messages` can be passed straight back. Examples: `'root'` (the whole Model), `'root.route'`, `'root.session.user'`, `'root.cards.0'`. When the path doesn't resolve, the response is an error listing the keys available at the deepest segment that did resolve, so the agent can refine in one follow-up call.
90
93
  - **`expand`** controls summarization. By default (`false`), large arrays collapse to `{ _summary: 'array', length, sample: [head, last] }`, deeply nested records collapse to `{ _summary: 'record', keys }`, and long strings collapse to `{ _summary: 'string', length, head }`. Tagged-union variants (`{ _tag, ... }`) keep their tag and recursively summarize children. With `expand: true`, the literal value at the path is returned with no summarization. Pair a narrow `path` with `expand: true` to read a specific subtree at full fidelity without paying for the rest of the Model.
91
94
 
95
+ ### Querying history efficiently
96
+
97
+ High-frequency flows (drag-paint, scroll, keystroke) can fill the history buffer with thousands of entries; reading them page by page burns agent context. The history tools query server-side instead, in Model terms rather than Message-tag terms:
98
+
99
+ 1. **Recon with `foldkit_count_messages_by_tag`.** A few hundred bytes regardless of history size. It shows which tags dominate and the absolute index range retained.
100
+ 2. **Filter with `changed_paths_match`.** Both `foldkit_list_messages` and `foldkit_count_messages_by_tag` accept dot-string patterns matched against each entry's `changedPaths`. Patterns compare segment by segment for the length of the shorter side, so `root.grid` matches every change inside the grid subtree and `root.grid.5.3` also matches a wholesale replacement recorded at `root.grid`. `*` matches exactly one segment: `root.cards.*.title`. The path alphabet is the same one `foldkit_get_model` uses, so paths can be copied between tools.
101
+ 3. **Tail with `from_end: true`.** The natural live-debugging lens is "what just happened". `from_end` returns the final `limit` matching entries without first discovering the total count.
102
+ 4. **Diff with `foldkit_diff_models`.** Once the interesting indices are known, ask for the path-level delta between them instead of fetching two full snapshots and diffing client-side.
103
+
92
104
  ## Architecture
93
105
 
94
106
  Three components cooperate:
package/dist/install.js CHANGED
@@ -40,7 +40,7 @@ const printNextSteps = (alreadyRegistered) => {
40
40
  console.log('');
41
41
  console.log(' plugins: [foldkit({ devToolsMcpPort: 9988 })]');
42
42
  console.log('');
43
- console.log(' 2. Pass your Message Schema to Runtime.makeProgram (enables dispatch):');
43
+ console.log(' 2. Pass your Message Schema to Runtime.makeApplication (enables dispatch):');
44
44
  console.log('');
45
45
  console.log(' devTools: { Message }');
46
46
  console.log('');
@@ -1 +1 @@
1
- {"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"AAAA,OAAO,EAAS,MAAM,EAA8B,MAAM,QAAQ,CAAA;AAkBlE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAA;AAmI3D,KAAK,UAAU,GAAG,QAAQ,CAAC;IACzB,OAAO,EAAE,aAAa,CAAC,QAAQ,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC,CAAA;IAChE,OAAO,CAAC,EAAE,OAAO,CAAA;CAClB,CAAC,CAAA;AAEF,8HAA8H;AAC9H,MAAM,MAAM,cAAc,GAAG,QAAQ,CAAC;IACpC,IAAI,EAAE,MAAM,CAAA;IACZ,WAAW,EAAE,MAAM,CAAA;IACnB,WAAW,EAAE,MAAM,CAAA;IACnB,MAAM,EAAE,CAAC,QAAQ,EAAE,OAAO,KAAK,MAAM,CAAC,MAAM,CAAC,UAAU,CAAC,CAAA;CACzD,CAAC,CAAA;AA0GF;;;;GAIG;AACH,eAAO,MAAM,UAAU,GACrB,UAAU,eAAe,KACxB,aAAa,CAAC,cAAc,CAmJ9B,CAAA"}
1
+ {"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"AAAA,OAAO,EAAS,MAAM,EAA8B,MAAM,QAAQ,CAAA;AAoBlE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAA;AAiL3D,KAAK,UAAU,GAAG,QAAQ,CAAC;IACzB,OAAO,EAAE,aAAa,CAAC,QAAQ,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC,CAAA;IAChE,OAAO,CAAC,EAAE,OAAO,CAAA;CAClB,CAAC,CAAA;AAEF,8HAA8H;AAC9H,MAAM,MAAM,cAAc,GAAG,QAAQ,CAAC;IACpC,IAAI,EAAE,MAAM,CAAA;IACZ,WAAW,EAAE,MAAM,CAAA;IACnB,WAAW,EAAE,MAAM,CAAA;IACnB,MAAM,EAAE,CAAC,QAAQ,EAAE,OAAO,KAAK,MAAM,CAAC,MAAM,CAAC,UAAU,CAAC,CAAA;CACzD,CAAC,CAAA;AAsHF;;;;GAIG;AACH,eAAO,MAAM,UAAU,GACrB,UAAU,eAAe,KACxB,aAAa,CAAC,cAAc,CAoL9B,CAAA"}
package/dist/tools.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { Array, Effect, Match, Option, Schema as S } from 'effect';
2
- import { RequestDispatchMessage, RequestGetInit, RequestGetMessage, RequestGetMessageSchema, RequestGetModel, RequestGetModelAt, RequestGetRuntimeState, RequestListKeyframes, RequestListMessages, RequestListRuntimes, RequestReplayToKeyframe, RequestResume, } from 'foldkit/devtools-protocol';
2
+ import { RequestCountMessagesByTag, RequestDiffModels, RequestDispatchMessage, RequestGetInit, RequestGetMessage, RequestGetMessageSchema, RequestGetModel, RequestGetModelAt, RequestGetRuntimeState, RequestListKeyframes, RequestListMessages, RequestListRuntimes, RequestReplayToKeyframe, RequestResume, } from 'foldkit/devtools-protocol';
3
3
  const RUNTIME_ID_DESCRIPTION = 'Optional connection id of a specific Foldkit runtime. Defaults to the most recently connected runtime.';
4
4
  const DEFAULT_LIST_MESSAGES_LIMIT = 50;
5
5
  const RuntimeIdField = S.optional(S.String.annotate({ description: RUNTIME_ID_DESCRIPTION }));
@@ -9,6 +9,21 @@ const ListLimit = S.Int.check(S.isBetween({ minimum: 1, maximum: 500 })).annotat
9
9
  const SinceIndex = S.Int.annotate({
10
10
  description: 'Absolute history index to start from. Use the maybeNextIndex returned by a prior call to paginate.',
11
11
  });
12
+ const ChangedPathsMatchField = S.optional(S.Array(S.String).annotate({
13
+ description: "Dot-string path patterns matched against each entry's changedPaths, in the same 'root'-anchored alphabet foldkit_get_model uses. An entry matches when any pattern matches any changed path. Patterns compare segment-by-segment for the length of the shorter side, so 'root.grid' matches every change inside the grid subtree, and 'root.grid.5.3' also matches a wholesale replacement recorded at 'root.grid'. '*' matches exactly one segment: 'root.cards.*.title'. Entries that did not change the Model never match. An empty list applies no filter. Patterns not starting with 'root' or '*' are rejected.",
14
+ }));
15
+ const DiffChangedPathsMatchField = S.optional(S.Array(S.String).annotate({
16
+ description: "Dot-string path patterns narrowing the reported changes, in the same 'root'-anchored alphabet as changedPaths. A changed path is kept when any pattern matches it: patterns compare segment-by-segment for the length of the shorter side ('root.grid' keeps everything under the grid subtree) and '*' matches exactly one segment. An empty list applies no filter. Patterns not starting with 'root' or '*' are rejected.",
17
+ }));
18
+ const FromEndField = S.optional(S.Boolean.annotate({
19
+ description: "When true, return the final `limit` matching entries (the most recent) instead of the first, still in chronological order. The natural first call when live-debugging: 'what just happened'. Cannot be combined with since_index.",
20
+ }));
21
+ const DiffFromIndex = S.Int.annotate({
22
+ description: 'Absolute history index of the diff baseline: the Model state right after this entry was applied. Use -1 for the initial Model.',
23
+ });
24
+ const DiffToIndex = S.Int.annotate({
25
+ description: 'Absolute history index of the diff target: the Model state right after this entry was applied. -1 addresses the initial Model.',
26
+ });
12
27
  const MessageIndex = S.Int.annotate({
13
28
  description: 'Absolute history index of the entry to read.',
14
29
  });
@@ -39,6 +54,19 @@ const ListMessagesInput = S.Struct({
39
54
  runtime_id: RuntimeIdField,
40
55
  limit: S.optional(ListLimit),
41
56
  since_index: S.optional(SinceIndex),
57
+ changed_paths_match: ChangedPathsMatchField,
58
+ from_end: FromEndField,
59
+ });
60
+ const CountMessagesByTagInput = S.Struct({
61
+ runtime_id: RuntimeIdField,
62
+ since_index: S.optional(SinceIndex),
63
+ changed_paths_match: ChangedPathsMatchField,
64
+ });
65
+ const DiffModelsInput = S.Struct({
66
+ runtime_id: RuntimeIdField,
67
+ from_index: DiffFromIndex,
68
+ to_index: DiffToIndex,
69
+ changed_paths_match: DiffChangedPathsMatchField,
42
70
  });
43
71
  const GetMessageInput = S.Struct({
44
72
  runtime_id: RuntimeIdField,
@@ -93,6 +121,12 @@ const formatError = (reason) => ({
93
121
  content: [{ type: 'text', text: `Error: ${reason}` }],
94
122
  isError: true,
95
123
  });
124
+ /**
125
+ * Read a display string from a caught error. Effect's `TimeoutError` carries no
126
+ * `message`, so `error.message` is `undefined` on a relay timeout; fall back to
127
+ * the error's string form (its tag) rather than surfacing `Error: undefined`.
128
+ */
129
+ const errorReason = (error) => error.message ? error.message : String(error);
96
130
  /**
97
131
  * Decode a tool's raw input against its Effect Schema. Failure surfaces as an
98
132
  * `Error` for the outer handler's `catchAll` to convert into a `ToolResult`.
@@ -122,7 +156,7 @@ const callRuntimeRequest = (wsClient, explicitRuntimeId, buildRequest) => Effect
122
156
  const runtimeId = yield* resolveRuntimeId(wsClient, explicitRuntimeId);
123
157
  const response = yield* wsClient.sendRequest(buildRequest(), Option.some(runtimeId));
124
158
  return responseToToolResult(response);
125
- }).pipe(Effect.catch(error => Effect.succeed(formatError(error.message))));
159
+ }).pipe(Effect.catch(error => Effect.succeed(formatError(errorReason(error)))));
126
160
  /**
127
161
  * Build a tool handler that decodes its input, resolves the target runtime,
128
162
  * issues a typed `Request`, and formats the response. Used for every tool
@@ -131,7 +165,7 @@ const callRuntimeRequest = (wsClient, explicitRuntimeId, buildRequest) => Effect
131
165
  const runRuntimeTool = (inputSchema, buildRequest, wsClient) => (rawInput) => Effect.gen(function* () {
132
166
  const input = yield* decodeInput(inputSchema, rawInput);
133
167
  return yield* callRuntimeRequest(wsClient, input.runtime_id, () => buildRequest(input));
134
- }).pipe(Effect.catch(error => Effect.succeed(formatError(error.message))));
168
+ }).pipe(Effect.catch(error => Effect.succeed(formatError(errorReason(error)))));
135
169
  /**
136
170
  * Build the read-only Foldkit DevTools tool definitions. Each tool decodes its
137
171
  * input via Effect Schema, dispatches a typed `Request` through the WebSocket
@@ -149,7 +183,7 @@ export const buildTools = (wsClient) => [
149
183
  },
150
184
  {
151
185
  name: 'foldkit_get_model_at',
152
- description: "Snapshot a historical Model after a given history entry was applied. Pass `index: N - 1` to read the Model just before message N. Same `path`/`expand` semantics as foldkit_get_model. For the initial Model (and the names of Commands returned from the application's `init`), use foldkit_get_init.",
186
+ description: "Snapshot a historical Model after a given history entry was applied. Pass `index: N - 1` to read the Model just before message N. Same `path`/`expand` semantics as foldkit_get_model. Indices outside the retained history range (older entries are evicted past the rolling buffer) are rejected with the readable range. For the initial Model (and the names of Commands returned from the application's `init`), use foldkit_get_init.",
153
187
  inputSchema: toInputSchema(GetModelAtInput),
154
188
  handle: runRuntimeTool(GetModelAtInput, ({ index, path, expand }) => RequestGetModelAt({
155
189
  index,
@@ -159,11 +193,32 @@ export const buildTools = (wsClient) => [
159
193
  },
160
194
  {
161
195
  name: 'foldkit_list_messages',
162
- description: 'List recent Message history entries from a Foldkit runtime, with optional pagination via since_index.',
196
+ description: 'List Message history entries from a Foldkit runtime. Filter server-side with `changed_paths_match` to ask in Model terms (which Messages touched this subtree), read the most recent entries with `from_end: true`, and paginate forward via `since_index` using the returned `maybeNextIndex` (the absolute index of the next matching entry). On busy histories (drag, scroll, keystroke flows), call foldkit_count_messages_by_tag first to learn what is worth filtering.',
163
197
  inputSchema: toInputSchema(ListMessagesInput),
164
- handle: runRuntimeTool(ListMessagesInput, ({ limit, since_index }) => RequestListMessages({
198
+ handle: runRuntimeTool(ListMessagesInput, ({ limit, since_index, changed_paths_match, from_end }) => RequestListMessages({
165
199
  limit: limit ?? DEFAULT_LIST_MESSAGES_LIMIT,
166
200
  maybeSinceIndex: Option.fromNullishOr(since_index),
201
+ maybeChangedPathsMatch: Option.fromNullishOr(changed_paths_match),
202
+ fromEnd: from_end ?? false,
203
+ }), wsClient),
204
+ },
205
+ {
206
+ name: 'foldkit_count_messages_by_tag',
207
+ description: 'Count retained Message history entries by tag, without payloads. Returns `{ counts: [{ tag, count }], totalCount, scannedFromIndex, scannedToIndex }` sorted by count descending. A cheap reconnaissance call before paging through history: it surfaces the high-frequency Messages worth filtering out, and with `changed_paths_match` it answers which Message tags touch a Model subtree. Accepts the same `since_index`/`changed_paths_match` filters as foldkit_list_messages.',
208
+ inputSchema: toInputSchema(CountMessagesByTagInput),
209
+ handle: runRuntimeTool(CountMessagesByTagInput, ({ since_index, changed_paths_match }) => RequestCountMessagesByTag({
210
+ maybeSinceIndex: Option.fromNullishOr(since_index),
211
+ maybeChangedPathsMatch: Option.fromNullishOr(changed_paths_match),
212
+ }), wsClient),
213
+ },
214
+ {
215
+ name: 'foldkit_diff_models',
216
+ description: "Diff the Models at two history indices server-side. Returns path-level changes `{ path, before, after }` sorted by path (numeric segments in numeric order), with values summarized. Each side is `{ _tag: 'Present', value }`, or `{ _tag: 'Absent' }` when the path does not exist on that side (a key or element that was added or removed). `from_index`/`to_index` follow foldkit_get_model_at semantics: the Model right after that entry was applied, with -1 for the initial Model. Pass `changed_paths_match` to narrow the diff to a Model subtree. Far cheaper than fetching two snapshots and diffing client-side; follow up with foldkit_get_model_at plus `path`/`expand: true` to read a changed subtree at full fidelity.",
217
+ inputSchema: toInputSchema(DiffModelsInput),
218
+ handle: runRuntimeTool(DiffModelsInput, ({ from_index, to_index, changed_paths_match }) => RequestDiffModels({
219
+ fromIndex: from_index,
220
+ toIndex: to_index,
221
+ maybeChangedPathsMatch: Option.fromNullishOr(changed_paths_match),
167
222
  }), wsClient),
168
223
  },
169
224
  {
@@ -223,6 +278,6 @@ export const buildTools = (wsClient) => [
223
278
  handle: () => Effect.gen(function* () {
224
279
  const response = yield* wsClient.sendRequest(RequestListRuntimes(), Option.none());
225
280
  return responseToToolResult(response);
226
- }).pipe(Effect.catch(error => Effect.succeed(formatError(error.message)))),
281
+ }).pipe(Effect.catch(error => Effect.succeed(formatError(errorReason(error))))),
227
282
  },
228
283
  ];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@foldkit/devtools-mcp",
3
- "version": "0.10.0",
3
+ "version": "0.11.0",
4
4
  "description": "MCP server exposing Foldkit DevTools to AI agents (Claude Code, Cursor, etc.)",
5
5
  "type": "module",
6
6
  "main": "./dist/server.js",
@@ -29,7 +29,7 @@
29
29
  "effect": "4.0.0-beta.78",
30
30
  "rimraf": "^6.1.3",
31
31
  "typescript": "^6.0.3",
32
- "foldkit": "0.106.0"
32
+ "foldkit": "0.111.0"
33
33
  },
34
34
  "files": [
35
35
  "dist"