@foldkit/devtools-mcp 0.1.2 → 0.2.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 +26 -13
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +52 -4
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -4,8 +4,11 @@ A Model Context Protocol server that exposes a running [Foldkit](https://foldkit
|
|
|
4
4
|
|
|
5
5
|
With it attached, agents can:
|
|
6
6
|
|
|
7
|
-
- Read the current Model
|
|
8
|
-
-
|
|
7
|
+
- Read the current Model, or any historical Model by history index
|
|
8
|
+
- Narrow reads with dot-string paths and summarized payloads to fit token budgets
|
|
9
|
+
- List and inspect the Message history, with diffs and submodel chains
|
|
10
|
+
- Read the recorded init Model and init Command names
|
|
11
|
+
- Inspect runtime state: current index, retained history bounds, pause status
|
|
9
12
|
- Replay to any past state and resume
|
|
10
13
|
- Dispatch Messages into the runtime, decoded against your `Message` Schema
|
|
11
14
|
|
|
@@ -55,7 +58,7 @@ Runtime.makeProgram({
|
|
|
55
58
|
})
|
|
56
59
|
```
|
|
57
60
|
|
|
58
|
-
Restart your dev server, then restart your AI agent. The MCP server will appear with the
|
|
61
|
+
Restart your dev server, then restart your AI agent. The MCP server will appear with the `foldkit_*` tools attached.
|
|
59
62
|
|
|
60
63
|
The browser bridge runs inside your app, so the MCP server only sees a runtime while the app is open in a browser tab. Close the tab and the runtime disappears from `foldkit_list_runtimes`.
|
|
61
64
|
|
|
@@ -63,16 +66,26 @@ The browser bridge runs inside your app, so the MCP server only sees a runtime w
|
|
|
63
66
|
|
|
64
67
|
Each tool accepts an optional `runtime_id`. When omitted, the most recently connected runtime is used.
|
|
65
68
|
|
|
66
|
-
| Tool | Description
|
|
67
|
-
| ---------------------------- |
|
|
68
|
-
| `foldkit_list_runtimes` | Returns metadata for every connected browser tab. Agents call this first to discover which runtime to target.
|
|
69
|
-
| `foldkit_get_model` | Snapshots the current Model.
|
|
70
|
-
| `
|
|
71
|
-
| `
|
|
72
|
-
| `
|
|
73
|
-
| `
|
|
74
|
-
| `
|
|
75
|
-
| `
|
|
69
|
+
| Tool | Description |
|
|
70
|
+
| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
71
|
+
| `foldkit_list_runtimes` | Returns metadata for every connected browser tab. Agents call this first to discover which runtime to target. |
|
|
72
|
+
| `foldkit_get_model` | Snapshots the current Model. Accepts an optional `path` to narrow to a subtree and `expand` to control summarization. |
|
|
73
|
+
| `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 init Command names), use `foldkit_get_init`. |
|
|
74
|
+
| `foldkit_get_init` | Reads the recorded initial Model and the names of Commands returned from the application's `init` function. Equivalent to selecting the synthetic "init" row in the DevTools panel. |
|
|
75
|
+
| `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. |
|
|
76
|
+
| `foldkit_list_messages` | Lists recent Message history entries with pagination. Each entry carries the Message body, Command names triggered, timestamp, an `isModelChanged` flag, the diff path lists (`changedPaths` / `affectedPaths`), and any extracted Submodel chain. |
|
|
77
|
+
| `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. |
|
|
78
|
+
| `foldkit_list_keyframes` | Returns the indices Foldkit can replay back to. Index `-1` is the initial Model. |
|
|
79
|
+
| `foldkit_replay_to_keyframe` | Time-travels the runtime to a previous state. The runtime is paused at that snapshot until `foldkit_resume` is called. |
|
|
80
|
+
| `foldkit_resume` | Resumes normal execution after a replay. |
|
|
81
|
+
| `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. |
|
|
82
|
+
|
|
83
|
+
### Reading the Model efficiently
|
|
84
|
+
|
|
85
|
+
`foldkit_get_model` and `foldkit_get_model_at` are designed for AI agents reading state into a token-bounded context. Two parameters control the payload size:
|
|
86
|
+
|
|
87
|
+
- **`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.
|
|
88
|
+
- **`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.
|
|
76
89
|
|
|
77
90
|
## Architecture
|
|
78
91
|
|
package/dist/tools.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"AAAA,OAAO,EAAS,MAAM,EAA0C,MAAM,QAAQ,CAAA;
|
|
1
|
+
{"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"AAAA,OAAO,EAAS,MAAM,EAA0C,MAAM,QAAQ,CAAA;AAiB9E,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAA;AAgI3D,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;AA4GF;;;;GAIG;AACH,eAAO,MAAM,UAAU,GACrB,UAAU,eAAe,KACxB,aAAa,CAAC,cAAc,CAqI9B,CAAA"}
|
package/dist/tools.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Array, Effect, JSONSchema, Match, Option, Schema as S } from 'effect';
|
|
2
|
-
import { RequestDispatchMessage, RequestGetMessage, RequestGetModel, RequestListKeyframes, RequestListMessages, RequestListRuntimes, RequestReplayToKeyframe, RequestResume, } from 'foldkit/devtools-protocol';
|
|
2
|
+
import { RequestDispatchMessage, RequestGetInit, RequestGetMessage, 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.annotations({ description: RUNTIME_ID_DESCRIPTION }));
|
|
@@ -15,8 +15,25 @@ const MessageIndex = S.Number.pipe(S.int(), S.annotations({
|
|
|
15
15
|
const KeyframeIndex = S.Number.pipe(S.int(), S.annotations({
|
|
16
16
|
description: 'Index to replay to. Use -1 to jump to the initial Model (before any messages). Use a non-negative index to jump to the Model state right after that history index. Call foldkit_list_keyframes for the canonical replay points.',
|
|
17
17
|
}));
|
|
18
|
+
const ModelIndex = S.Number.pipe(S.int(), S.annotations({
|
|
19
|
+
description: 'Absolute history index. Returns the Model state right after the entry at this index was applied. To inspect the Model immediately before message N, pass index N - 1. For the initial Model, use foldkit_get_init.',
|
|
20
|
+
}));
|
|
21
|
+
const PathField = S.optional(S.String.annotations({
|
|
22
|
+
description: "Dot-string path into the Model anchored at 'root'. Examples: 'root', 'root.route', 'root.session.user', 'root.cards.0'. Matches the alphabet used by SerializedEntry.changedPaths so paths copied from one tool's output can be passed straight into the next. Defaults to 'root' (the whole Model).",
|
|
23
|
+
}));
|
|
24
|
+
const ExpandField = S.optional(S.Boolean.annotations({
|
|
25
|
+
description: "When false (the default), large arrays/records/strings collapse to '_summary' placeholders to keep payloads small. Set true to receive the literal value at the path. Pair with `path` to drill in: a narrow path with `expand: true` is the cheapest way to read a specific subtree at full fidelity.",
|
|
26
|
+
}));
|
|
18
27
|
const GetModelInput = S.Struct({
|
|
19
28
|
runtime_id: RuntimeIdField,
|
|
29
|
+
path: PathField,
|
|
30
|
+
expand: ExpandField,
|
|
31
|
+
});
|
|
32
|
+
const GetModelAtInput = S.Struct({
|
|
33
|
+
runtime_id: RuntimeIdField,
|
|
34
|
+
index: ModelIndex,
|
|
35
|
+
path: PathField,
|
|
36
|
+
expand: ExpandField,
|
|
20
37
|
});
|
|
21
38
|
const ListMessagesInput = S.Struct({
|
|
22
39
|
runtime_id: RuntimeIdField,
|
|
@@ -37,6 +54,12 @@ const ReplayToKeyframeInput = S.Struct({
|
|
|
37
54
|
const ResumeInput = S.Struct({
|
|
38
55
|
runtime_id: RuntimeIdField,
|
|
39
56
|
});
|
|
57
|
+
const GetInitInput = S.Struct({
|
|
58
|
+
runtime_id: RuntimeIdField,
|
|
59
|
+
});
|
|
60
|
+
const GetRuntimeStateInput = S.Struct({
|
|
61
|
+
runtime_id: RuntimeIdField,
|
|
62
|
+
});
|
|
40
63
|
const DispatchMessageInput = S.Struct({
|
|
41
64
|
runtime_id: RuntimeIdField,
|
|
42
65
|
message: S.Unknown.annotations({
|
|
@@ -107,9 +130,22 @@ const runRuntimeTool = (inputSchema, buildRequest, wsClient) => (rawInput) => Ef
|
|
|
107
130
|
export const buildTools = (wsClient) => [
|
|
108
131
|
{
|
|
109
132
|
name: 'foldkit_get_model',
|
|
110
|
-
description:
|
|
133
|
+
description: "Snapshot the current Model from a connected Foldkit runtime. By default the response is summarized (large arrays/records/strings collapse to `_summary` placeholders) to keep payloads small for AI agents. Pass `path` (e.g. 'root.session.user') to narrow to a subtree, and `expand: true` to receive the literal value at that path. Returns `{ value, atPath, summarized }`.",
|
|
111
134
|
inputSchema: JSONSchema.make(GetModelInput),
|
|
112
|
-
handle: runRuntimeTool(GetModelInput, () => RequestGetModel(
|
|
135
|
+
handle: runRuntimeTool(GetModelInput, ({ path, expand }) => RequestGetModel({
|
|
136
|
+
maybePath: Option.fromNullable(path),
|
|
137
|
+
expand: expand ?? false,
|
|
138
|
+
}), wsClient),
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
name: 'foldkit_get_model_at',
|
|
142
|
+
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.",
|
|
143
|
+
inputSchema: JSONSchema.make(GetModelAtInput),
|
|
144
|
+
handle: runRuntimeTool(GetModelAtInput, ({ index, path, expand }) => RequestGetModelAt({
|
|
145
|
+
index,
|
|
146
|
+
maybePath: Option.fromNullable(path),
|
|
147
|
+
expand: expand ?? false,
|
|
148
|
+
}), wsClient),
|
|
113
149
|
},
|
|
114
150
|
{
|
|
115
151
|
name: 'foldkit_list_messages',
|
|
@@ -122,10 +158,22 @@ export const buildTools = (wsClient) => [
|
|
|
122
158
|
},
|
|
123
159
|
{
|
|
124
160
|
name: 'foldkit_get_message',
|
|
125
|
-
description: 'Read a single Message history entry by absolute index,
|
|
161
|
+
description: 'Read a single Message history entry by absolute index. The response carries the SerializedEntry (tag, message body, commandNames, timestamp, `isModelChanged`, `changedPaths` for leaf-level mutations, `affectedPaths` adding their ancestor paths). For Submodel-routed entries (tag matches `Got*Message`), the entry also carries `submodelPath` listing wrapper tags from outer to inner and `maybeLeafTag` naming the innermost child Message. Model snapshots are not included; call foldkit_get_model_at with `index - 1` (before) and `index` (after) to inspect Model state around the entry.',
|
|
126
162
|
inputSchema: JSONSchema.make(GetMessageInput),
|
|
127
163
|
handle: runRuntimeTool(GetMessageInput, ({ index }) => RequestGetMessage({ index }), wsClient),
|
|
128
164
|
},
|
|
165
|
+
{
|
|
166
|
+
name: 'foldkit_get_init',
|
|
167
|
+
description: "Read the runtime's initial Model and the names of Commands returned from the application's `init` function. The init entry is the synthetic row at index -1 in the DevTools panel; this tool exposes the same data without time-travelling the runtime. `maybeModel` is `None` until the runtime has finished its first render and recorded init, then stays `Some` for the rest of the runtime's life.",
|
|
168
|
+
inputSchema: JSONSchema.make(GetInitInput),
|
|
169
|
+
handle: runRuntimeTool(GetInitInput, () => RequestGetInit(), wsClient),
|
|
170
|
+
},
|
|
171
|
+
{
|
|
172
|
+
name: 'foldkit_get_runtime_state',
|
|
173
|
+
description: "Snapshot the runtime's DevTools state: history bounds, current paused/live status, and whether init is recorded. Returns `currentIndex` (the absolute index of the most recent Message, or -1 when none), `startIndex` (the earliest absolute index still retained in the rolling buffer), `totalEntries` (count of retained entries), `isPaused`, `maybePausedAtIndex` (`Some(index)` when paused, `None` otherwise), and `hasInitModel`. Use it to reason about what `foldkit_list_messages` and `foldkit_get_message` will see, and to detect whether the runtime is currently paused at a replayed snapshot.",
|
|
174
|
+
inputSchema: JSONSchema.make(GetRuntimeStateInput),
|
|
175
|
+
handle: runRuntimeTool(GetRuntimeStateInput, () => RequestGetRuntimeState(), wsClient),
|
|
176
|
+
},
|
|
129
177
|
{
|
|
130
178
|
name: 'foldkit_list_keyframes',
|
|
131
179
|
description: 'List the available keyframes (replayable Model snapshots) from a Foldkit runtime.',
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@foldkit/devtools-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.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": "^3.19.19",
|
|
30
30
|
"rimraf": "^6.0.0",
|
|
31
31
|
"typescript": "^6.0.2",
|
|
32
|
-
"foldkit": "0.
|
|
32
|
+
"foldkit": "0.78.0"
|
|
33
33
|
},
|
|
34
34
|
"files": [
|
|
35
35
|
"dist"
|