@ggui-ai/mcp-server-handlers 0.17.0 → 0.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -117,6 +117,8 @@ declare const outputSchema: {
117
117
  overlayHash: z.ZodLiteral<"mismatch">;
118
118
  }, z.core.$strict>, z.ZodObject<{
119
119
  refused: z.ZodLiteral<"v1 shape">;
120
+ }, z.core.$strict>, z.ZodObject<{
121
+ wouldDrop: z.ZodArray<z.ZodString>;
120
122
  }, z.core.$strict>]>>;
121
123
  /** Present on a shape refusal the schema itself raised — its messages, one per issue. */
122
124
  readonly issues: z.ZodOptional<z.ZodArray<z.ZodString>>;
@@ -271,6 +273,8 @@ export declare function createSetAppThemeHandler(deps: SetAppThemeDeps): import(
271
273
  overlayHash: z.ZodLiteral<"mismatch">;
272
274
  }, z.core.$strict>, z.ZodObject<{
273
275
  refused: z.ZodLiteral<"v1 shape">;
276
+ }, z.core.$strict>, z.ZodObject<{
277
+ wouldDrop: z.ZodArray<z.ZodString>;
274
278
  }, z.core.$strict>]>>;
275
279
  /** Present on a shape refusal the schema itself raised — its messages, one per issue. */
276
280
  readonly issues: z.ZodOptional<z.ZodArray<z.ZodString>>;
@@ -332,6 +336,8 @@ export declare function createSetAppThemeHandler(deps: SetAppThemeDeps): import(
332
336
  overlayHash: "mismatch";
333
337
  } | {
334
338
  refused: "v1 shape";
339
+ } | {
340
+ wouldDrop: string[];
335
341
  } | undefined;
336
342
  issues?: string[] | undefined;
337
343
  }>>;
