@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 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 everything and is refused at every write tool.
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: its token is a read-only (render) credential, or 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.";
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.15"
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.name));
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.15",
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.15",
39
- "@plitzi/sdk-authoring": "0.38.15",
40
- "@plitzi/sdk-schema": "0.38.15",
41
- "@plitzi/sdk-server": "0.38.15",
42
- "@plitzi/sdk-shared": "0.38.15",
43
- "@plitzi/sdk-style": "0.38.15",
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"