@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.
- package/README.md +1 -1
- package/dist/build-mcp.d.ts +8 -8
- package/dist/build-mcp.d.ts.map +1 -1
- package/dist/build-mcp.js +53 -12
- package/dist/console-auth.d.ts +7 -7
- package/dist/console-auth.d.ts.map +1 -1
- package/dist/console-auth.js +4 -4
- package/dist/console-cache.d.ts +2 -2
- package/dist/console-cache.d.ts.map +1 -1
- package/dist/console-cache.js +10 -10
- package/dist/console-headers.d.ts +2 -2
- package/dist/console-headers.js +2 -2
- package/dist/console-payloads.d.ts +3 -3
- package/dist/console-payloads.d.ts.map +1 -1
- package/dist/console-payloads.js +10 -10
- package/dist/console-timeline.d.ts +12 -30
- package/dist/console-timeline.d.ts.map +1 -1
- package/dist/console-timeline.js +44 -45
- package/dist/index.d.ts +7 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -5
- package/dist/instructions-presets.d.ts +1 -1
- package/dist/instructions-presets.d.ts.map +1 -1
- package/dist/instructions-presets.js +40 -22
- package/dist/llm-backed-negotiator.d.ts +6 -6
- package/dist/llm-backed-negotiator.d.ts.map +1 -1
- package/dist/llm-backed-negotiator.js +92 -91
- package/dist/mcp-apps-inbound.d.ts +3 -3
- package/dist/mcp-apps-inbound.d.ts.map +1 -1
- package/dist/mcp-apps-inbound.js +28 -23
- package/dist/mcp-apps-outbound.d.ts +136 -121
- package/dist/mcp-apps-outbound.d.ts.map +1 -1
- package/dist/mcp-apps-outbound.js +384 -414
- package/dist/mcp-mounts.d.ts +21 -20
- package/dist/mcp-mounts.d.ts.map +1 -1
- package/dist/mcp-mounts.js +25 -29
- package/dist/{session-channel.d.ts → render-channel.d.ts} +145 -102
- package/dist/render-channel.d.ts.map +1 -0
- package/dist/{session-channel.js → render-channel.js} +481 -462
- package/dist/schema-compat.d.ts +11 -11
- package/dist/schema-compat.d.ts.map +1 -1
- package/dist/schema-compat.js +6 -6
- package/dist/server.d.ts +224 -219
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +1498 -1963
- package/dist/storage.d.ts +7 -7
- package/dist/storage.d.ts.map +1 -1
- package/dist/storage.js +12 -12
- package/package.json +12 -11
- package/dist/render-gate.d.ts +0 -87
- package/dist/render-gate.d.ts.map +0 -1
- package/dist/render-gate.js +0 -77
- package/dist/render-rate-limit.d.ts +0 -59
- package/dist/render-rate-limit.d.ts.map +0 -1
- package/dist/render-rate-limit.js +0 -73
- package/dist/render-signing.d.ts +0 -98
- package/dist/render-signing.d.ts.map +0 -1
- package/dist/render-signing.js +0 -113
- 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
|
-
* `
|
|
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 {
|
|
54
|
-
import {
|
|
55
|
-
import {
|
|
56
|
-
import {
|
|
57
|
-
import {
|
|
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 ===
|
|
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
|
-
|
|
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:
|
|
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:
|
|
172
|
+
tool_choice: { type: "tool", name: args.tool.name },
|
|
162
173
|
};
|
|
163
|
-
const response = await fetch(
|
|
164
|
-
method:
|
|
174
|
+
const response = await fetch("https://api.anthropic.com/v1/messages", {
|
|
175
|
+
method: "POST",
|
|
165
176
|
headers: {
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
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 ===
|
|
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:
|
|
203
|
+
origin: "agent",
|
|
193
204
|
rationale: reason,
|
|
194
205
|
blueprintMeta: {
|
|
195
206
|
blueprintId: `bp_${randomUUID()}`,
|
|
196
207
|
contractHash,
|
|
197
|
-
generator:
|
|
208
|
+
generator: "ui-gen-default-haiku-4-5",
|
|
198
209
|
variance: {},
|
|
199
210
|
},
|
|
200
211
|
};
|
|
201
212
|
return {
|
|
202
|
-
action:
|
|
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
|
|
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
|
|
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 ===
|
|
255
|
+
if (matchResult.strategy === "exact-key") {
|
|
247
256
|
const matched = matchResult.blueprint;
|
|
248
|
-
const codeHash = createHash(
|
|
249
|
-
.update(matched.componentCode)
|
|
250
|
-
.digest('hex');
|
|
257
|
+
const codeHash = createHash("sha256").update(matched.componentCode).digest("hex");
|
|
251
258
|
const suggestion = {
|
|
252
|
-
origin:
|
|
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:
|
|
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,
|
|
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
|
-
|
|
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:
|
|
330
|
+
origin: "cache",
|
|
322
331
|
rationale: decision.reasoning ?? `cache match (${blueprintId})`,
|
|
323
332
|
blueprintMeta: {
|
|
324
333
|
blueprintId,
|
|
325
334
|
contractHash,
|
|
326
|
-
generator:
|
|
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:
|
|
334
|
-
rationale: decision.reasoning ??
|
|
342
|
+
origin: "agent",
|
|
343
|
+
rationale: decision.reasoning ?? "novel-but-clean contract",
|
|
335
344
|
blueprintMeta: {
|
|
336
345
|
blueprintId: `bp_${randomUUID()}`,
|
|
337
346
|
contractHash,
|
|
338
|
-
generator:
|
|
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:
|
|
346
|
-
rationale: decision.reasoning ??
|
|
354
|
+
origin: "synth",
|
|
355
|
+
rationale: decision.reasoning ?? "synth amended contract",
|
|
347
356
|
blueprintMeta: {
|
|
348
357
|
blueprintId: `bp_${randomUUID()}`,
|
|
349
358
|
contractHash,
|
|
350
|
-
generator:
|
|
359
|
+
generator: "ui-gen-default-haiku-4-5",
|
|
351
360
|
variance: {},
|
|
352
361
|
},
|
|
353
362
|
amendments: {
|
|
354
363
|
contractDiff: [
|
|
355
364
|
{
|
|
356
|
-
op:
|
|
357
|
-
path:
|
|
365
|
+
op: "replace",
|
|
366
|
+
path: "",
|
|
358
367
|
value: contract,
|
|
359
368
|
},
|
|
360
369
|
],
|
|
361
|
-
reasoning: decision.reasoning ??
|
|
370
|
+
reasoning: decision.reasoning ?? "negotiator-amended contract",
|
|
362
371
|
},
|
|
363
372
|
};
|
|
364
373
|
}
|
|
365
374
|
return {
|
|
366
|
-
action: blueprintId ?
|
|
367
|
-
reason: decision.reasoning ??
|
|
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 :
|
|
377
|
-
return buildCreateFallback(draftContract, `negotiator-degraded: ${errorClass} during decision LLM call — ${message}. Falling back to bare-create; the paired
|
|
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(
|
|
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(
|
|
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 =
|
|
434
|
-
const VARIANT_SELECTION_TOOL_DESCRIPTION =
|
|
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:
|
|
451
|
+
type: "object",
|
|
443
452
|
properties: {
|
|
444
453
|
blueprintId: {
|
|
445
|
-
type:
|
|
446
|
-
description:
|
|
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:
|
|
458
|
+
type: "number",
|
|
450
459
|
minimum: 0,
|
|
451
460
|
maximum: 1,
|
|
452
|
-
description:
|
|
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:
|
|
456
|
-
description:
|
|
464
|
+
type: "string",
|
|
465
|
+
description: "One-sentence rationale citing the matching axes (persona, aesthetic, context, seedPrompt, validator, pin).",
|
|
457
466
|
},
|
|
458
467
|
},
|
|
459
|
-
required: [
|
|
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
|
-
|
|
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
|
-
|
|
514
|
+
"CANDIDATES:",
|
|
514
515
|
JSON.stringify(projectedCandidates, null, 2),
|
|
515
|
-
|
|
516
|
-
|
|
516
|
+
"",
|
|
517
|
+
"REQUEST:",
|
|
517
518
|
JSON.stringify(requestProjection, null, 2),
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
].join(
|
|
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 !==
|
|
560
|
-
throw new Error(
|
|
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[
|
|
564
|
-
const confidence = obj[
|
|
565
|
-
const reason = obj[
|
|
566
|
-
if (typeof blueprintId !==
|
|
567
|
-
throw new Error(
|
|
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 !==
|
|
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 !==
|
|
576
|
-
throw new Error(
|
|
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
|
|
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
|
|
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
|
|
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,
|
|
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"}
|
package/dist/mcp-apps-inbound.js
CHANGED
|
@@ -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 {
|
|
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 `{
|
|
101
|
-
* {@link
|
|
102
|
-
*
|
|
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,
|
|
105
|
-
const
|
|
106
|
-
if (!
|
|
110
|
+
async function resolveMcpAppsItem(store, renderId, _itemId) {
|
|
111
|
+
const stored = await store.get(renderId);
|
|
112
|
+
if (!stored)
|
|
107
113
|
return null;
|
|
108
|
-
|
|
109
|
-
if (!entry || !isMcpAppsStackItem(entry))
|
|
114
|
+
if (!isMcpAppsRender(stored.render))
|
|
110
115
|
return null;
|
|
111
|
-
return
|
|
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
|
|
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
|
|
134
|
-
const itemId = typeof req.query.item === 'string' ? req.query.item :
|
|
135
|
-
if (!
|
|
136
|
-
res.status(400).type('text/plain').send('Missing ?
|
|
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.
|
|
144
|
+
const item = await resolveMcpAppsItem(opts.renderStore, renderId, itemId);
|
|
140
145
|
if (!item) {
|
|
141
|
-
res.status(404).type('text/plain').send('
|
|
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
|
-
|
|
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
|
|
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 (!
|
|
194
|
+
if (!renderId || !toolName) {
|
|
190
195
|
res.status(400).json({ error: 'missing_fields' });
|
|
191
196
|
return;
|
|
192
197
|
}
|
|
193
|
-
const item = await resolveMcpAppsItem(opts.
|
|
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
|
-
|
|
247
|
+
renderId,
|
|
243
248
|
itemId,
|
|
244
249
|
connectorId: item.source.connectorId,
|
|
245
250
|
toolName,
|