@@ -1 +1 @@
1
- {"version":3,"file":"set-app-theme.d.ts","sourceRoot":"","sources":["../../src/ops-apps/set-app-theme.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,OAAO,EAIL,KAAK,QAAQ,EACb,KAAK,mBAAmB,EACzB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAIL,KAAK,mBAAmB,EACzB,MAAM,aAAa,CAAC;AAGrB,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAE7C;;;;GAIG;AACH,MAAM,MAAM,wBAAwB,GAAG,CAAC,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,KAAK;IACpF,QAAQ,CAAC,SAAS,EAAE,SAAS,MAAM,EAAE,CAAC;IACtC,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IACpC,QAAQ,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAC;CACtC,CAAC;AAcF,QAAA,MAAM,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAKhB,mEAAmE;;IAEnE,sGAAsG;;;;;;;;;;;;;;;;IAEtG,yFAAyF;;CAEjF,CAAC;AAEX,uEAAuE;AACvE,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC,OAAO,YAAY,CAAC,CAAC,CAAC;AAE1E,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;IAC1B,8FAA8F;IAC9F,QAAQ,CAAC,eAAe,EAAE,wBAAwB,CAAC;CACpD;AAED,0EAA0E;AAC1E,MAAM,MAAM,iBAAiB,GACzB;IAAE,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,QAAQ,CAAA;CAAE,GAC/C;IAAE,QAAQ,CAAC,EAAE,EAAE,KAAK,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,mBAAmB,CAAA;CAAE,GAC7D;IAAE,QAAQ,CAAC,EAAE,EAAE,KAAK,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAA;CAAE,CAAC;AAY/D;;;;;GAKG;AACH,wBAAsB,aAAa,CACjC,GAAG,EAAE,OAAO,EACZ,eAAe,EAAE,wBAAwB,GACxC,OAAO,CAAC,iBAAiB,CAAC,CAsB5B;AAWD,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IA3E5D,mEAAmE;;IAEnE,sGAAsG;;;;;;;;;;;;;;;;IAEtG,yFAAyF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IA4G1F"}
1
+ {"version":3,"file":"set-app-theme.d.ts","sourceRoot":"","sources":["../../src/ops-apps/set-app-theme.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,OAAO,EAIL,KAAK,QAAQ,EACb,KAAK,mBAAmB,EAEzB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAIL,KAAK,mBAAmB,EACzB,MAAM,aAAa,CAAC;AAGrB,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAE7C;;;;GAIG;AACH,MAAM,MAAM,wBAAwB,GAAG,CAAC,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,KAAK;IACpF,QAAQ,CAAC,SAAS,EAAE,SAAS,MAAM,EAAE,CAAC;IACtC,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IACpC,QAAQ,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAC;CACtC,CAAC;AAcF,QAAA,MAAM,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAKhB,mEAAmE;;IAEnE,sGAAsG;;;;;;;;;;;;;;;;;;IAEtG,yFAAyF;;CAEjF,CAAC;AAEX,uEAAuE;AACvE,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC,OAAO,YAAY,CAAC,CAAC,CAAC;AAE1E,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;IAC1B,8FAA8F;IAC9F,QAAQ,CAAC,eAAe,EAAE,wBAAwB,CAAC;CACpD;AAED,0EAA0E;AAC1E,MAAM,MAAM,iBAAiB,GACzB;IAAE,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,QAAQ,CAAA;CAAE,GAC/C;IAAE,QAAQ,CAAC,EAAE,EAAE,KAAK,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,mBAAmB,CAAA;CAAE,GAC7D;IAAE,QAAQ,CAAC,EAAE,EAAE,KAAK,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAA;CAAE,CAAC;AAY/D;;;;;GAKG;AACH,wBAAsB,aAAa,CACjC,GAAG,EAAE,OAAO,EACZ,eAAe,EAAE,wBAAwB,GACxC,OAAO,CAAC,iBAAiB,CAAC,CAsB5B;AAmBD,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAnF5D,mEAAmE;;IAEnE,sGAAsG;;;;;;;;;;;;;;;;;;IAEtG,yFAAyF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAoH1F"}
@@ -31,7 +31,7 @@
31
31
  *
32
32
  * Pure over the {@link AppsSource} seam + the injected validator.
33
33
  */
34
- import { appThemeSchema, appThemeRefusalBodySchema, canonicalOverlayHash, } from '@ggui-ai/protocol';
34
+ import { appThemeSchema, appThemeRefusalBodySchema, canonicalOverlayHash, appThemeWouldDropRefusalText, } from '@ggui-ai/protocol';
35
35
  import { z } from 'zod';
36
36
  import { defineHandler, handlerFailure, } from '../types.js';
37
37
  import { resolveOwnerSub } from './identity.js';
@@ -101,6 +101,14 @@ function refusalText(admission) {
101
101
  return 'invalid_app_config: the one-palette theme body is retired — send { overlays: { light, dark }, overlayHash, … }';
102
102
  if ('overlayHash' in r)
103
103
  return 'invalid_app_config: overlayHash does not match canonicalOverlayHash({ overlays, cssVariables, keyframes })';
104
+ if ('wouldDrop' in r) {
105
+ // ggui#1124 — the write would have DESTROYED stored members it did not
106
+ // carry. The wording is the protocol's ONE text, not this handler's:
107
+ // every door that refuses a destructive write says the same words, and
108
+ // the refusal names the compliance path so it is an instruction rather
109
+ // than a verdict (founder's ruling, 2026-09-16).
110
+ return `invalid_app_config: ${appThemeWouldDropRefusalText(r.wouldDrop)}`;
111
+ }
104
112
  if ('unknown' in r)
105
113
  return `invalid_app_config: keys outside the consumed-token manifest — light: [${r.unknown.light.join(', ')}] dark: [${r.unknown.dark.join(', ')}]`;
106
114
  return `invalid_app_config: consumed tokens left uncovered — light: [${r.uncovered.light.join(', ')}] dark: [${r.uncovered.dark.join(', ')}]`;
@@ -68,7 +68,7 @@ export function createGguiConsumeHandler(deps) {
68
68
  name: 'ggui_consume',
69
69
  title: 'Consume',
70
70
  audience: ['agent'],
71
- description: 'Long-poll for buffered events on a GguiSession. CALL THIS RIGHT AFTER EVERY `ggui_render` THAT RETURNS `nextStep.tool === "ggui_consume"` — that hint is your cue to start listening for the user\'s gesture. Keyed by sessionId (global UUID); app-scoped via ctx.appId. Inline long-poll: `timeout` is an integer in [0, 25] seconds (default 0 — an instant non-blocking drain; values outside reject INVALID_PARAMS — host MCP clients abort longer tool calls; pick 5-15s typical, 25 max). Returns `{events, status}` — each event carries `{type: "action", sessionId, intent, actionData, uiContext, actionId, firedAt}`: `actionData` is WHAT the user did, `uiContext` is the iframe-local snapshot of the contract\'s contextSpec slots AT THE MOMENT they did it. Both inform your reaction without a second round trip. Returns immediately when an action event arrives OR the render completes OR the timeout elapses. On timeout with no event you may re-call once, then end your turn — a later gesture arrives as a new user message. THE LOOP: when `events` is non-empty, REACT, then re-call `ggui_consume` for the next gesture; when `events` is empty, end your turn — a later gesture arrives as a new user message carrying its own consume directive. status:"expired" means the render is gone. IMPORTANT — the iframe state is independent of your backend state: after you mutate via domain tools (todo_toggle, cart_add, etc.), the UI still shows the OLD props until you call `ggui_amend`. If the events caused observable state changes the user is looking at, your reaction MUST include `ggui_amend` somewhere before re-consuming; otherwise the user sees stale props (the #1 wire compliance bug). Pure-info events that don\'t change displayed state can skip it. `ggui_amend` repaints the SAME card in place (the default move in this loop); `ggui_update` instead renders the state as a NEW card and advances the history number — reserve it for milestones worth a card in the transcript. HOSTS WITH PROGRESSIVE TOOL DISCOVERY (claude.ai-style connectors): if a call here errors with "tool not loaded yet" or "wrong parameter names," call `tool_search({query:"ggui_consume"})` once to warm the tool, then retry with the same args. DO NOT skip the consume — silent gesture drops are the worst protocol failure.',
71
+ description: 'Long-poll for buffered events on a GguiSession. CALL THIS RIGHT AFTER EVERY `ggui_render` THAT RETURNS `nextStep.tool === "ggui_consume"` — that hint is your cue to start listening for the user\'s gesture. Keyed by sessionId (global UUID); app-scoped via ctx.appId. Inline long-poll: `timeout` is an integer in [0, 25] seconds (default 0 — an instant non-blocking drain; values outside reject INVALID_PARAMS — host MCP clients abort longer tool calls; pick 5-15s typical, 25 max). Returns `{events, status}` — each event carries `{type: "action", sessionId, intent, actionData, uiContext, actionId, firedAt}`: `actionData` is WHAT the user did, `uiContext` is the iframe-local snapshot of the contract\'s contextSpec slots AT THE MOMENT they did it. Both inform your reaction without a second round trip. Returns immediately when an action event arrives OR the render completes OR the timeout elapses. On timeout with no event you may re-call once, then end your turn — a later gesture arrives as a new user message. THE LOOP: when `events` is non-empty, REACT, then re-call `ggui_consume` for the next gesture; when `events` is empty, end your turn — a later gesture arrives as a new user message carrying its own consume directive. status:"expired" means the render is gone. IMPORTANT — the iframe state is independent of your backend state: after you mutate via domain tools (todo_toggle, cart_add, etc.), the UI still shows the OLD props until you call `ggui_amend`. If the events caused observable state changes the user is looking at, your reaction MUST include `ggui_amend` somewhere before re-consuming; otherwise the user sees stale props (the #1 wire compliance bug). Pure-info events that don\'t change displayed state can skip it. `ggui_amend` repaints the SAME card in place (the default move in this loop); `ggui_update` instead renders the state as a NEW card and advances the history number — reserve it for milestones worth a card in the transcript. HOSTS WITH PROGRESSIVE TOOL DISCOVERY (claude.ai-style connectors): if a call here errors with "tool not loaded yet" or "wrong parameter names," call `tool_search({query:"ggui_consume"})` once to warm the tool, then retry with the same args. DO NOT skip the consume — silent gesture drops are the worst protocol failure. IDEMPOTENCY: treat the `actionId` on each entry as an idempotency key and apply a side-effecting action AT MOST ONCE per `actionId` — the same id delivered twice is one gesture replayed, never two. This holds for EVERY action, not only those a contract marks `oneShot`: a card served mid-rollout may be running a runtime that suppresses nothing, and keying on the marker would leave a hole exactly there. Two DISTINCT ids are two distinct gestures and both are real.',
72
72
  inputSchema,
73
73
  outputSchema,
74
74
  async handler(rawInput, ctx) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ggui-ai/mcp-server-handlers",
3
- "version": "0.17.0",
3
+ "version": "0.18.0",
4
4
  "description": "MCP tool handler logic for the ggui protocol. Pure over @ggui-ai/mcp-server-core seams and consumed by @ggui-ai/mcp-server. Never imports AWS, HTTP transports, or CLI concerns.",
5
5
  "keywords": [
6
6
  "ggui",
@@ -70,18 +70,18 @@
70
70
  "dependencies": {
71
71
  "canonicalize": "^2.0.0",
72
72
  "zod": "^4.3.6",
73
- "@ggui-ai/mcp-server-core": "0.17.0",
74
- "@ggui-ai/negotiator": "0.17.0",
75
- "@ggui-ai/protocol": "0.17.0",
76
- "@ggui-ai/ui-gen": "0.17.0",
77
- "@ggui-ai/ui-registry": "0.17.0"
73
+ "@ggui-ai/mcp-server-core": "0.18.0",
74
+ "@ggui-ai/negotiator": "0.18.0",
75
+ "@ggui-ai/ui-gen": "0.18.0",
76
+ "@ggui-ai/protocol": "0.18.0",
77
+ "@ggui-ai/ui-registry": "0.18.0"
78
78
  },
79
79
  "devDependencies": {
80
80
  "@types/node": "^22.0.0",
81
81
  "typescript": "^5.0.0",
82
82
  "vitest": "^3.2.6",
83
- "@ggui-ai/embedding-local": "0.17.0",
84
- "@ggui-ai/protocol-conformance": "0.17.0"
83
+ "@ggui-ai/embedding-local": "0.18.0",
84
+ "@ggui-ai/protocol-conformance": "0.18.0"
85
85
  },
86
86
  "repository": {
87
87
  "type": "git",