@plitzi/sdk-mcp 0.38.14 → 0.38.15

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,115 @@
1
1
  # @plitzi/sdk-mcp
2
2
 
3
+ ## 0.38.15
4
+
5
+ ### Patch Changes
6
+
7
+ - 6cdd5cf: ## Security
8
+
9
+ - **A write another site caused is refused, cookie or none** (`@plitzi/sdk-server`, `@plitzi/sdk-shared`): the page
10
+ server guarded nothing it answers past `/auth` — a server action (`/_action`), a realtime publish, a space's `/fn/`
11
+ route, its runtime's endpoints, the deployment's own `data` stages, the adapter-only `loginPath`, `logoutPath` and
12
+ `exchangePath`. A plain `text/plain` POST from any page open in the same browser ran them, with the visitor's session
13
+ cookie when there was one (`SameSite=None` off this machine) and, in a project nobody signs in to, as whoever holds
14
+ the server's own credentials. Every unsafe request a browser says another site caused (`Sec-Fetch-Site: cross-site`,
15
+ or a foreign `Origin`) is now refused `403 reason: 'foreign'`, saying how to allow it: `crossSiteWrites.origins`
16
+ (with `auth`, `identity.platformOrigins` too) and `crossSiteWrites.paths` for a form other sites post to by design.
17
+ `Authorization`, and callers that are not browsers — webhooks, scripts — pass as before. One rule for this and the
18
+ sign-in check (`crossSiteRequest`, `csrf.ts`). A self-hosted runtime's endpoints now sit where the platform forwards a
19
+ space's — after the auth chain and this guard (`data`, was `preAuth`).
20
+
21
+ ## Testing
22
+ - **`page check` and `page shot` anywhere the project is served** (`@plitzi/cli`): `--url <origin>` for a second server
23
+ of it, staging or a deploy, instead of `npm start`'s port; `page check --space <module>` holds the pages to that space
24
+ instead of the project's — an agent's page — authored with `authorIsolated`.
25
+ - **A route with a query is checked as its page** (`@plitzi/cli`): `page check "/?v=2"` matched the query as part of
26
+ the path, found no page, and checked the space's "not found" page in its place.
27
+
28
+ - **What an element that chooses its children draws is checked** (`@plitzi/sdk-authoring`, `@plitzi/cli`): with
29
+ `choosesChildren`, the page checks owed none of its children — and checked none of them either: a dashboard drawing
30
+ three panels reported `checked: 1`. Each child it may draw, and what is inside one under no condition of its own,
31
+ now carries `shownWith` (the child it comes with) on its handle: once that child is on screen it and everything
32
+ named in it are owed like the rest of the page, and `checked` counts them. `onScreenIfChosen(handles, page)` lists
33
+ them.
34
+ - **A page that met a server error is looked at once more** (`@plitzi/cli`): `page check` and `verify` failed a page
35
+ for a `500` from somebody else's service having a bad second (`/fn/<space>/weather`), and passed on the next run with
36
+ nothing changed. A page whose requests got a `5xx` is checked again once; its report is the second look, and says
37
+ what failed the first time (`retriedAfter`). A `4xx` fails the same way twice, and is never retried.
38
+
39
+ ## Plugins
40
+ - **`useNotify`** (`@plitzi/plitzi-sdk`): a plugin shows the page's notifications — `notify('Deleted 79 recordings', {
41
+ appearance: 'danger' })` — the same toasts an `addNotification` step shows, in the space's theme and `notifications`
42
+ look, with no trigger, flow or `window` event of the space's to wire. The step's vocabulary (`NOTIFICATION_PLACEMENTS`,
43
+ `NOTIFICATION_APPEARANCES`, `NOTIFICATION_DEFAULTS`) is one list in `@plitzi/sdk-shared`, read by both.
44
+ - **`appearance: 'danger'` looks like one** (`@plitzi/plitzi-sdk`): it reached the toast library as a type it does not
45
+ know, so a danger notification showed neither the error icon nor the colour `notifications.danger` sets.
46
+ A value a notification does not take — from a flow, or a plugin in plain JavaScript — is set aside by name and said
47
+ in the console, rather than shown as the default in silence (`readNotificationOptions`, `@plitzi/sdk-shared`).
48
+ - **`useServerAction`** (`@plitzi/plitzi-sdk`, `@plitzi/sdk-authoring`, `@plitzi/sdk-interactions`,
49
+ `@plitzi/sdk-server`): a plugin runs one of the space's server actions, awaited —
50
+ `useServerAction<typeof habitDone>('habits-done')({ id })` answers `{ ok, output }` or `{ ok: false, reason, error }`
51
+ — through the `runServerAction` step itself: the same access, refusals, session handling and dev-tools entry.
52
+ `defineAction` keeps the literal types of its `call` way in, so the input is typed by the fields it declares (the
53
+ required ones required); `taskAction` types both ends from the task's `run`, and `FunctionTask<T, R>` takes what
54
+ `run` answers. `CallInputOf` / `CallOutputOf` read them. One operation is one action, never a `case` in one route.
55
+ What a run answers is typed (`ServerActionStatus`): a status the server never uses reads as a run that did not
56
+ complete, never as one that did.
57
+ The key those types ride under (`CALL_TYPES`) is exported, by `@plitzi/sdk-shared` and `@plitzi/sdk-authoring` with
58
+ `TypedActionEntry`, `CallInputOf` and `CallOutputOf`: a package that emits declarations could not write the type of an
59
+ action it exports (`TS4023` on every `export const actions = [defineAction(…)]`).
60
+ - **A plugin is drawn on the server first** (docs): a preference per device belongs in kept state (`keepState`,
61
+ `paintedState`) as a bound prop, never read from `localStorage` while rendering — React discards the whole tree on the
62
+ mismatch, and only in the browser of whoever saved something. `@plitzi/plitzi-ui` is not the page's: a plugin
63
+ importing it bundles its own copy, with its own contexts and its own look. The authoring skill's `plugin-reach.md`
64
+ says what a plugin says and to whom: a notification, another element (an event and the space's flow), a server
65
+ action, shared state, other pages.
66
+
67
+ ## Realtime
68
+ - **A channel that retains** (`@plitzi/sdk-shared`, `@plitzi/sdk-server`, `@plitzi/sdk-elements`): declared
69
+ `retain: true` (with `publish: 'server'`, which authoring requires), the server keeps the latest message of each type
70
+ on a topic — said by `ctx.publish` or a flow's `realtime.publish` — and hands them to every page that joins, marked
71
+ `retained`. A page reads them as `<channel>.retained.<type>`, a plugin as `useChannel(topic).retained`, the newest of
72
+ a type holding whichever order they arrive in; `null` forgets a type. Kept in the store the actions' `kv` uses (beside
73
+ the grants, by compare-and-swap), so every replica hands out the same; a topic keeps at most 64 types and 512 KB, and
74
+ a publish past that is refused before anything is sent. State every screen shares is one publish and one binding —
75
+ no kv key, initial read or copy in `state` of the project's own. The fleet store `realtime.grants` is now
76
+ `realtime.kv`.
77
+ - **The MCP writes every field a channel takes** (`@plitzi/sdk-mcp`): `patchSettings`'s channel had no `grant` — which
78
+ the guide taught — nor `retain` or the limits, so an agent could not declare a private channel at all. All are there
79
+ now, and the guide says what `retain` is for and that `channel_<id>.retained` holds it.
80
+
81
+ ## Authoring
82
+ - **`plitzi explain` knows every `@plitzi` package installed** (`@plitzi/cli`): a name that was not
83
+ `@plitzi/sdk-authoring`'s (`createServer`, `createJsonAdapters`, `useChannel`) was answered as the project's own code,
84
+ to `grep` in `src/`. It is read from the published types of every one installed, through their re-exports and entry
85
+ points (`@plitzi/sdk-server/handlers`), and a name two packages export — `createServer` is sdk-server's and sdk-mcp's
86
+ — is answered with both.
87
+
88
+ - **A space module somebody else wrote, authored where it reaches nothing** (`@plitzi/sdk-authoring`):
89
+ `authorIsolated(file, { timeoutMs })` of `@plitzi/sdk-authoring/node` runs `authorSpace` on it in a process Node's
90
+ permission model holds to reading the module's folder and its packages — no file written, no process started, no
91
+ variable of the caller's environment — kills one that runs too long, and validates what it answers again, so a forged
92
+ answer gains nothing. A refusal comes back as `authorSpace` says it, with the problems as data.
93
+ - **Pages an agent writes, served** (`@plitzi/sdk-server`): `createAuthoringHost({ dir, frameAncestors })` serves each
94
+ space it is handed at an address of its own (`put(space, { id }) → /?__pt=<id>`) as a draft of its one space, kept in
95
+ `dir` across restarts; `drop(id)` forgets one, and anything else answers `404` saying where the pages are.
96
+ - **A space is chosen by host or credential** (`@plitzi/sdk-server`, docs): `getSpaceDeployment` is asked on every
97
+ request, and what a page fetches after it carries nothing of its address but the host — a space chosen by the page's
98
+ query or path lost everything it asked for, and `/_plitzi/space/<hash>.json` answered a bare `404`. The contract and
99
+ the README say so (the README offered "by path prefix"), and that `404` says why.
100
+
101
+ - **`plitzi explain on` says how to read what a trigger carries** (`@plitzi/sdk-authoring`): its doc sat above a
102
+ helper, so `explain` printed only the signature. It now leads with `[named('picked', on('onPick')), …('{{ picked.seat
103
+ }}')]`, and the CLI skill's plugin section says the same.
104
+
105
+ - Updated dependencies [6cdd5cf]
106
+ - @plitzi/plitzi-sdk@0.38.15
107
+ - @plitzi/sdk-server@0.38.15
108
+ - @plitzi/sdk-authoring@0.38.15
109
+ - @plitzi/sdk-schema@0.38.15
110
+ - @plitzi/sdk-shared@0.38.15
111
+ - @plitzi/sdk-style@0.38.15
112
+
3
113
  ## 0.38.14
