@ggui-ai/mcp-server 0.1.0-rc.3 → 0.2.0-alpha.1

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.
Files changed (59) hide show
  1. package/README.md +1 -1
  2. package/dist/build-mcp.d.ts +8 -8
  3. package/dist/build-mcp.d.ts.map +1 -1
  4. package/dist/build-mcp.js +53 -12
  5. package/dist/console-auth.d.ts +7 -7
  6. package/dist/console-auth.d.ts.map +1 -1
  7. package/dist/console-auth.js +4 -4
  8. package/dist/console-cache.d.ts +2 -2
  9. package/dist/console-cache.d.ts.map +1 -1
  10. package/dist/console-cache.js +10 -10
  11. package/dist/console-headers.d.ts +2 -2
  12. package/dist/console-headers.js +2 -2
  13. package/dist/console-payloads.d.ts +3 -3
  14. package/dist/console-payloads.d.ts.map +1 -1
  15. package/dist/console-payloads.js +10 -10
  16. package/dist/console-timeline.d.ts +12 -30
  17. package/dist/console-timeline.d.ts.map +1 -1
  18. package/dist/console-timeline.js +44 -45
  19. package/dist/index.d.ts +7 -7
  20. package/dist/index.d.ts.map +1 -1
  21. package/dist/index.js +5 -5
  22. package/dist/instructions-presets.d.ts +1 -1
  23. package/dist/instructions-presets.d.ts.map +1 -1
  24. package/dist/instructions-presets.js +40 -22
  25. package/dist/llm-backed-negotiator.d.ts +6 -6
  26. package/dist/llm-backed-negotiator.d.ts.map +1 -1
  27. package/dist/llm-backed-negotiator.js +92 -91
  28. package/dist/mcp-apps-inbound.d.ts +3 -3
  29. package/dist/mcp-apps-inbound.d.ts.map +1 -1
  30. package/dist/mcp-apps-inbound.js +28 -23
  31. package/dist/mcp-apps-outbound.d.ts +136 -121
  32. package/dist/mcp-apps-outbound.d.ts.map +1 -1
  33. package/dist/mcp-apps-outbound.js +384 -414
  34. package/dist/mcp-mounts.d.ts +21 -20
  35. package/dist/mcp-mounts.d.ts.map +1 -1
  36. package/dist/mcp-mounts.js +25 -29
  37. package/dist/{session-channel.d.ts → render-channel.d.ts} +145 -102
  38. package/dist/render-channel.d.ts.map +1 -0
  39. package/dist/{session-channel.js → render-channel.js} +481 -462
  40. package/dist/schema-compat.d.ts +11 -11
  41. package/dist/schema-compat.d.ts.map +1 -1
  42. package/dist/schema-compat.js +6 -6
  43. package/dist/server.d.ts +224 -219
  44. package/dist/server.d.ts.map +1 -1
  45. package/dist/server.js +1498 -1963
  46. package/dist/storage.d.ts +7 -7
  47. package/dist/storage.d.ts.map +1 -1
  48. package/dist/storage.js +12 -12
  49. package/package.json +12 -11
  50. package/dist/render-gate.d.ts +0 -87
  51. package/dist/render-gate.d.ts.map +0 -1
  52. package/dist/render-gate.js +0 -77
  53. package/dist/render-rate-limit.d.ts +0 -59
  54. package/dist/render-rate-limit.d.ts.map +0 -1
  55. package/dist/render-rate-limit.js +0 -73
  56. package/dist/render-signing.d.ts +0 -98
  57. package/dist/render-signing.d.ts.map +0 -1
  58. package/dist/render-signing.js +0 -113
  59. package/dist/session-channel.d.ts.map +0 -1
@@ -39,7 +39,7 @@
39
39
  *
40
40
  * One LLM call per handshake (when creds resolve). Operators
41
41
  * concerned about cost can either (a) skip handshake and call
42
- * `ggui_push` directly with `{story}`, or (b) bind a different
42
+ * `ggui_render` directly with `{story}`, or (b) bind a different
43
43
  * negotiator (e.g., the cache-backed one for read-only cache
44
44
  * lookups) via `createGguiServer({handshake: {negotiator: ...}})`.
45
45
  *
@@ -50,11 +50,11 @@
50
50
  * same `negotiate()` pipeline, just with degraded RAG when local
51
51
  * infrastructure isn't bound.
52
52
  */
