@plitzi/sdk-mcp 0.38.15 → 0.38.16
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 +69 -0
- package/README.md +6 -2
- package/dist/modules/mcp/helpers/guide.js +7 -1
- package/dist/modules/mcp/helpers/space.js +2 -2
- package/dist/modules/mcp/server.js +16 -11
- package/dist/modules/mcp/tests/activity.test.d.ts +1 -0
- package/dist/modules/mcp/tests/grants.test.d.ts +1 -0
- package/dist/modules/mcp/tools/activity.d.ts +16 -0
- package/dist/modules/mcp/tools/activity.js +36 -0
- package/dist/modules/mcp/tools/apply/index.js +1 -1
- package/dist/modules/mcp/tools/describeOperation.js +1 -1
- package/dist/modules/mcp/tools/index.js +3 -1
- package/dist/modules/mcp/tools/intents.js +1 -1
- package/dist/modules/mcp/tools/look.js +1 -1
- package/dist/modules/mcp/tools/render.js +1 -1
- package/dist/modules/mcp/tools/shared/tool.d.ts +7 -0
- package/dist/modules/mcp/tools/shared/tool.js +1 -0
- package/package.json +7 -7
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,74 @@
|
|
|
1
1
|
# @plitzi/sdk-mcp
|
|
2
2
|
|
|
3
|
+
## 0.38.16
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 9cfdccb: ## Security
|
|
8
|
+
|
|
9
|
+
- **A site's credential reads nothing of the space over MCP** (`@plitzi/sdk-mcp`): the `render` token every published
|
|
10
|
+
page embeds — and a self-hosting `host` key — resolved to a grant naming the space, and the MCP served every read
|
|
11
|
+
under it: the unpublished draft, the change history with who made each change and every value before and after, the
|
|
12
|
+
functions' source, the space's private data, its action documents and connectors. Only an `agent` grant attaches a
|
|
13
|
+
space now; any other is a connection with no space (`plitzi_render` and the public guides), told so in the words
|
|
14
|
+
`NO_SPACE_ATTACHED` already uses. A deployment whose `getGrant` answered other scopes for reading must issue agent
|
|
15
|
+
grants for them.
|
|
16
|
+
|
|
17
|
+
## Agents
|
|
18
|
+
- **`plitzi space watch`** (`@plitzi/cli`): the space read again on every save of `src/space/`, `src/plugins/`,
|
|
19
|
+
`src/data/` or `public/data/`, and only what changed said — what the save did to the space element by element, the
|
|
20
|
+
findings and warnings it brought and fixed (one that only moved down its file is the same one), a save that changed
|
|
21
|
+
nothing, or that the space stopped authoring and why. `--json` is one event a line (`ready`, `pass`, `refused`,
|
|
22
|
+
`error`), for an agent to run in the background instead of `author` and `space lint` after every edit. Each pass reads
|
|
23
|
+
the space in a process of its own (`space watch-pass`, hidden). `space lint`'s reading is `spaceFindings`, shared.
|
|
24
|
+
- **A space's activity, followed** (`@plitzi/sdk-shared`, `@plitzi/sdk-mcp`, `@plitzi/cli`): what happened on a space
|
|
25
|
+
after a cursor — its draft changed (a person in the builder, an agent, the autofix), a version went out, a server
|
|
26
|
+
action failed or was refused — and, with nothing yet, waited for, up to 50 s. One vocabulary,
|
|
27
|
+
`@plitzi/sdk-shared/activity` (`SpaceActivity`, `SpaceActivityPage`, `activityLine`, `isSpaceActivityPage`, the
|
|
28
|
+
limits); the reader's own writes left out and counted (`skippedMine`), `mine` keeps them.
|
|
29
|
+
- **`plitzi_activity { after, wait, limit, mine }`** (`@plitzi/sdk-mcp`), on the new `getActivity` adapter
|
|
30
|
+
(`SSRAdapters`). The listing budget is 78k (was 77k) for it.
|
|
31
|
+
- **`plitzi space activity [--follow] [--after <cursor>] [--mine] [--limit <n>] [--json]`** (`@plitzi/cli`), over
|
|
32
|
+
the platform's `GET /spaces/:spaceId/activity`; `--follow` stays, one page a line with `--json`, and asks again
|
|
33
|
+
when the platform does not answer.
|
|
34
|
+
- **The people in the builder see the agent work** (`@plitzi/sdk-server`, `@plitzi/sdk-shared`, `@plitzi/sdk-mcp`,
|
|
35
|
+
`@plitzi/plitzi-builder`): an agent grant carries the application the member connected (`Grant.client`,
|
|
36
|
+
`StoredSpaceToken.client`, `SSRGrant.client`), and every MCP write `SSRWriteContext.client` — the history's
|
|
37
|
+
`client` for an agent, which was never set. A collaborator may be an agent (`SubscriptionCollaborator.agent`): the
|
|
38
|
+
builder draws it as a robot, names it `Claude · for ana` (`collaboratorTitle`) in the header, on the canvas and in
|
|
39
|
+
its notices. The deployment announces it — Plitzi's joins it on a write, selects what it changed, and lets it go
|
|
40
|
+
after a minute of nothing. `activityLine` names it the same way (`Claude for ana over the MCP, …`), so
|
|
41
|
+
`plitzi space activity` says which application wrote.
|
|
42
|
+
- A tool may declare `documents: false` (`ToolSpec`): the host loads none of the space for it, and passes the call's
|
|
43
|
+
abort signal through `ToolContext.activity`.
|
|
44
|
+
|
|
45
|
+
## Authoring
|
|
46
|
+
- **A file that does not parse is said at its line under any loader** (`@plitzi/sdk-authoring`): `moduleProblem` read
|
|
47
|
+
the first line of the error, and an esbuild transform (tsx, vitest) puts only `Transform failed with 1 error:` there
|
|
48
|
+
— the file, line and what is wrong are on the next. A line of the message that points at a file of the project is
|
|
49
|
+
now the problem (`src/space/hero.ts:139: Expected "}" but found ";"`), the project's root matched as given and as it
|
|
50
|
+
is on disk (`/var/…` and `/private/var/…` on macOS).
|
|
51
|
+
|
|
52
|
+
## Testing
|
|
53
|
+
- **`unusedPort` is chosen where the system never hands one out** (`@plitzi/sdk-server`): it answered a port the system
|
|
54
|
+
had just handed out (`listen(0)`), from the ephemeral range every outgoing connection takes its local port from — so
|
|
55
|
+
another worker's request could take it before the test listened, and CI failed `runtime.test.ts` with `EADDRINUSE`
|
|
56
|
+
(the page server then ends the process). It is a free port below that range now, at random, checked first.
|
|
57
|
+
|
|
58
|
+
## Docs
|
|
59
|
+
- **A self-hosted runtime's endpoints go in `data`**, as 0.38.15 moved them: `docs/en/runtimes.md`, the
|
|
60
|
+
`10-runtime` example and the website still put `served.stage` in `preAuth`, before the cross-site guard.
|
|
61
|
+
- New: `docs/en/activity.md`; `space watch` in `docs/en/agent-tooling.md`'s loop; the CLI README, the CLI skill, the
|
|
62
|
+
generated project's `AGENTS.md`, the MCP guide and README.
|
|
63
|
+
|
|
64
|
+
- Updated dependencies [9cfdccb]
|
|
65
|
+
- @plitzi/plitzi-sdk@0.38.16
|
|
66
|
+
- @plitzi/sdk-server@0.38.16
|
|
67
|
+
- @plitzi/sdk-authoring@0.38.16
|
|
68
|
+
- @plitzi/sdk-schema@0.38.16
|
|
69
|
+
- @plitzi/sdk-shared@0.38.16
|
|
70
|
+
- @plitzi/sdk-style@0.38.16
|
|
71
|
+
|
|
3
72
|
## 0.38.15
|
|
4
73
|
|
|
5
74
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -66,12 +66,13 @@ The server is stateless: it resolves the space per request and reads and writes
|
|
|
66
66
|
|
|
67
67
|
| Adapter | Required | Purpose |
|
|
68
68
|
|---|---|---|
|
|
69
|
-
| `getGrant(req)` | yes | The space this request operates on **and whether the caller may change it** (`{ spaceId, scope, userId?, canWrite }`), decoded from the verified `Authorization` bearer. You own the JWT secret and the authorization model, so both are decided on your side. Return `undefined` for a missing or invalid token. |
|
|
69
|
+
| `getGrant(req)` | yes | The space this request operates on **and whether the caller may change it** (`{ spaceId, scope, userId?, client?, canWrite }` — `client`, the application an agent grant was given to, rides every write as `SSRWriteContext.client`), decoded from the verified `Authorization` bearer. You own the JWT secret and the authorization model, so both are decided on your side. Return `undefined` for a missing or invalid token. |
|
|
70
70
|
| `getSchema(spaceId, env)` | yes | The element schema the tools read and mutate. |
|
|
71
71
|
| `getStyle(spaceId, env)` | yes | The full style document, including `platform`/`mode`. |
|
|
72
72
|
| `saveSchema(spaceId, env, schema)` | for writes | Persist a mutated schema. Without it, `plitzi_apply` reports `persisted: false` and leads its warnings with what was NOT saved. |
|
|
73
73
|
| `saveStyle(spaceId, env, style)` | for writes | Persist a mutated style document. |
|
|
74
74
|
| `getChanges(spaceId, env, query)` | no | The space's change history, for the `plitzi://changes` resources. Writes arrive with an `SSRWriteContext` (`userId`, one `batch` per tool call) so a consumer can record them. |
|
|
75
|
+
| `getActivity(spaceId, query, reader, signal)` | no | What happened on the space after a cursor — the draft changed, a version went out, an action failed (`SpaceActivityPage`, `@plitzi/sdk-shared/activity`) — waiting up to `query.wait` seconds; `reader` is the member the agent acts for with `origin: 'mcp'`, whose own writes are left out unless `mine`, and `signal` ends the wait with the call. Without it there is no `plitzi_activity`. Only an `agent` grant reaches it. |
|
|
75
76
|
| `getOfflineData(spaceId, env, rev)` | for preview | Read side of draft-preview. Only the preview endpoint calls it. |
|
|
76
77
|
| `getComponentCatalog(spaceId, env)` | no | The element types the space's plugins add, for the catalog resources. |
|
|
77
78
|
| `getConnectors` / `saveConnector` / `deleteConnector` | for connectors | The space's connector manifests, read and written by the connector operations. |
|
|
@@ -90,7 +91,9 @@ the page server too; a dedicated MCP that keeps its weight down writes those fou
|
|
|
90
91
|
|
|
91
92
|
Without a `getGrant` that resolves, the server still answers its public surface — the handshake, the tool list,
|
|
92
93
|
the guide, the CSS-property catalog — and asks for a space only when a tool or resource needs one. A grant that
|
|
93
|
-
resolves with `canWrite: false` reads
|
|
94
|
+
resolves with `canWrite: false` reads the space and is refused at every write tool. Only an `agent` grant attaches a
|
|
95
|
+
space: a site's credential (`scope: 'render'` or `'host'`) names one too, but the draft, its functions' source, its
|
|
96
|
+
private data and who edits it are not a site's to read — it is answered as a connection with no space.
|
|
94
97
|
|
|
95
98
|
## Options
|
|
96
99
|
|
|
@@ -123,6 +126,7 @@ createServer({ adapters }, { screenshot: { serviceUrl, renderBaseUrl }, proxy: {
|
|
|
123
126
|
| `plitzi_look` | read | See a saved page: its accessibility outline (default), its HTML, or a PNG (desktop, mobile or both) |
|
|
124
127
|
| `plitzi_render` | read | Render a self-contained UI widget, offline, with no space |
|
|
125
128
|
| `plitzi_try_function` | write | Run one task of the space's functions against the draft, in the sandbox: its value, logs and error |
|
|
129
|
+
| `plitzi_activity` | read | What happened on the space since a cursor — edits by people and other agents, versions out, actions failing — waited for up to 50 s; the agent's own writes left out and counted |
|
|
126
130
|
|
|
127
131
|
The server advertises each tool's access as its `readOnlyHint` annotation, so a host can run a read without asking
|
|
128
132
|
and ask before a write.
|
|
@@ -2,7 +2,7 @@ import { BUILTIN_TRANSFORMERS, GLOBAL_SOURCES, MOTION_ENTERS, MOTION_LOOPS } fro
|
|
|
2
2
|
//#region src/modules/mcp/helpers/guide.ts
|
|
3
3
|
var GLOBALS = GLOBAL_SOURCES.map((source) => `\`${source}\``).join(", ");
|
|
4
4
|
var OTHER_TRANSFORMERS = Object.keys(BUILTIN_TRANSFORMERS).filter((action) => action !== "twigTemplate").map((action) => `\`${action}\``).join(", ");
|
|
5
|
-
var serverInstructions = "Plitzi AI server: read-then-write editing of a Plitzi space. Reads follow a filesystem model — list cheap, read one item in detail on demand; never fetch a whole tree you do not need. Workflow: (1) read plitzi://primer/{env} once — it bundles a quickstart of the guide, types, css-properties and page/definition/variable summaries in a single call; (2) plitzi_search with include:\"detail\" to jump to elements — each hit then carries its uri, stateVersion AND full style/resolvedStyle, so an edit needs no per-element read; open a page skeleton or element only when you need its tree/detail (the skeleton already lists the style classes of each node, and plitzi_read fetches many uris at once); (3) plitzi_apply with dryRun to preview a batch; (4) plitzi_apply to persist, passing expectedResourceVersions to guard against concurrent edits — apply and search both hand back the versions you need for the next edit. Use patchElement / patchDefinition to change only some props / CSS (the upsert variants replace them all). An element read (and search include:\"detail\") inlines the CSS of the definitions it attaches under resolvedStyle, so you rarely need a separate definition read. Every element has ONE name and it IS its id ([A-Za-z0-9_-] starting with a letter, unique, chosen by you) — the same name is the runtime wiring key, so a provider source is `<type>_<name>.<field>`, visible to the provider’s DESCENDANTS only (bind inside its subtree). CSS is plain kebab-case — write shorthands freely (`border: 1px solid red`, `padding: 8px 16px`, `font: bold 16px/1.5 Arial`), they are expanded to longhands for you and STORED that way, so read-back shows the longhands; style vars are var(--name), schema vars are {{name}}. READERS — do not confuse them: MCP *resources* are the browsable catalog (list them, or open one by URI); plitzi_search FINDS elements by label/type/attribute and hands you the name to write ops against; plitzi_read BATCH-fetches URIs you already hold. Reach for search/read to work; browse resources to discover. CMS / API integrations (Strapi, WordPress, Contentful, any REST service) go through a CONNECTOR: a manifest you author declaring the base URL, endpoints and auth template, which the SERVER executes — read plitzi://connector-presets for working ones and plitzi://connectors/{env} for this space’s. You never see or create the credential; the space owner attaches it, and the connector saves without one. An apiContainer reads through it only with runtime:\"server\". Elements also carry applied style variants + visibility (initialState), data bindings and interaction flows: edit them with patchElement (initialState), upsertBinding/patchBinding/deleteBinding, and upsertInteractionFlow/patchInteractionNode/deleteInteraction. An element read shows all three plus availableVariants (which variant each of its classes offers). Build pages people can use without sight: screen readers and browser agents (Claude in Chrome) work a page through its accessibility tree, so every control needs words and clicks go on buttons and links — see Accessibility in plitzi://guide, and plitzi_look (its accessibility outline, the default view) to check. Separately, to SHOW the user a small self-contained widget (offline, no space or backend) instead of editing the space — a card, hero, pricing table, a visual answer — use plitzi_render; read plitzi://render/guide for it.";
|
|
5
|
+
var serverInstructions = "Plitzi AI server: read-then-write editing of a Plitzi space. Reads follow a filesystem model — list cheap, read one item in detail on demand; never fetch a whole tree you do not need. Workflow: (1) read plitzi://primer/{env} once — it bundles a quickstart of the guide, types, css-properties and page/definition/variable summaries in a single call; (2) plitzi_search with include:\"detail\" to jump to elements — each hit then carries its uri, stateVersion AND full style/resolvedStyle, so an edit needs no per-element read; open a page skeleton or element only when you need its tree/detail (the skeleton already lists the style classes of each node, and plitzi_read fetches many uris at once); (3) plitzi_apply with dryRun to preview a batch; (4) plitzi_apply to persist, passing expectedResourceVersions to guard against concurrent edits — apply and search both hand back the versions you need for the next edit. Use patchElement / patchDefinition to change only some props / CSS (the upsert variants replace them all). An element read (and search include:\"detail\") inlines the CSS of the definitions it attaches under resolvedStyle, so you rarely need a separate definition read. Every element has ONE name and it IS its id ([A-Za-z0-9_-] starting with a letter, unique, chosen by you) — the same name is the runtime wiring key, so a provider source is `<type>_<name>.<field>`, visible to the provider’s DESCENDANTS only (bind inside its subtree). CSS is plain kebab-case — write shorthands freely (`border: 1px solid red`, `padding: 8px 16px`, `font: bold 16px/1.5 Arial`), they are expanded to longhands for you and STORED that way, so read-back shows the longhands; style vars are var(--name), schema vars are {{name}}. READERS — do not confuse them: MCP *resources* are the browsable catalog (list them, or open one by URI); plitzi_search FINDS elements by label/type/attribute and hands you the name to write ops against; plitzi_read BATCH-fetches URIs you already hold. Reach for search/read to work; browse resources to discover. CMS / API integrations (Strapi, WordPress, Contentful, any REST service) go through a CONNECTOR: a manifest you author declaring the base URL, endpoints and auth template, which the SERVER executes — read plitzi://connector-presets for working ones and plitzi://connectors/{env} for this space’s. You never see or create the credential; the space owner attaches it, and the connector saves without one. An apiContainer reads through it only with runtime:\"server\". Elements also carry applied style variants + visibility (initialState), data bindings and interaction flows: edit them with patchElement (initialState), upsertBinding/patchBinding/deleteBinding, and upsertInteractionFlow/patchInteractionNode/deleteInteraction. An element read shows all three plus availableVariants (which variant each of its classes offers). Build pages people can use without sight: screen readers and browser agents (Claude in Chrome) work a page through its accessibility tree, so every control needs words and clicks go on buttons and links — see Accessibility in plitzi://guide, and plitzi_look (its accessibility outline, the default view) to check. On a space people edit too, plitzi_activity says — and waits for — what they changed since you last looked. Separately, to SHOW the user a small self-contained widget (offline, no space or backend) instead of editing the space — a card, hero, pricing table, a visual answer — use plitzi_render; read plitzi://render/guide for it.";
|
|
6
6
|
var widgetsOnlyInstructions = "Plitzi widget server (widgets-only connection). This connection carries NO Plitzi space: nothing can be read or edited in one, and the space tools are not offered here — do not look for them. What it does do is build self-contained UI widgets fully offline, with no backend, account or setup: call plitzi_render to SHOW the user a real rendered layout (card, hero, pricing table, checklist, form, gallery) instead of describing one. Read plitzi://render/guide first — the element/prop table, the style model and a worked example; plitzi://render/types lists every element type you can author (plitzi_read fetches both). To change a widget you already rendered, call plitzi_render again with patch:true and its renderId. If the user wants to edit their real Plitzi space from here, that is a reconnection they make: the integration must be re-authorized and granted a space (the consent screen lists theirs) — you cannot do it from this connection, and no retry will change it.";
|
|
7
7
|
var guideQuickstart = `# Plitzi AI MCP — quickstart
|
|
8
8
|
This is the condensed guide; read \`plitzi://guide\` for the full reference (every resource, op and example).
|
|
@@ -248,6 +248,12 @@ When you do hold several refs to open (e.g. from a skeleton), read them together
|
|
|
248
248
|
the way to SEE overflow and misalignment (desktop, mobile or \`both\`); \`"both"\` image and outline. A change not
|
|
249
249
|
saved yet is seen with \`plitzi_apply\`'s \`dryRun\` and \`look\`, the same views. It needs the SSR render service
|
|
250
250
|
(\`PREVIEW_UNAVAILABLE\` without it); without a browser, an image or an outline falls back to the HTML, said.
|
|
251
|
+
- \`plitzi_activity { after, wait }\` — what happened on the space since you last looked: the draft changed (a person
|
|
252
|
+
in the builder, another agent, the autofix — \`seq\` is its place in \`plitzi://changes/{env}\`), a version went
|
|
253
|
+
out, a server action failed or was refused. The first call, without \`after\`, answers the latest and a \`cursor\`;
|
|
254
|
+
pass it back as \`after\` and the call waits up to \`wait\` seconds (45, at most 50) until something happens. Your own
|
|
255
|
+
writes are left out and counted (\`skippedMine\`; \`mine: true\` keeps them). On a space people edit too, call it
|
|
256
|
+
before a write that rests on what you read earlier.
|
|
251
257
|
- \`plitzi_render\` — a different job: show the user a self-contained widget built offline. It never touches the space.
|
|
252
258
|
|
|
253
259
|
## Suggestions — the same page with fewer elements
|
|
@@ -280,7 +280,7 @@ var emptySpaceMessage = "Space data not available";
|
|
|
280
280
|
* can tell "this connection cannot do that" from "the call failed". */
|
|
281
281
|
var noSpaceErrorCode = "NO_SPACE_ATTACHED";
|
|
282
282
|
var readOnlyGrantErrorCode = "READ_ONLY_GRANT";
|
|
283
|
-
var unauthorizedSpaceMessage = "This connection has no space attached (a guest or widgets-only grant, or a token that carries no space), so NOTHING in a space can be read or edited — every other space tool will fail the same way, do not retry them. Use plitzi_render instead: it builds a self-contained widget offline, with no space, backend or account (read plitzi://render/guide). To edit a real space, the user must reconnect the integration and grant access to one.";
|
|
283
|
+
var unauthorizedSpaceMessage = "This connection has no space attached (a guest or widgets-only grant, a site’s own credential — its render token or host key, which reads its pages and nothing of the space behind them — or a token that carries no space), so NOTHING in a space can be read or edited — every other space tool will fail the same way, do not retry them. Use plitzi_render instead: it builds a self-contained widget offline, with no space, backend or account (read plitzi://render/guide). To edit a real space, the user must reconnect the integration and grant access to one.";
|
|
284
284
|
/** Raised when a space-dependent operation runs on a connection that resolves no spaceId. A type of its own so the
|
|
285
285
|
* host answers it as a STATE of the connection — a plain result the agent reads — instead of letting it surface as
|
|
286
286
|
* a failed call, which hosts render to the user as "cannot connect to this server". */
|
|
@@ -290,7 +290,7 @@ var NoSpaceError = class extends Error {
|
|
|
290
290
|
this.name = "NoSpaceError";
|
|
291
291
|
}
|
|
292
292
|
};
|
|
293
|
-
var readOnlyGrantMessage = "This connection may READ this space but not change it:
|
|
293
|
+
var readOnlyGrantMessage = "This connection may READ this space but not change it: the member who authorized it no longer has permission to edit this space. Every write tool will fail the same way, do not retry them — reads still work. To edit, the user must reconnect the integration and grant edit access.";
|
|
294
294
|
/** Raised when a write runs under a grant that carries no write authority. Like NoSpaceError this is a STATE of
|
|
295
295
|
* the connection rather than a failed call, so the host renders it as a result the agent can act on. */
|
|
296
296
|
var ReadOnlyGrantError = class extends Error {
|
|
@@ -18,7 +18,7 @@ var asText = (data) => ({ content: [{
|
|
|
18
18
|
var createMcpServer = async ({ adapters, getGrant, preview, screenshot, logger, renderStreaming = true, proxy }) => {
|
|
19
19
|
const log = createMcpLog(logger);
|
|
20
20
|
const grant = await getGrant().catch(() => void 0);
|
|
21
|
-
const spaceId = grant?.spaceId;
|
|
21
|
+
const spaceId = grant?.scope === "agent" ? grant.spaceId : void 0;
|
|
22
22
|
const hasSpace = spaceId !== void 0;
|
|
23
23
|
const requireSpaceId = () => {
|
|
24
24
|
if (spaceId === void 0) throw new NoSpaceError();
|
|
@@ -53,10 +53,11 @@ var createMcpServer = async ({ adapters, getGrant, preview, screenshot, logger,
|
|
|
53
53
|
...data ? { data } : {}
|
|
54
54
|
};
|
|
55
55
|
};
|
|
56
|
-
const { saveSchema, saveStyle, saveConnector, deleteConnector, saveAction, deleteAction, saveFunctions, tryFunction, saveData } = adapters;
|
|
56
|
+
const { saveSchema, saveStyle, saveConnector, deleteConnector, saveAction, deleteAction, saveFunctions, tryFunction, saveData, getActivity } = adapters;
|
|
57
57
|
const write = {
|
|
58
58
|
userId: grant?.userId,
|
|
59
|
-
batch: randomUUID()
|
|
59
|
+
batch: randomUUID(),
|
|
60
|
+
...grant?.client === void 0 ? {} : { client: grant.client }
|
|
60
61
|
};
|
|
61
62
|
const persisters = {
|
|
62
63
|
schema: saveSchema ? (schema) => saveSchema(requireWritableSpaceId(), MCP_ENV, schema, write) : void 0,
|
|
@@ -72,7 +73,7 @@ var createMcpServer = async ({ adapters, getGrant, preview, screenshot, logger,
|
|
|
72
73
|
const getSpace = () => spacePromise ??= loadSpace();
|
|
73
74
|
const server = new McpServer({
|
|
74
75
|
name: "plitzi-mcp",
|
|
75
|
-
version: "0.38.
|
|
76
|
+
version: "0.38.16"
|
|
76
77
|
}, { instructions: hasSpace ? serverInstructions : widgetsOnlyInstructions });
|
|
77
78
|
const { getChanges } = adapters;
|
|
78
79
|
registerResources(server, getSpace, MCP_ENV, log, hasSpace, getChanges ? (query) => getChanges(requireSpaceId(), MCP_ENV, query) : void 0);
|
|
@@ -80,15 +81,19 @@ var createMcpServer = async ({ adapters, getGrant, preview, screenshot, logger,
|
|
|
80
81
|
streaming: renderStreaming,
|
|
81
82
|
proxyEndpoint: proxy?.endpoint
|
|
82
83
|
});
|
|
83
|
-
const toolContext = async (tool) => ({
|
|
84
|
-
space: await getSpace(),
|
|
84
|
+
const toolContext = async (tool, signal) => ({
|
|
85
|
+
space: tool.documents === false ? emptySpace() : await getSpace(),
|
|
85
86
|
env: MCP_ENV,
|
|
86
87
|
persisters,
|
|
87
88
|
spaceId: requireSpaceId(),
|
|
88
89
|
preview,
|
|
89
90
|
screenshot,
|
|
90
|
-
proxy: proxyForTool(proxy, tool),
|
|
91
|
-
...tryFunction ? { tryFunction: (task, params) => tryFunction(requireWritableSpaceId(), task, params, write) } : {}
|
|
91
|
+
proxy: proxyForTool(proxy, tool.name),
|
|
92
|
+
...tryFunction ? { tryFunction: (task, params) => tryFunction(requireWritableSpaceId(), task, params, write) } : {},
|
|
93
|
+
...getActivity ? { activity: (query) => getActivity(requireSpaceId(), query, {
|
|
94
|
+
userId: grant?.userId,
|
|
95
|
+
origin: "mcp"
|
|
96
|
+
}, signal) } : {}
|
|
92
97
|
});
|
|
93
98
|
const spacelessContext = (tool) => ({
|
|
94
99
|
space: emptySpace(),
|
|
@@ -100,7 +105,7 @@ var createMcpServer = async ({ adapters, getGrant, preview, screenshot, logger,
|
|
|
100
105
|
});
|
|
101
106
|
const behaviorOf = (tool) => {
|
|
102
107
|
if (tool.spaceless) return (args) => tool.execute(args, spacelessContext(tool.name));
|
|
103
|
-
if (hasSpace) return async (args) => tool.execute(args, await toolContext(tool
|
|
108
|
+
if (hasSpace) return async (args, signal) => tool.execute(args, await toolContext(tool, signal));
|
|
104
109
|
return tool.executePublic ? (args) => tool.executePublic?.(args, MCP_ENV) : void 0;
|
|
105
110
|
};
|
|
106
111
|
const listed = tools.flatMap((tool) => {
|
|
@@ -112,10 +117,10 @@ var createMcpServer = async ({ adapters, getGrant, preview, screenshot, logger,
|
|
|
112
117
|
});
|
|
113
118
|
const operationsCarried = listed.some(({ tool }) => tool.carriesOperations);
|
|
114
119
|
for (const { tool, behavior } of listed) {
|
|
115
|
-
const run = async (args) => {
|
|
120
|
+
const run = async (args, { signal }) => {
|
|
116
121
|
const start = performance.now();
|
|
117
122
|
try {
|
|
118
|
-
const result = await behavior(args);
|
|
123
|
+
const result = await behavior(args, signal);
|
|
119
124
|
log.toolCall(tool.name, args, performance.now() - start);
|
|
120
125
|
return isCallToolResult(result) ? result : asText(result);
|
|
121
126
|
} catch (error) {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { ToolContext } from './shared/tool';
|
|
3
|
+
import { SpaceActivityQuery } from '@plitzi/sdk-shared/activity';
|
|
4
|
+
export declare const activityShape: {
|
|
5
|
+
after: z.ZodOptional<z.ZodString>;
|
|
6
|
+
wait: z.ZodOptional<z.ZodNumber>;
|
|
7
|
+
limit: z.ZodOptional<z.ZodNumber>;
|
|
8
|
+
mine: z.ZodOptional<z.ZodBoolean>;
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* What happened on the space after a cursor — the draft changed, a version went out, an action failed — waited for when
|
|
12
|
+
* nothing has. How an agent follows a space it works on with others: it learns of a person's edit in the builder, or a
|
|
13
|
+
* run its flow broke, without reading the space again and again. Its own writes are left out, and counted.
|
|
14
|
+
*/
|
|
15
|
+
export declare const activity: (input: SpaceActivityQuery, ctx: ToolContext) => Promise<unknown>;
|
|
16
|
+
export declare const activityTool: import('.').ToolDef;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { defineTool } from "./shared/tool.js";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { ACTIVITY_LIMIT_MAX, ACTIVITY_WAIT_DEFAULT_SECONDS, ACTIVITY_WAIT_MAX_SECONDS } from "@plitzi/sdk-shared/activity";
|
|
4
|
+
//#region src/modules/mcp/tools/activity.ts
|
|
5
|
+
var activityShape = {
|
|
6
|
+
after: z.string().optional().describe("The `cursor` last answered. Left out: the latest, at once"),
|
|
7
|
+
wait: z.number().min(0).max(ACTIVITY_WAIT_MAX_SECONDS).optional().describe(`Seconds to wait for something; default ${String(ACTIVITY_WAIT_DEFAULT_SECONDS)}`),
|
|
8
|
+
limit: z.number().int().min(1).max(ACTIVITY_LIMIT_MAX).optional(),
|
|
9
|
+
mine: z.boolean().optional().describe("Include your own writes")
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* What happened on the space after a cursor — the draft changed, a version went out, an action failed — waited for when
|
|
13
|
+
* nothing has. How an agent follows a space it works on with others: it learns of a person's edit in the builder, or a
|
|
14
|
+
* run its flow broke, without reading the space again and again. Its own writes are left out, and counted.
|
|
15
|
+
*/
|
|
16
|
+
var activity = async (input, ctx) => {
|
|
17
|
+
if (!ctx.activity) return {
|
|
18
|
+
read: false,
|
|
19
|
+
reason: "This deployment keeps no activity for its spaces"
|
|
20
|
+
};
|
|
21
|
+
return ctx.activity({
|
|
22
|
+
...input,
|
|
23
|
+
wait: input.after === void 0 ? 0 : input.wait ?? ACTIVITY_WAIT_DEFAULT_SECONDS
|
|
24
|
+
});
|
|
25
|
+
};
|
|
26
|
+
var activityTool = defineTool({
|
|
27
|
+
name: "plitzi_activity",
|
|
28
|
+
title: "Space activity",
|
|
29
|
+
description: "What happened on the space since you last looked — an edit by a person or another agent, a version out, an action failing — waited for. Start without `after`, then pass back `cursor`.",
|
|
30
|
+
inputShape: activityShape,
|
|
31
|
+
access: "read",
|
|
32
|
+
documents: false,
|
|
33
|
+
run: (input, ctx) => activity(input, ctx)
|
|
34
|
+
});
|
|
35
|
+
//#endregion
|
|
36
|
+
export { activity, activityShape, activityTool };
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { dataFileUri, dataUri, functionFileUri, functionsUri } from "../../helpers/uris.js";
|
|
2
|
+
import { defineTool, imageResult } from "../shared/tool.js";
|
|
2
3
|
import { changedResources, conflictMessage, detectConflicts, resolvedElements } from "./writeResult.js";
|
|
3
4
|
import { environment, operations } from "../operations/index.js";
|
|
4
5
|
import { draftBatch } from "../shared/draftBatch.js";
|
|
5
6
|
import { effectsOf } from "../shared/effects.js";
|
|
6
7
|
import { fullPage, lookAt, pageRef, viewport } from "../shared/look.js";
|
|
7
8
|
import { AGAIN, REPEATED, batchKey, forgetRefused, noteRefused, timesRefused } from "../shared/repeats.js";
|
|
8
|
-
import { defineTool, imageResult } from "../shared/tool.js";
|
|
9
9
|
import { z } from "zod";
|
|
10
10
|
import { generateCache } from "@plitzi/sdk-style/StyleHelper";
|
|
11
11
|
//#region src/modules/mcp/tools/apply/index.ts
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { OPERATIONS_BY_TYPE } from "./operations/index.js";
|
|
2
1
|
import { defineTool } from "./shared/tool.js";
|
|
2
|
+
import { OPERATIONS_BY_TYPE } from "./operations/index.js";
|
|
3
3
|
import { closest } from "@plitzi/sdk-authoring";
|
|
4
4
|
import { z } from "zod";
|
|
5
5
|
//#region src/modules/mcp/tools/describeOperation.ts
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { activityTool } from "./activity.js";
|
|
1
2
|
import { operation } from "./operations/index.js";
|
|
2
3
|
import "./shared/validator/index.js";
|
|
3
4
|
import { apply, applyShape, applyTool } from "./apply/index.js";
|
|
@@ -23,7 +24,8 @@ var tools = [
|
|
|
23
24
|
readTool,
|
|
24
25
|
renderTool,
|
|
25
26
|
lookTool,
|
|
26
|
-
tryFunctionTool
|
|
27
|
+
tryFunctionTool,
|
|
28
|
+
activityTool
|
|
27
29
|
];
|
|
28
30
|
//#endregion
|
|
29
31
|
export { apply, applyShape, operation, read, readShape, search, searchShape, tools };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { findElementByRef } from "../helpers/space.js";
|
|
2
|
-
import { scalar } from "./operations/schema/shared.js";
|
|
3
2
|
import { defineTool } from "./shared/tool.js";
|
|
3
|
+
import { scalar } from "./operations/schema/shared.js";
|
|
4
4
|
import { apply } from "./apply/index.js";
|
|
5
5
|
import { closest } from "@plitzi/sdk-authoring";
|
|
6
6
|
import { z } from "zod";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { fullPage, lookAt, pageRef, viewport } from "./shared/look.js";
|
|
2
1
|
import { defineTool, imageResult } from "./shared/tool.js";
|
|
2
|
+
import { fullPage, lookAt, pageRef, viewport } from "./shared/look.js";
|
|
3
3
|
import { z } from "zod";
|
|
4
4
|
//#region src/modules/mcp/tools/look.ts
|
|
5
5
|
var lookShape = {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { iconFontCss } from "../apps/render/styles.js";
|
|
2
2
|
import { RENDER_APP_URI } from "../apps/render/index.js";
|
|
3
|
-
import { compactDocumentOperations } from "./operations/index.js";
|
|
4
3
|
import { defineTool } from "./shared/tool.js";
|
|
4
|
+
import { compactDocumentOperations } from "./operations/index.js";
|
|
5
5
|
import { render, renderWidgetShape } from "./renderWidget.js";
|
|
6
6
|
import { randomUUID } from "node:crypto";
|
|
7
7
|
import { z } from "zod";
|
|
@@ -5,6 +5,7 @@ import { PreviewClient, ScreenshotClient, ScreenshotImage, Env, Persisters } fro
|
|
|
5
5
|
import { McpUiToolMeta } from '@modelcontextprotocol/ext-apps';
|
|
6
6
|
import { CallToolResult } from '@modelcontextprotocol/sdk/types.js';
|
|
7
7
|
import { ActionRunReport } from '@plitzi/sdk-shared';
|
|
8
|
+
import { SpaceActivityPage, SpaceActivityQuery } from '@plitzi/sdk-shared/activity';
|
|
8
9
|
/** Everything a tool needs at call time: the loaded space, the target environment, and the persisters (only the
|
|
9
10
|
* write tools use them). Built by whoever hosts the tools — the MCP server, or a host calling them in-process — so
|
|
10
11
|
* a tool's behavior never touches spaceId resolution or adapters directly. `spaceId`, `preview` and `screenshot`
|
|
@@ -23,6 +24,8 @@ export interface ToolContext {
|
|
|
23
24
|
/** Runs one task of the space's functions in the sandbox, for the person behind the connection — when the host
|
|
24
25
|
* wired it. Only plitzi_try_function uses it. */
|
|
25
26
|
tryFunction?: (task: string, params: Record<string, unknown>) => Promise<ActionRunReport>;
|
|
27
|
+
/** What happened on the space after a cursor, waited for — when the host wired it. Only plitzi_activity uses it. */
|
|
28
|
+
activity?: (query: SpaceActivityQuery) => Promise<SpaceActivityPage>;
|
|
26
29
|
}
|
|
27
30
|
/** Build the MCP CallToolResult for an image-returning tool: a text meta block followed by one image block per
|
|
28
31
|
* PNG, so a vision-capable agent (or MCP client) sees the render directly. */
|
|
@@ -47,6 +50,9 @@ export interface ToolSpec<Shape extends ZodRawShape> {
|
|
|
47
50
|
/** The tool operates on no space (plitzi_render authors a throwaway one), so the host must NOT resolve a spaceId
|
|
48
51
|
* or load a space for it — that keeps it callable with no auth, on the public surface. */
|
|
49
52
|
spaceless?: boolean;
|
|
53
|
+
/** The tool reads none of the space's documents (plitzi_activity): the host resolves the space but loads nothing of
|
|
54
|
+
* it, and the context's `space` is the empty placeholder. A call that waits must not read the whole space first. */
|
|
55
|
+
documents?: false;
|
|
50
56
|
/** MCP Apps: link this tool to an interactive UI resource (a `ui://` HTML page the host renders in a sandboxed
|
|
51
57
|
* iframe, receiving the tool result). Registered as the tool's `_meta.ui` — see registerAppTool. */
|
|
52
58
|
ui?: McpUiToolMeta;
|
|
@@ -68,6 +74,7 @@ export interface ToolDef {
|
|
|
68
74
|
carriesOperations?: boolean;
|
|
69
75
|
compactInputShape?: ZodRawShape;
|
|
70
76
|
spaceless?: boolean;
|
|
77
|
+
documents?: false;
|
|
71
78
|
ui?: McpUiToolMeta;
|
|
72
79
|
execute: (args: unknown, ctx: ToolContext) => unknown;
|
|
73
80
|
/** Present when the tool degrades to a space-less connection (see ToolSpec.runPublic); its absence is what tells
|
|
@@ -25,6 +25,7 @@ var defineTool = (spec) => {
|
|
|
25
25
|
carriesOperations: spec.carriesOperations,
|
|
26
26
|
compactInputShape: spec.compactInputShape,
|
|
27
27
|
spaceless: spec.spaceless,
|
|
28
|
+
documents: spec.documents,
|
|
28
29
|
ui: spec.ui,
|
|
29
30
|
execute: (args, ctx) => spec.run(parse(args), ctx),
|
|
30
31
|
...runPublic ? { executePublic: (args, env) => runPublic(parse(args), env) } : {}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@plitzi/sdk-mcp",
|
|
3
|
-
"version": "0.38.
|
|
3
|
+
"version": "0.38.16",
|
|
4
4
|
"license": "AGPL-3.0",
|
|
5
5
|
"files": [
|
|
6
6
|
"dist",
|
|
@@ -35,12 +35,12 @@
|
|
|
35
35
|
"dependencies": {
|
|
36
36
|
"@modelcontextprotocol/ext-apps": "^1.7.5",
|
|
37
37
|
"@modelcontextprotocol/sdk": "^1.32.0",
|
|
38
|
-
"@plitzi/plitzi-sdk": "0.38.
|
|
39
|
-
"@plitzi/sdk-authoring": "0.38.
|
|
40
|
-
"@plitzi/sdk-schema": "0.38.
|
|
41
|
-
"@plitzi/sdk-server": "0.38.
|
|
42
|
-
"@plitzi/sdk-shared": "0.38.
|
|
43
|
-
"@plitzi/sdk-style": "0.38.
|
|
38
|
+
"@plitzi/plitzi-sdk": "0.38.16",
|
|
39
|
+
"@plitzi/sdk-authoring": "0.38.16",
|
|
40
|
+
"@plitzi/sdk-schema": "0.38.16",
|
|
41
|
+
"@plitzi/sdk-server": "0.38.16",
|
|
42
|
+
"@plitzi/sdk-shared": "0.38.16",
|
|
43
|
+
"@plitzi/sdk-style": "0.38.16",
|
|
44
44
|
"ejs": "^6.0.1",
|
|
45
45
|
"esbuild": "^0.28.2",
|
|
46
46
|
"zod": "^4.6.5"
|