4
114
 
5
115
  ### Patch Changes
@@ -797,7 +797,7 @@ Pages that see each other — cursors, presence, a shared board, a game. Three p
797
797
  \`publish: "server"\` is for what must be validated and saved first; \`"clients"\` (the default) for what pages send
798
798
  directly — add \`presence: true\` for who-is-here.
799
799
  2. **Subscribe** with a \`channel\` element whose \`topic\` a pattern matches (\`board:{{ id }}\`, a route param).
800
- Its descendants bind \`channel_<id>\`: \`connected\`, \`members\`, \`messages\`, \`last\`; flows use its \`onMessage\`,
800
+ Its descendants bind \`channel_<id>\`: \`connected\`, \`members\`, \`messages\`, \`last\`, \`retained\`; flows use its \`onMessage\`,
801
801
  \`onJoin\`, \`onLeave\` triggers (\`from\`, \`user\` and the \`state\` the member announced — \`onJoin\` only for who
802
802
  came after this page) and its \`publish\` / \`setPresence\` callbacks.
803
803
  3. **Announce from the server**: an action whose last step is the \`realtime.publish\` task — validate, save, then say so.
@@ -809,6 +809,11 @@ decides the visitor may be there ends with \`realtime.grant { topic }\` and answ
809
809
  element binds \`grant\` to it. Without one the topic is refused however well its name is known (\`channel-grant\`).
810
810
  \`realtime.revoke { topic, grant }\` takes one back — or all, naming none — and lets go whoever is on it with it.
811
811
 
812
+ **State every screen shares** (a dashboard's lists, a board's settings): declare the pattern \`retain: true\` with
813
+ \`publish: "server"\`. The server keeps the latest message of each type it says (\`realtime.publish\`) and hands them to
814
+ every page that joins — and to its \`onMessage\` flows, as if just said; the page binds \`desk.retained.lists\`, with no
815
+ read of its own and no copy in \`state\`. \`data: null\` forgets a type; a topic keeps at most 64 types and 512 KB.
816
+
812
817
  **Two writers at once**: read-change-write with \`kv.setIf\` (written only if the value is still the one read — empty
813
818
  \`expected\` = only if nothing is there yet), ordered lists with \`list.put\` / \`list.range\`, and put
814
819
  \`flow.rateLimit\` first in any public action that writes.
@@ -72,7 +72,7 @@ var createMcpServer = async ({ adapters, getGrant, preview, screenshot, logger,
72
72
  const getSpace = () => spacePromise ??= loadSpace();
73
73
  const server = new McpServer({
74
74
  name: "plitzi-mcp",
75
- version: "0.38.14"
75
+ version: "0.38.15"
76
76
  }, { instructions: hasSpace ? serverInstructions : widgetsOnlyInstructions });
77
77
  const { getChanges } = adapters;
78
78
  registerResources(server, getSpace, MCP_ENV, log, hasSpace, getChanges ? (query) => getChanges(requireSpaceId(), MCP_ENV, query) : void 0);
@@ -292,6 +292,10 @@ export declare const applyShape: {
292
292
  clients: "clients";
293
293
  }>>;
294
294
  presence: z.ZodOptional<z.ZodBoolean>;
295
+ grant: z.ZodOptional<z.ZodBoolean>;
296
+ retain: z.ZodOptional<z.ZodBoolean>;
297
+ maxMessageBytes: z.ZodOptional<z.ZodNumber>;
298
+ messagesPerSecond: z.ZodOptional<z.ZodNumber>;
295
299
  }, z.core.$strip>>>>;
296
300
  }, z.core.$strict>, z.ZodObject<{
297
301
  type: z.ZodLiteral<"upsertComponent">;
@@ -288,6 +288,10 @@ export declare const documentOperation: z.ZodDiscriminatedUnion<readonly [z.ZodO
288
288
  clients: "clients";
289
289
  }>>;
290
290
  presence: z.ZodOptional<z.ZodBoolean>;
291
+ grant: z.ZodOptional<z.ZodBoolean>;
292
+ retain: z.ZodOptional<z.ZodBoolean>;
293
+ maxMessageBytes: z.ZodOptional<z.ZodNumber>;
294
+ messagesPerSecond: z.ZodOptional<z.ZodNumber>;
291
295
  }, z.core.$strip>>>>;
292
296
  }, z.core.$strict>, z.ZodObject<{
293
297
  type: z.ZodLiteral<"upsertComponent">;
@@ -1710,6 +1714,10 @@ export declare const operation: z.ZodDiscriminatedUnion<[z.ZodObject<{
1710
1714
  clients: "clients";
1711
1715
  }>>;
1712
1716
  presence: z.ZodOptional<z.ZodBoolean>;
1717
+ grant: z.ZodOptional<z.ZodBoolean>;
1718
+ retain: z.ZodOptional<z.ZodBoolean>;
1719
+ maxMessageBytes: z.ZodOptional<z.ZodNumber>;
1720
+ messagesPerSecond: z.ZodOptional<z.ZodNumber>;
1713
1721
  }, z.core.$strip>>>>;
1714
1722
  }, z.core.$strict>, z.ZodObject<{
1715
1723
  type: z.ZodLiteral<"upsertComponent">;
@@ -3411,6 +3419,10 @@ export declare const operations: z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject
3411
3419
  clients: "clients";
3412
3420
  }>>;
3413
3421
  presence: z.ZodOptional<z.ZodBoolean>;
3422
+ grant: z.ZodOptional<z.ZodBoolean>;
3423
+ retain: z.ZodOptional<z.ZodBoolean>;
3424
+ maxMessageBytes: z.ZodOptional<z.ZodNumber>;
3425
+ messagesPerSecond: z.ZodOptional<z.ZodNumber>;
3414
3426
  }, z.core.$strip>>>>;
3415
3427
  }, z.core.$strict>, z.ZodObject<{
3416
3428
  type: z.ZodLiteral<"upsertComponent">;
@@ -5107,6 +5119,10 @@ export declare const documentOperations: z.ZodArray<z.ZodDiscriminatedUnion<read
5107
5119
  clients: "clients";
5108
5120
  }>>;
5109
5121
  presence: z.ZodOptional<z.ZodBoolean>;
5122
+ grant: z.ZodOptional<z.ZodBoolean>;
5123
+ retain: z.ZodOptional<z.ZodBoolean>;
5124
+ maxMessageBytes: z.ZodOptional<z.ZodNumber>;
5125
+ messagesPerSecond: z.ZodOptional<z.ZodNumber>;
5110
5126
  }, z.core.$strip>>>>;
5111
5127
  }, z.core.$strict>, z.ZodObject<{
5112
5128
  type: z.ZodLiteral<"upsertComponent">;
@@ -307,6 +307,10 @@ export declare const elementOps: {
307
307
  clients: "clients";
308
308
  }>>;
309
309
  presence: import('zod').ZodOptional<import('zod').ZodBoolean>;
310
+ grant: import('zod').ZodOptional<import('zod').ZodBoolean>;
311
+ retain: import('zod').ZodOptional<import('zod').ZodBoolean>;
312
+ maxMessageBytes: import('zod').ZodOptional<import('zod').ZodNumber>;
313
+ messagesPerSecond: import('zod').ZodOptional<import('zod').ZodNumber>;
310
314
  }, import('zod/v4/core').$strip>>>>;
311
315
  }, import('zod/v4/core').$strip>;
312
316
  upsertComponent: import('zod').ZodObject<{
@@ -48,6 +48,10 @@ export declare const patchSettingsOp: z.ZodObject<{
48
48
  clients: "clients";
49
49
  }>>;
50
50
  presence: z.ZodOptional<z.ZodBoolean>;
51
+ grant: z.ZodOptional<z.ZodBoolean>;
52
+ retain: z.ZodOptional<z.ZodBoolean>;
53
+ maxMessageBytes: z.ZodOptional<z.ZodNumber>;
54
+ messagesPerSecond: z.ZodOptional<z.ZodNumber>;
51
55
  }, z.core.$strip>>>>;
52
56
  }, z.core.$strip>;
53
57
  export type PatchSettings = z.infer<typeof patchSettingsOp>;
@@ -20,7 +20,11 @@ var channel = z.object({
20
20
  permissions: z.array(z.string()).optional()
21
21
  }),
22
22
  publish: z.enum(["clients", "server"]).optional(),
23
- presence: z.boolean().optional()
23
+ presence: z.boolean().optional(),
24
+ grant: z.boolean().optional().describe("A page needs a grant from realtime.grant to open a topic"),
25
+ retain: z.boolean().optional().describe("The server keeps the latest message of each type for pages that join"),
26
+ maxMessageBytes: z.number().int().positive().optional(),
27
+ messagesPerSecond: z.number().int().positive().optional()
24
28
  });
25
29
  var notifications = z.record(z.string(), z.string().nullable());
26
30
  var patchSettingsOp = z.object({
@@ -290,6 +290,10 @@ export declare const renderShape: {
290
290
  clients: "clients";
291
291
  }>>;
292
292
  presence: z.ZodOptional<z.ZodBoolean>;
293
+ grant: z.ZodOptional<z.ZodBoolean>;
294
+ retain: z.ZodOptional<z.ZodBoolean>;
295
+ maxMessageBytes: z.ZodOptional<z.ZodNumber>;
296
+ messagesPerSecond: z.ZodOptional<z.ZodNumber>;
293
297
  }, z.core.$strip>>>>;
294
298
  }, z.core.$strict>, z.ZodObject<{
295
299
  type: z.ZodLiteral<"upsertComponent">;
@@ -295,6 +295,10 @@ export declare const renderWidgetShape: {
295
295
  clients: "clients";
296
296
  }>>;
297
297
  presence: import('zod').ZodOptional<import('zod').ZodBoolean>;
298
+ grant: import('zod').ZodOptional<import('zod').ZodBoolean>;
299
+ retain: import('zod').ZodOptional<import('zod').ZodBoolean>;
300
+ maxMessageBytes: import('zod').ZodOptional<import('zod').ZodNumber>;
301
+ messagesPerSecond: import('zod').ZodOptional<import('zod').ZodNumber>;
298
302
  }, import('zod/v4/core').$strip>>>>;
299
303
  }, import('zod/v4/core').$strict>, import('zod').ZodObject<{
300
304
  type: import('zod').ZodLiteral<"upsertComponent">;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@plitzi/sdk-mcp",
3
- "version": "0.38.14",
3
+ "version": "0.38.15",
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.14",
39
- "@plitzi/sdk-authoring": "0.38.14",
40
- "@plitzi/sdk-schema": "0.38.14",
41
- "@plitzi/sdk-server": "0.38.14",
42
- "@plitzi/sdk-shared": "0.38.14",
43
- "@plitzi/sdk-style": "0.38.14",
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",
44
44
  "ejs": "^6.0.1",
45
45
  "esbuild": "^0.28.2",
46
46
  "zod": "^4.6.5"