53
- import { negotiate } from '@ggui-ai/negotiator';
54
- import { DEFAULT_GENERATOR_SLUG, matchBlueprint, } from '@ggui-ai/mcp-server-handlers/session-mutations';
55
- import { createHash, randomUUID } from 'node:crypto';
56
- import { blueprintKey } from '@ggui-ai/protocol/blueprint-key';
57
- import { selectAdapter } from '@ggui-ai/ui-gen/providers';
53
+ import { DEFAULT_GENERATOR_SLUG, matchBlueprint, } from "@ggui-ai/mcp-server-handlers/renders";
54
+ import { negotiate } from "@ggui-ai/negotiator";
55
+ import { blueprintKey } from "@ggui-ai/protocol/blueprint-key";
56
+ import { selectAdapter } from "@ggui-ai/ui-gen/providers";
57
+ import { createHash, randomUUID } from "node:crypto";
58
58
  /**
59
59
  * Operational error classifier — mirrors the cloud helper. Bugs
60
60
  * surface; provider failures + network blips degrade to a "create"
@@ -96,12 +96,23 @@ function isOperationalError(err) {
96
96
  */
97
97
  export function buildLlmCaller(selection, providerKey) {
98
98
  const adapter = selectAdapter(selection.provider);
99
- const isAnthropic = selection.provider === 'anthropic';
99
+ const isAnthropic = selection.provider === "anthropic";
100
+ // `selection` IS an `LlmRoute & {inference params}` — the typed
101
+ // discriminated union means `selection.model` is already in the
102
+ // wire-canonical form the provider SDK expects. No LiteLLM-prefix
103
+ // strip happens here; if one were ever needed again, the validator
104
+ // belongs at the route construction boundary (`parseAnyLlmRoute`),
105
+ // not at the SDK-call site. This is what eliminates the #22 / #42
106
+ // bug class structurally. We pass `selection` itself as the `route`
107
+ // — re-constructing an object literal would widen the typed
108
+ // discriminator and lose the (provider, model) pairing TS needs to
109
+ // narrow against `LlmRoute`'s union.
110
+ const route = selection;
100
111
  const caller = {
101
112
  async call(systemPrompt, userMessage, maxTokens) {
102
113
  const result = await adapter.complete({
103
114
  apiKey: providerKey.key,
104
- model: selection.model,
115
+ route,
105
116
  systemPrompt,
106
117
  userPrompt: userMessage,
107
118
  ...(maxTokens !== undefined ? { maxTokens } : {}),
@@ -147,7 +158,7 @@ async function anthropicCallStructured(args) {
147
158
  // paraphrase via canonical-key normalisation rather than relying
148
159
  // on temperature=0.
149
160
  system: args.systemPrompt,
150
- messages: [{ role: 'user', content: args.userMessage }],
161
+ messages: [{ role: "user", content: args.userMessage }],
151
162
  tools: [
152
163
  {
153
164
  name: args.tool.name,
@@ -158,26 +169,26 @@ async function anthropicCallStructured(args) {
158
169
  // Forced tool use — model MUST emit exactly this tool. Without
159
170
  // this the model can drift to a text reply and synth/rerank
160
171
  // both lose their structured guarantee.
161
- tool_choice: { type: 'tool', name: args.tool.name },
172
+ tool_choice: { type: "tool", name: args.tool.name },
162
173
  };
163
- const response = await fetch('https://api.anthropic.com/v1/messages', {
164
- method: 'POST',
174
+ const response = await fetch("https://api.anthropic.com/v1/messages", {
175
+ method: "POST",
165
176
  headers: {
166
- 'x-api-key': args.apiKey,
167
- 'anthropic-version': '2023-06-01',
168
- 'content-type': 'application/json',
177
+ "x-api-key": args.apiKey,
178
+ "anthropic-version": "2023-06-01",
179
+ "content-type": "application/json",
169
180
  },
170
181
  body: JSON.stringify(body),
171
182
  });
172
183
  if (!response.ok) {
173
- const text = await response.text().catch(() => '');
184
+ const text = await response.text().catch(() => "");
174
185
  throw new Error(`anthropic tool-use HTTP ${response.status}: ${text.slice(0, 500)}`);
175
186
  }
176
187
  const json = (await response.json());
177
188
  // Find the tool_use block. With `tool_choice: {type:'tool'}` the
178
189
  // model is forced to emit exactly one; defensively scan in case the
179
190
  // shape ever shifts.
180
- const toolUse = json.content?.find((block) => block.type === 'tool_use' && block.name === args.tool.name);
191
+ const toolUse = json.content?.find((block) => block.type === "tool_use" && block.name === args.tool.name);
181
192
  if (!toolUse || toolUse.input === undefined) {
182
193
  throw new Error(`anthropic tool-use response missing tool_use block for "${args.tool.name}"`);
183
194
  }
@@ -189,17 +200,17 @@ function buildCreateFallback(draftContract, reason, _estimatedLatencyMs) {
189
200
  // agent's draft. Gen-pending; no codeHash; provisional blueprintId.
190
201
  const contractHash = blueprintKey(draftContract);
191
202
  const suggestion = {
192
- origin: 'agent',
203
+ origin: "agent",
193
204
  rationale: reason,
194
205
  blueprintMeta: {
195
206
  blueprintId: `bp_${randomUUID()}`,
196
207
  contractHash,
197
- generator: 'ui-gen-default-haiku-4-5',
208
+ generator: "ui-gen-default-haiku-4-5",
198
209
  variance: {},
199
210
  },
200
211
  };
201
212
  return {
202
- action: 'create',
213
+ action: "create",
203
214
  reason,
204
215
  suggestion,
205
216
  effectiveContract: draftContract,
@@ -217,7 +228,7 @@ function buildCreateFallback(draftContract, reason, _estimatedLatencyMs) {
217
228
  export function createLlmBackedHandshakeNegotiator(deps) {
218
229
  const estimatedLatencyMs = deps.estimatedGenerationLatencyMs ?? DEFAULT_GEN_LATENCY_MS;
219
230
  return {
220
- async decide({ intent, blueprintDraft, gadgets, ctx, sessionId }) {
231
+ async decide({ intent, blueprintDraft, gadgets, ctx }) {
221
232
  const draftContract = blueprintDraft.contract;
222
233
  // Handshake-time exact-key fast path. Runs BEFORE the BYOK
223
234
  // creds resolve + LLM synth — a cache hit needs neither the
@@ -225,7 +236,7 @@ export function createLlmBackedHandshakeNegotiator(deps) {
225
236
  // draft canonical-key-equals a registered blueprint, return
226
237
  // `origin: 'cache'` with the matched blueprint's contract +
227
238
  // componentCode hash so the paired push.accept short-circuits
228
- // straight into commitCachedStackItem.
239
+ // straight into commitCachedRender.
229
240
  //
230
241
  // No-match (or any throw) falls through to today's synth path
231
242
  // — the negotiator stays useful when the registry is cold,
@@ -235,21 +246,17 @@ export function createLlmBackedHandshakeNegotiator(deps) {
235
246
  try {
236
247
  const matchDeps = {
237
248
  registry: deps.cache,
238
- ...(deps.installedBlueprints
239
- ? { installedBlueprints: deps.installedBlueprints }
240
- : {}),
249
+ ...(deps.installedBlueprints ? { installedBlueprints: deps.installedBlueprints } : {}),
241
250
  };
242
251
  const matchResult = await matchBlueprint(matchDeps, ctx.appId, {
243
252
  intent,
244
253
  contract: draftContract,
245
254
  });
246
- if (matchResult.strategy === 'exact-key') {
255
+ if (matchResult.strategy === "exact-key") {
247
256
  const matched = matchResult.blueprint;
248
- const codeHash = createHash('sha256')
249
- .update(matched.componentCode)
250
- .digest('hex');
257
+ const codeHash = createHash("sha256").update(matched.componentCode).digest("hex");
251
258
  const suggestion = {
252
- origin: 'cache',
259
+ origin: "cache",
253
260
  rationale: matchResult.reason,
254
261
  blueprintMeta: {
255
262
  blueprintId: matched.id,
@@ -261,7 +268,7 @@ export function createLlmBackedHandshakeNegotiator(deps) {
261
268
  },
262
269
  };
263
270
  return {
264
- action: 'reuse',
271
+ action: "reuse",
265
272
  reason: matchResult.reason,
266
273
  suggestion,
267
274
  effectiveContract: matched.contract,
@@ -286,7 +293,7 @@ export function createLlmBackedHandshakeNegotiator(deps) {
286
293
  }
287
294
  const creds = await deps.resolveLlm(ctx);
288
295
  if (!creds) {
289
- return buildCreateFallback(draftContract, 'no-creds: no BYOK credentials resolved for the configured provider; ggui_push will surface the same error and the handshake stays a no-op create.', estimatedLatencyMs);
296
+ return buildCreateFallback(draftContract, "no-creds: no BYOK credentials resolved for the configured provider; ggui_render will surface the same error and the handshake stays a no-op create.", estimatedLatencyMs);
290
297
  }
291
298
  try {
292
299
  const llm = buildLlmCaller(creds.selection, creds.providerKey);
@@ -295,14 +302,16 @@ export function createLlmBackedHandshakeNegotiator(deps) {
295
302
  const result = await negotiate({ llm }, {
296
303
  agent: {
297
304
  prompt: synthPrompt,
298
- ...(declaredAgentTools.length > 0
299
- ? { agentTools: declaredAgentTools }
300
- : {}),
305
+ ...(declaredAgentTools.length > 0 ? { agentTools: declaredAgentTools } : {}),
301
306
  ...(gadgets !== undefined ? { gadgets } : {}),
302
307
  },
303
308
  config: {
304
309
  appId: ctx.appId,
305
- sessionId: sessionId ?? 'handshake',
310
+ // Handshake runs ahead of any concrete render — no renderId
311
+ // is bound yet. The negotiator only uses `renderId` to key
312
+ // its optional `readRenderState` callback, which the OSS
313
+ // path doesn't wire here, so a stable placeholder works.
314
+ renderId: "handshake",
306
315
  includeSharedPool: false,
307
316
  },
308
317
  });
@@ -318,53 +327,53 @@ export function createLlmBackedHandshakeNegotiator(deps) {
318
327
  let suggestion;
319
328
  if (blueprintId) {
320
329
  suggestion = {
321
- origin: 'cache',
330
+ origin: "cache",
322
331
  rationale: decision.reasoning ?? `cache match (${blueprintId})`,
323
332
  blueprintMeta: {
324
333
  blueprintId,
325
334
  contractHash,
326
- generator: 'ui-gen-default-haiku-4-5',
335
+ generator: "ui-gen-default-haiku-4-5",
327
336
  variance: {},
328
337
  },
329
338
  };
330
339
  }
331
340
  else if (contractHash === draftHash) {
332
341
  suggestion = {
333
- origin: 'agent',
334
- rationale: decision.reasoning ?? 'novel-but-clean contract',
342
+ origin: "agent",
343
+ rationale: decision.reasoning ?? "novel-but-clean contract",
335
344
  blueprintMeta: {
336
345
  blueprintId: `bp_${randomUUID()}`,
337
346
  contractHash,
338
- generator: 'ui-gen-default-haiku-4-5',
347
+ generator: "ui-gen-default-haiku-4-5",
339
348
  variance: {},
340
349
  },
341
350
  };
342
351
  }
343
352
  else {
344
353
  suggestion = {
345
- origin: 'synth',
346
- rationale: decision.reasoning ?? 'synth amended contract',
354
+ origin: "synth",
355
+ rationale: decision.reasoning ?? "synth amended contract",
347
356
  blueprintMeta: {
348
357
  blueprintId: `bp_${randomUUID()}`,
349
358
  contractHash,
350
- generator: 'ui-gen-default-haiku-4-5',
359
+ generator: "ui-gen-default-haiku-4-5",
351
360
  variance: {},
352
361
  },
353
362
  amendments: {
354
363
  contractDiff: [
355
364
  {
356
- op: 'replace',
357
- path: '',
365
+ op: "replace",
366
+ path: "",
358
367
  value: contract,
359
368
  },
360
369
  ],
361
- reasoning: decision.reasoning ?? 'negotiator-amended contract',
370
+ reasoning: decision.reasoning ?? "negotiator-amended contract",
362
371
  },
363
372
  };
364
373
  }
365
374
  return {
366
- action: blueprintId ? 'reuse' : 'create',
367
- reason: decision.reasoning ?? 'negotiated',
375
+ action: blueprintId ? "reuse" : "create",
376
+ reason: decision.reasoning ?? "negotiated",
368
377
  suggestion,
369
378
  effectiveContract: contract,
370
379
  };
@@ -373,8 +382,8 @@ export function createLlmBackedHandshakeNegotiator(deps) {
373
382
  if (!isOperationalError(err))
374
383
  throw err;
375
384
  const message = err instanceof Error ? err.message : String(err);
376
- const errorClass = err instanceof Error ? err.name : 'unknown';
377
- return buildCreateFallback(draftContract, `negotiator-degraded: ${errorClass} during decision LLM call — ${message}. Falling back to bare-create; the paired ggui_push will still generate the UI.`, estimatedLatencyMs);
385
+ const errorClass = err instanceof Error ? err.name : "unknown";
386
+ return buildCreateFallback(draftContract, `negotiator-degraded: ${errorClass} during decision LLM call — ${message}. Falling back to bare-create; the paired ggui_render will still generate the UI.`, estimatedLatencyMs);
378
387
  }
379
388
  },
380
389
  // LLM-driven variant selection. Reads each candidate's
@@ -390,11 +399,11 @@ export function createLlmBackedHandshakeNegotiator(deps) {
390
399
  // variant-selector caller owns the fallback path itself.
391
400
  async selectVariant({ candidates, context, ctx }) {
392
401
  if (candidates.length === 0) {
393
- throw new Error('selectVariant: empty candidates list — orchestration should short-circuit before calling');
402
+ throw new Error("selectVariant: empty candidates list — orchestration should short-circuit before calling");
394
403
  }
395
404
  const creds = await deps.resolveLlm(ctx);
396
405
  if (!creds) {
397
- throw new Error('selectVariant: no BYOK credentials resolved; orchestration falls through to deterministic ladder');
406
+ throw new Error("selectVariant: no BYOK credentials resolved; orchestration falls through to deterministic ladder");
398
407
  }
399
408
  const llm = buildLlmCaller(creds.selection, creds.providerKey);
400
409
  return runVariantSelectionLlm(llm, candidates, context);
@@ -430,8 +439,8 @@ The request context carries the same fields. Match on:
430
439
  Honor operator pins (isOperatorDefault: true) unless variance.persona / variance.aesthetic on the request clearly contradicts the pinned variant — that's the only case where you should override the pin.
431
440
 
432
441
  Calibrate confidence honestly. Return high (≥ 0.7) only when a clear best match exists. Return low (< 0.6) when signals are weak — the orchestration falls back to a deterministic ladder in that case. The fallback is safe; over-confident picks are NOT.`;
433
- const VARIANT_SELECTION_TOOL_NAME = 'select_variant';
434
- const VARIANT_SELECTION_TOOL_DESCRIPTION = 'Pick the variant that best fits the request context. Return the chosen blueprintId, a calibrated 0-1 confidence, and a one-sentence reason citing the matching axes (persona / aesthetic / context / seedPrompt / pin / validator).';
442
+ const VARIANT_SELECTION_TOOL_NAME = "select_variant";
443
+ const VARIANT_SELECTION_TOOL_DESCRIPTION = "Pick the variant that best fits the request context. Return the chosen blueprintId, a calibrated 0-1 confidence, and a one-sentence reason citing the matching axes (persona / aesthetic / context / seedPrompt / pin / validator).";
435
444
  /**
436
445
  * JSON Schema for the structured-output tool call. Forces the model
437
446
  * to emit `{blueprintId, confidence, reason}` — no free-text prose.
@@ -439,24 +448,24 @@ const VARIANT_SELECTION_TOOL_DESCRIPTION = 'Pick the variant that best fits the
439
448
  * path parses the same shape from regex-extracted JSON.
440
449
  */
441
450
  const VARIANT_SELECTION_TOOL_SCHEMA = {
442
- type: 'object',
451
+ type: "object",
443
452
  properties: {
444
453
  blueprintId: {
445
- type: 'string',
446
- description: 'One of the candidate blueprintIds shown in the request. Echo exactly — must match a candidate.',
454
+ type: "string",
455
+ description: "One of the candidate blueprintIds shown in the request. Echo exactly — must match a candidate.",
447
456
  },
448
457
  confidence: {
449
- type: 'number',
458
+ type: "number",
450
459
  minimum: 0,
451
460
  maximum: 1,
452
- description: 'Calibrated confidence in this pick on [0, 1]. Use < 0.6 when signals are weak; the orchestration falls back to a deterministic ladder.',
461
+ description: "Calibrated confidence in this pick on [0, 1]. Use < 0.6 when signals are weak; the orchestration falls back to a deterministic ladder.",
453
462
  },
454
463
  reason: {
455
- type: 'string',
456
- description: 'One-sentence rationale citing the matching axes (persona, aesthetic, context, seedPrompt, validator, pin).',
464
+ type: "string",
465
+ description: "One-sentence rationale citing the matching axes (persona, aesthetic, context, seedPrompt, validator, pin).",
457
466
  },
458
467
  },
459
- required: ['blueprintId', 'confidence', 'reason'],
468
+ required: ["blueprintId", "confidence", "reason"],
460
469
  };
461
470
  /**
462
471
  * Build the user-message payload for the variant-selection prompt.
@@ -471,20 +480,12 @@ export function buildVariantSelectionUserMessage(candidates, context) {
471
480
  const projectedCandidates = candidates.map((c) => ({
472
481
  blueprintId: c.blueprintId,
473
482
  generator: c.generator,
474
- ...(c.validatorScore !== undefined
475
- ? { validatorScore: c.validatorScore }
476
- : {}),
483
+ ...(c.validatorScore !== undefined ? { validatorScore: c.validatorScore } : {}),
477
484
  ...(c.isOperatorDefault === true ? { isOperatorDefault: true } : {}),
478
485
  variance: {
479
- ...(c.variance.persona !== undefined
480
- ? { persona: c.variance.persona }
481
- : {}),
482
- ...(c.variance.context !== undefined
483
- ? { context: c.variance.context }
484
- : {}),
485
- ...(c.variance.seedPrompt !== undefined
486
- ? { seedPrompt: c.variance.seedPrompt }
487
- : {}),
486
+ ...(c.variance.persona !== undefined ? { persona: c.variance.persona } : {}),
487
+ ...(c.variance.context !== undefined ? { context: c.variance.context } : {}),
488
+ ...(c.variance.seedPrompt !== undefined ? { seedPrompt: c.variance.seedPrompt } : {}),
488
489
  },
489
490
  }));
490
491
  const requestProjection = {
@@ -510,14 +511,14 @@ export function buildVariantSelectionUserMessage(candidates, context) {
510
511
  : {}),
511
512
  };
512
513
  return [
513
- 'CANDIDATES:',
514
+ "CANDIDATES:",
514
515
  JSON.stringify(projectedCandidates, null, 2),
515
- '',
516
- 'REQUEST:',
516
+ "",
517
+ "REQUEST:",
517
518
  JSON.stringify(requestProjection, null, 2),
518
- '',
519
- 'Pick the variant that best fits the REQUEST. Echo the blueprintId exactly, surface calibrated confidence, give a one-sentence rationale.',
520
- ].join('\n');
519
+ "",
520
+ "Pick the variant that best fits the REQUEST. Echo the blueprintId exactly, surface calibrated confidence, give a one-sentence rationale.",
521
+ ].join("\n");
521
522
  }
522
523
  /**
523
524
  * Run the LLM call + decode. Anthropic path uses `callStructured`
@@ -556,24 +557,24 @@ async function runVariantSelectionLlm(llm, candidates, context) {
556
557
  * `VariantSelectionResult.reason` for telemetry.
557
558
  */
558
559
  export function parseVariantSelectionResponse(raw) {
559
- if (raw === null || typeof raw !== 'object') {
560
- throw new Error('variant-selection: response is not an object');
560
+ if (raw === null || typeof raw !== "object") {
561
+ throw new Error("variant-selection: response is not an object");
561
562
  }
562
563
  const obj = raw;
563
- const blueprintId = obj['blueprintId'];
564
- const confidence = obj['confidence'];
565
- const reason = obj['reason'];
566
- if (typeof blueprintId !== 'string' || blueprintId.length === 0) {
567
- throw new Error('variant-selection: blueprintId missing or non-string');
564
+ const blueprintId = obj["blueprintId"];
565
+ const confidence = obj["confidence"];
566
+ const reason = obj["reason"];
567
+ if (typeof blueprintId !== "string" || blueprintId.length === 0) {
568
+ throw new Error("variant-selection: blueprintId missing or non-string");
568
569
  }
569
- if (typeof confidence !== 'number' ||
570
+ if (typeof confidence !== "number" ||
570
571
  !Number.isFinite(confidence) ||
571
572
  confidence < 0 ||
572
573
  confidence > 1) {
573
574
  throw new Error(`variant-selection: confidence missing or out of range: ${JSON.stringify(confidence)}`);
574
575
  }
575
- if (typeof reason !== 'string') {
576
- throw new Error('variant-selection: reason missing or non-string');
576
+ if (typeof reason !== "string") {
577
+ throw new Error("variant-selection: reason missing or non-string");
577
578
  }
578
579
  return { blueprintId, confidence, reason };
579
580
  }
@@ -27,13 +27,13 @@
27
27
  * A can NEVER call tools on connector B.
28
28
  */
29
29
  import type { Express } from 'express';
30
- import { type ConnectorRegistry, type SessionStore } from '@ggui-ai/mcp-server-core';
30
+ import { type ConnectorRegistry, type RenderStore } from '@ggui-ai/mcp-server-core';
31
31
  import type { Logger } from './logger.js';
32
32
  /** Default mount path for the MCP Apps inbound routes. */
33
33
  export declare const DEFAULT_MCP_APPS_INBOUND_PATH = "/mcp-apps";
34
34
  export interface McpAppsInboundOptions {
35
35
  readonly connectors: ConnectorRegistry;
36
- readonly sessionStore: SessionStore;
36
+ readonly renderStore: RenderStore;
37
37
  readonly logger: Logger;
38
38
  /** Override for the mount path prefix. Defaults to `/mcp-apps`. */
39
39
  readonly path?: string;
@@ -69,7 +69,7 @@ export declare class ToolsListCache {
69
69
  * Register the MCP Apps inbound proxy routes on an Express app.
70
70
  *
71
71
  * GET /mcp-apps/resource?session=<id>&item=<id>
72
- * Fetches the referenced McpAppsStackItem's `source.resourceUri`
72
+ * Fetches the referenced McpAppsRender's `source.resourceUri`
73
73
  * via resources/read on the source MCP server. Returns the HTML
74
74
  * with the MIME declared by the source (typically
75
75
  * `text/html;profile=mcp-app`) and spec-canonical CSP headers.
@@ -1 +1 @@
1
- {"version":3,"file":"mcp-apps-inbound.d.ts","sourceRoot":"","sources":["../src/mcp-apps-inbound.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAqB,MAAM,SAAS,CAAC;AAG1D,OAAO,EACL,KAAK,iBAAiB,EAEtB,KAAK,YAAY,EAClB,MAAM,0BAA0B,CAAC;AAGlC,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAE1C,0DAA0D;AAC1D,eAAO,MAAM,6BAA6B,cAAc,CAAC;AAEzD,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,UAAU,EAAE,iBAAiB,CAAC;IACvC,QAAQ,CAAC,YAAY,EAAE,YAAY,CAAC;IACpC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,mEAAmE;IACnE,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;CACxB;AAED;;;;;GAKG;AACH,UAAU,cAAc;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,UAAU,EAAE,aAAa,CAAC,OAAO,GAAG,KAAK,CAAC,CAAC;CACrD;AAED,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;IACrD,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B;AAED;;;;GAIG;AACH,qBAAa,cAAc;IAEb,OAAO,CAAC,QAAQ,CAAC,KAAK;IADlC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAA0C;gBACjC,KAAK,GAAE,MAAe;IAEnD,GAAG,CAAC,WAAW,EAAE,MAAM,GAAG,mBAAmB,GAAG,IAAI;IAUpD,GAAG,CAAC,WAAW,EAAE,MAAM,EAAE,KAAK,EAAE,QAAQ,CAAC,cAAc,CAAC,GAAG,mBAAmB;IAQ9E,UAAU,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI;CAGtC;AAwDD;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,qBAAqB,CACnC,GAAG,EAAE,OAAO,EACZ,IAAI,EAAE,qBAAqB,GAC1B,cAAc,CAsIhB"}
1
+ {"version":3,"file":"mcp-apps-inbound.d.ts","sourceRoot":"","sources":["../src/mcp-apps-inbound.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAqB,MAAM,SAAS,CAAC;AAG1D,OAAO,EACL,KAAK,iBAAiB,EAEtB,KAAK,WAAW,EACjB,MAAM,0BAA0B,CAAC;AAGlC,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAE1C,0DAA0D;AAC1D,eAAO,MAAM,6BAA6B,cAAc,CAAC;AAEzD,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,UAAU,EAAE,iBAAiB,CAAC;IACvC,QAAQ,CAAC,WAAW,EAAE,WAAW,CAAC;IAClC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,mEAAmE;IACnE,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;CACxB;AAED;;;;;GAKG;AACH,UAAU,cAAc;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,UAAU,EAAE,aAAa,CAAC,OAAO,GAAG,KAAK,CAAC,CAAC;CACrD;AAED,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;IACrD,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B;AAED;;;;GAIG;AACH,qBAAa,cAAc;IAEb,OAAO,CAAC,QAAQ,CAAC,KAAK;IADlC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAA0C;gBACjC,KAAK,GAAE,MAAe;IAEnD,GAAG,CAAC,WAAW,EAAE,MAAM,GAAG,mBAAmB,GAAG,IAAI;IAUpD,GAAG,CAAC,WAAW,EAAE,MAAM,EAAE,KAAK,EAAE,QAAQ,CAAC,cAAc,CAAC,GAAG,mBAAmB;IAQ9E,UAAU,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI;CAGtC;AA6DD;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,qBAAqB,CACnC,GAAG,EAAE,OAAO,EACZ,IAAI,EAAE,qBAAqB,GAC1B,cAAc,CAsIhB"}
@@ -28,7 +28,7 @@
28
28
  */
29
29
  import { Client } from '@modelcontextprotocol/sdk/client/index.js';
30
30
  import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
31
- import { isMcpAppsStackItem } from '@ggui-ai/protocol/integrations/mcp-apps';
31
+ import { isMcpAppsRender } from '@ggui-ai/protocol/integrations/mcp-apps';
32
32
  /** Default mount path for the MCP Apps inbound routes. */
33
33
  export const DEFAULT_MCP_APPS_INBOUND_PATH = '/mcp-apps';
34
34
  /**
@@ -97,24 +97,29 @@ function readVisibility(toolDecl) {
97
97
  return out.length > 0 ? out : ['model'];
98
98
  }
99
99
  /**
100
- * Resolve `{session, item}` from the ggui session store to a
101
- * {@link McpAppsStackItem}. Returns null if session or item is missing
102
- * or the target is not an mcpApps variant.
100
+ * Resolve `{render}` from the ggui render store to a
101
+ * {@link McpAppsRender}. Returns null if the render is missing or is
102
+ * not an mcpApps variant.
103
+ *
104
+ * Phase B: a render IS the addressable unit — no per-item lookup inside
105
+ * a stack vessel — so the render id alone resolves the McpAppsRender
106
+ * directly. The `itemId` parameter is preserved on the wire-facing
107
+ * query string for callsite stability but ignored when it matches the
108
+ * renderId (which is the only valid value post-collapse).
103
109
  */
104
- async function resolveMcpAppsItem(store, sessionId, itemId) {
105
- const session = await store.get(sessionId);
106
- if (!session)
110
+ async function resolveMcpAppsItem(store, renderId, _itemId) {
111
+ const stored = await store.get(renderId);
112
+ if (!stored)
107
113
  return null;
108
- const entry = session.stack.find((e) => e.id === itemId);
109
- if (!entry || !isMcpAppsStackItem(entry))
114
+ if (!isMcpAppsRender(stored.render))
110
115
  return null;
111
- return entry;
116
+ return stored.render;
112
117
  }
113
118
  /**
114
119
  * Register the MCP Apps inbound proxy routes on an Express app.
115
120
  *
116
121
  * GET /mcp-apps/resource?session=<id>&item=<id>
117
- * Fetches the referenced McpAppsStackItem's `source.resourceUri`
122
+ * Fetches the referenced McpAppsRender's `source.resourceUri`
118
123
  * via resources/read on the source MCP server. Returns the HTML
119
124
  * with the MIME declared by the source (typically
120
125
  * `text/html;profile=mcp-app`) and spec-canonical CSP headers.
@@ -130,15 +135,15 @@ export function installMcpAppsInbound(app, opts) {
130
135
  const path = opts.path ?? DEFAULT_MCP_APPS_INBOUND_PATH;
131
136
  const cache = new ToolsListCache();
132
137
  app.get(`${path}/resource`, async (req, res) => {
133
- const sessionId = typeof req.query.session === 'string' ? req.query.session : '';
134
- const itemId = typeof req.query.item === 'string' ? req.query.item : '';
135
- if (!sessionId || !itemId) {
136
- res.status(400).type('text/plain').send('Missing ?session and/or ?item');
138
+ const renderId = typeof req.query.render === 'string' ? req.query.render : '';
139
+ const itemId = typeof req.query.item === 'string' ? req.query.item : renderId;
140
+ if (!renderId) {
141
+ res.status(400).type('text/plain').send('Missing ?render');
137
142
  return;
138
143
  }
139
- const item = await resolveMcpAppsItem(opts.sessionStore, sessionId, itemId);
144
+ const item = await resolveMcpAppsItem(opts.renderStore, renderId, itemId);
140
145
  if (!item) {
141
- res.status(404).type('text/plain').send('Stack item not found');
146
+ res.status(404).type('text/plain').send('Render not found');
142
147
  return;
143
148
  }
144
149
  const connector = await opts.connectors.get(item.source.connectorId);
@@ -169,7 +174,7 @@ export function installMcpAppsInbound(app, opts) {
169
174
  }
170
175
  catch (err) {
171
176
  opts.logger.error('mcp_apps_resource_proxy_failed', {
172
- sessionId,
177
+ renderId,
173
178
  itemId,
174
179
  connectorId: item.source.connectorId,
175
180
  error: String(err),
@@ -183,14 +188,14 @@ export function installMcpAppsInbound(app, opts) {
183
188
  });
184
189
  app.post(`${path}/tools-call`, async (req, res) => {
185
190
  const body = (req.body ?? {});
186
- const sessionId = typeof body.session === 'string' ? body.session : '';
187
- const itemId = typeof body.item === 'string' ? body.item : '';
191
+ const renderId = typeof body.render === 'string' ? body.render : '';
192
+ const itemId = typeof body.item === 'string' ? body.item : renderId;
188
193
  const toolName = typeof body.tool === 'string' ? body.tool : '';
189
- if (!sessionId || !itemId || !toolName) {
194
+ if (!renderId || !toolName) {
190
195
  res.status(400).json({ error: 'missing_fields' });
191
196
  return;
192
197
  }
193
- const item = await resolveMcpAppsItem(opts.sessionStore, sessionId, itemId);
198
+ const item = await resolveMcpAppsItem(opts.renderStore, renderId, itemId);
194
199
  if (!item) {
195
200
  res.status(404).json({ error: 'item_not_found' });
196
201
  return;
@@ -239,7 +244,7 @@ export function installMcpAppsInbound(app, opts) {
239
244
  }
240
245
  catch (err) {
241
246
  opts.logger.error('mcp_apps_tools_call_proxy_failed', {
242
- sessionId,
247
+ renderId,
243
248
  itemId,
244
249
  connectorId: item.source.connectorId,
245
250
  toolName,