@ggui-ai/mcp-server 0.2.0-alpha.4 → 0.4.0-rc.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.
Files changed (152) hide show
  1. package/dist/admin-blueprints-transport.d.ts.map +1 -1
  2. package/dist/admin-blueprints-transport.js +2 -1
  3. package/dist/admin-oauth-providers-transport.d.ts.map +1 -1
  4. package/dist/admin-oauth-providers-transport.js +7 -5
  5. package/dist/api-renders-routes.d.ts +85 -0
  6. package/dist/api-renders-routes.d.ts.map +1 -0
  7. package/dist/api-renders-routes.js +372 -0
  8. package/dist/build-mcp.d.ts +2 -2
  9. package/dist/build-mcp.d.ts.map +1 -1
  10. package/dist/build-mcp.js +64 -5
  11. package/dist/code-routes.d.ts +47 -0
  12. package/dist/code-routes.d.ts.map +1 -0
  13. package/dist/code-routes.js +81 -0
  14. package/dist/code-store-fs.js +2 -2
  15. package/dist/console-auth.d.ts +10 -10
  16. package/dist/console-auth.d.ts.map +1 -1
  17. package/dist/console-auth.js +5 -5
  18. package/dist/console-blueprint-routes.d.ts +71 -0
  19. package/dist/console-blueprint-routes.d.ts.map +1 -0
  20. package/dist/console-blueprint-routes.js +348 -0
  21. package/dist/console-chat-routes.d.ts +80 -0
  22. package/dist/console-chat-routes.d.ts.map +1 -0
  23. package/dist/console-chat-routes.js +204 -0
  24. package/dist/console-config-routes.d.ts +37 -0
  25. package/dist/console-config-routes.d.ts.map +1 -0
  26. package/dist/console-config-routes.js +91 -0
  27. package/dist/console-headers.d.ts +1 -1
  28. package/dist/console-info-routes.d.ts +84 -0
  29. package/dist/console-info-routes.d.ts.map +1 -0
  30. package/dist/console-info-routes.js +135 -0
  31. package/dist/console-keys-routes.d.ts +50 -0
  32. package/dist/console-keys-routes.d.ts.map +1 -0
  33. package/dist/console-keys-routes.js +222 -0
  34. package/dist/console-llm-keys-routes.d.ts +47 -0
  35. package/dist/console-llm-keys-routes.d.ts.map +1 -0
  36. package/dist/console-llm-keys-routes.js +443 -0
  37. package/dist/console-mcp-tools-routes.d.ts +41 -0
  38. package/dist/console-mcp-tools-routes.d.ts.map +1 -0
  39. package/dist/console-mcp-tools-routes.js +60 -0
  40. package/dist/console-registry-routes.d.ts +66 -0
  41. package/dist/console-registry-routes.d.ts.map +1 -0
  42. package/dist/console-registry-routes.js +276 -0
  43. package/dist/console-session-routes.d.ts +89 -0
  44. package/dist/console-session-routes.d.ts.map +1 -0
  45. package/dist/console-session-routes.js +385 -0
  46. package/dist/console-sessions-routes.d.ts +52 -0
  47. package/dist/console-sessions-routes.d.ts.map +1 -0
  48. package/dist/console-sessions-routes.js +106 -0
  49. package/dist/console-static-routes.d.ts +54 -0
  50. package/dist/console-static-routes.d.ts.map +1 -0
  51. package/dist/console-static-routes.js +190 -0
  52. package/dist/console-theme-routes.d.ts +3 -3
  53. package/dist/console-theme-routes.js +1 -1
  54. package/dist/console-timeline.d.ts +5 -5
  55. package/dist/console-timeline.d.ts.map +1 -1
  56. package/dist/console-timeline.js +27 -26
  57. package/dist/console-welcome.js +2 -2
  58. package/dist/email-login.d.ts.map +1 -1
  59. package/dist/email-login.js +2 -3
  60. package/dist/ggui-session-channel/action-ingress.d.ts +54 -0
  61. package/dist/ggui-session-channel/action-ingress.d.ts.map +1 -0
  62. package/dist/ggui-session-channel/action-ingress.js +228 -0
  63. package/dist/ggui-session-channel/channel-subscriptions.d.ts +97 -0
  64. package/dist/ggui-session-channel/channel-subscriptions.d.ts.map +1 -0
  65. package/dist/ggui-session-channel/channel-subscriptions.js +224 -0
  66. package/dist/ggui-session-channel/internal-types.d.ts +102 -0
  67. package/dist/ggui-session-channel/internal-types.d.ts.map +1 -0
  68. package/dist/ggui-session-channel/internal-types.js +6 -0
  69. package/dist/ggui-session-channel/outbound.d.ts +81 -0
  70. package/dist/ggui-session-channel/outbound.d.ts.map +1 -0
  71. package/dist/ggui-session-channel/outbound.js +174 -0
  72. package/dist/ggui-session-channel/socket-router.d.ts +38 -0
  73. package/dist/ggui-session-channel/socket-router.d.ts.map +1 -0
  74. package/dist/ggui-session-channel/socket-router.js +213 -0
  75. package/dist/ggui-session-channel/subscribe.d.ts +165 -0
  76. package/dist/ggui-session-channel/subscribe.d.ts.map +1 -0
  77. package/dist/ggui-session-channel/subscribe.js +370 -0
  78. package/dist/ggui-session-channel/subscriber-lifecycle.d.ts +40 -0
  79. package/dist/ggui-session-channel/subscriber-lifecycle.d.ts.map +1 -0
  80. package/dist/ggui-session-channel/subscriber-lifecycle.js +123 -0
  81. package/dist/ggui-session-channel.d.ts +425 -0
  82. package/dist/ggui-session-channel.d.ts.map +1 -0
  83. package/dist/ggui-session-channel.js +262 -0
  84. package/dist/health-routes.d.ts +76 -0
  85. package/dist/health-routes.d.ts.map +1 -0
  86. package/dist/health-routes.js +145 -0
  87. package/dist/index.d.ts +10 -11
  88. package/dist/index.d.ts.map +1 -1
  89. package/dist/index.js +8 -9
  90. package/dist/instructions-presets.d.ts +3 -3
  91. package/dist/instructions-presets.js +24 -24
  92. package/dist/llm-backed-negotiator.d.ts +68 -67
  93. package/dist/llm-backed-negotiator.d.ts.map +1 -1
  94. package/dist/llm-backed-negotiator.js +82 -221
  95. package/dist/mcp-apps-outbound.d.ts +47 -48
  96. package/dist/mcp-apps-outbound.d.ts.map +1 -1
  97. package/dist/mcp-apps-outbound.js +154 -177
  98. package/dist/mcp-endpoint-routes.d.ts +88 -0
  99. package/dist/mcp-endpoint-routes.d.ts.map +1 -0
  100. package/dist/mcp-endpoint-routes.js +359 -0
  101. package/dist/mcp-mounts.d.ts +2 -76
  102. package/dist/mcp-mounts.d.ts.map +1 -1
  103. package/dist/mcp-mounts.js +0 -76
  104. package/dist/oauth-as-routes.d.ts +60 -0
  105. package/dist/oauth-as-routes.d.ts.map +1 -0
  106. package/dist/oauth-as-routes.js +82 -0
  107. package/dist/oauth-clients-routes.d.ts +39 -0
  108. package/dist/oauth-clients-routes.d.ts.map +1 -0
  109. package/dist/oauth-clients-routes.js +87 -0
  110. package/dist/oauth-login-types.d.ts +1 -20
  111. package/dist/oauth-login-types.d.ts.map +1 -1
  112. package/dist/oauth-login-types.js +30 -7
  113. package/dist/oauth-login.d.ts.map +1 -1
  114. package/dist/oauth-login.js +3 -2
  115. package/dist/oauth-providers-store.d.ts.map +1 -1
  116. package/dist/oauth-providers-store.js +5 -5
  117. package/dist/oauth.d.ts +9 -8
  118. package/dist/oauth.d.ts.map +1 -1
  119. package/dist/oauth.js +41 -19
  120. package/dist/pairing-transport.d.ts.map +1 -1
  121. package/dist/pairing-transport.js +2 -1
  122. package/dist/request-context.d.ts +2 -2
  123. package/dist/request-context.js +2 -2
  124. package/dist/reserved-validators.d.ts.map +1 -1
  125. package/dist/reserved-validators.js +9 -1
  126. package/dist/route-param.d.ts +9 -0
  127. package/dist/route-param.d.ts.map +1 -0
  128. package/dist/route-param.js +10 -0
  129. package/dist/runtime-bundle-route.d.ts +43 -0
  130. package/dist/runtime-bundle-route.d.ts.map +1 -0
  131. package/dist/runtime-bundle-route.js +80 -0
  132. package/dist/schema-compat.d.ts +64 -62
  133. package/dist/schema-compat.d.ts.map +1 -1
  134. package/dist/schema-compat.js +23 -51
  135. package/dist/server.d.ts +179 -193
  136. package/dist/server.d.ts.map +1 -1
  137. package/dist/server.js +644 -3759
  138. package/dist/storage.d.ts +5 -5
  139. package/dist/storage.d.ts.map +1 -1
  140. package/dist/storage.js +5 -5
  141. package/dist/thread-transport.d.ts.map +1 -1
  142. package/dist/thread-transport.js +4 -3
  143. package/dist/user-session-auth.d.ts +7 -21
  144. package/dist/user-session-auth.d.ts.map +1 -1
  145. package/dist/user-session-auth.js +7 -28
  146. package/package.json +16 -15
  147. package/dist/mcp-apps-inbound.d.ts +0 -86
  148. package/dist/mcp-apps-inbound.d.ts.map +0 -1
  149. package/dist/mcp-apps-inbound.js +0 -283
  150. package/dist/render-channel.d.ts +0 -694
  151. package/dist/render-channel.d.ts.map +0 -1
  152. package/dist/render-channel.js +0 -1775
@@ -0,0 +1,88 @@
1
+ /**
2
+ * MCP wire endpoints — the audience-routed JSON-RPC surfaces.
3
+ *
4
+ * POST <universalMcpPath> — agent+runtime tools (default `/mcp`)
5
+ * POST <pathPrefix>/:appId — per-tenant variant (opt-in via
6
+ * `perAppRouting`)
7
+ * POST /protocol — design-time spec/discovery tools
8
+ * POST /ops — operator-class management tools
9
+ * POST <service.path> — isolated MCP services (path IS
10
+ * the audience)
11
+ * GET/DELETE on each — 405 (stateless server; no
12
+ * streaming continuation /
13
+ * session-terminate verbs)
14
+ *
15
+ * Every route shares ONE request pipeline (`makeMcpHandler`): resolve
16
+ * identity via the AuthAdapter (anonymous services synthesize a
17
+ * builder identity on missing/invalid bearers), apply the per-app
18
+ * authorize hook, build a fresh `McpServer` + Streamable HTTP
19
+ * transport per request (stateless), and dispatch under the
20
+ * AsyncLocalStorage-scoped `HandlerContext`. The difference between
21
+ * routes is ONLY the handler set each exposes.
22
+ *
23
+ * See `docs/development/audience-routes.md` for the audience taxonomy
24
+ * (`agent` / `runtime` / `protocol` / `ops`) and the wire-name prefix
25
+ * rules.
26
+ */
27
+ import type { AuthAdapter, AuthResult } from "@ggui-ai/mcp-server-core";
28
+ import type { HandlerContext, SharedHandler } from "@ggui-ai/mcp-server-handlers";
29
+ import type { Express } from "express";
30
+ import type { AsyncLocalStorage } from "node:async_hooks";
31
+ import type { ZodRawShape } from "zod";
32
+ import { type BuildMcpServerOptions, type ServerInfo } from "./build-mcp.js";
33
+ import type { Logger } from "./logger.js";
34
+ import type { McpService } from "./mcp-mounts.js";
35
+ /** Per-tenant URL routing shape — mirrors `CreateGguiServerOptions.perAppRouting`. */
36
+ interface PerAppRouting {
37
+ readonly paramName: string;
38
+ readonly paramPattern: string;
39
+ readonly pathPrefix?: string;
40
+ readonly authorize?: (urlAppId: string, identity: AuthResult) => Promise<void>;
41
+ }
42
+ interface MountOptions {
43
+ /** Express app to mount onto. */
44
+ readonly app: Express;
45
+ /** Structured logger; per-request children carry `requestId`. */
46
+ readonly logger: Logger;
47
+ /** Auth adapter every route resolves bearers against. */
48
+ readonly auth: AuthAdapter;
49
+ /** Server identity forwarded to every per-request `buildMcpServer`. */
50
+ readonly info: ServerInfo;
51
+ /** Full composed handler list (audience filtering happens here). */
52
+ readonly handlers: ReadonlyArray<SharedHandler<ZodRawShape, ZodRawShape>>;
53
+ /** Validated isolated-service list (`validateMcpServices` output). */
54
+ readonly mcpServices: ReadonlyArray<McpService>;
55
+ /** Request-scoped HandlerContext storage shared with the handlers. */
56
+ readonly als: AsyncLocalStorage<HandlerContext>;
57
+ /** Identity → appId resolution rule (SPEC §12.2). */
58
+ readonly appIdFromIdentity: (result: AuthResult) => string;
59
+ /** Universal endpoint path (default `/mcp`; cloud overrides to `/`). */
60
+ readonly universalMcpPath: string;
61
+ /** Per-tenant endpoint shape — absent = universal-only deployment. */
62
+ readonly perAppRouting?: PerAppRouting;
63
+ /** Whether OAuth is enabled (adds `WWW-Authenticate` on 401). */
64
+ readonly oauthEnabled: boolean;
65
+ /** Operator-configured issuer URL override (OAuth). */
66
+ readonly oauthIssuerUrl?: string;
67
+ /** Operator-supplied error → HTTP/JSON-RPC mapping hook. */
68
+ readonly errorMapper?: (err: unknown) => {
69
+ readonly status: number;
70
+ readonly code: number;
71
+ readonly message: string;
72
+ } | undefined;
73
+ /**
74
+ * Per-boot `buildMcpServer` options. Assembled once by the composer
75
+ * (every input is fixed at composition time); the handler spreads a
76
+ * fresh object per request so the builder never sees a shared
77
+ * mutable reference.
78
+ */
79
+ readonly buildMcpOptions: BuildMcpServerOptions;
80
+ }
81
+ /**
82
+ * Mount the universal / per-app / protocol / ops / service MCP
83
+ * endpoints onto the express app. Returns nothing — the routes
84
+ * self-register.
85
+ */
86
+ export declare function mountMcpEndpoints(opts: MountOptions): void;
87
+ export {};
88
+ //# sourceMappingURL=mcp-endpoint-routes.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mcp-endpoint-routes.d.ts","sourceRoot":"","sources":["../src/mcp-endpoint-routes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AACxE,OAAO,KAAK,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAElF,OAAO,KAAK,EAAE,OAAO,EAAqB,MAAM,SAAS,CAAC;AAC1D,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAE1D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,KAAK,CAAC;AAEvC,OAAO,EAAkB,KAAK,qBAAqB,EAAE,KAAK,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAC7F,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAGlD,sFAAsF;AACtF,UAAU,aAAa;IACrB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,UAAU,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CAChF;AAED,UAAU,YAAY;IACpB,iCAAiC;IACjC,QAAQ,CAAC,GAAG,EAAE,OAAO,CAAC;IACtB,iEAAiE;IACjE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,yDAAyD;IACzD,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAC;IAC3B,uEAAuE;IACvE,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;IAC1B,oEAAoE;IACpE,QAAQ,CAAC,QAAQ,EAAE,aAAa,CAAC,aAAa,CAAC,WAAW,EAAE,WAAW,CAAC,CAAC,CAAC;IAC1E,sEAAsE;IACtE,QAAQ,CAAC,WAAW,EAAE,aAAa,CAAC,UAAU,CAAC,CAAC;IAChD,sEAAsE;IACtE,QAAQ,CAAC,GAAG,EAAE,iBAAiB,CAAC,cAAc,CAAC,CAAC;IAChD,qDAAqD;IACrD,QAAQ,CAAC,iBAAiB,EAAE,CAAC,MAAM,EAAE,UAAU,KAAK,MAAM,CAAC;IAC3D,wEAAwE;IACxE,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;IAClC,sEAAsE;IACtE,QAAQ,CAAC,aAAa,CAAC,EAAE,aAAa,CAAC;IACvC,iEAAiE;IACjE,QAAQ,CAAC,YAAY,EAAE,OAAO,CAAC;IAC/B,uDAAuD;IACvD,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;IACjC,4DAA4D;IAC5D,QAAQ,CAAC,WAAW,CAAC,EAAE,CACrB,GAAG,EAAE,OAAO,KACT;QAAE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;KAAE,GAAG,SAAS,CAAC;IAC9F;;;;;OAKG;IACH,QAAQ,CAAC,eAAe,EAAE,qBAAqB,CAAC;CACjD;AAyBD;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,YAAY,GAAG,IAAI,CA4V1D"}
@@ -0,0 +1,359 @@
1
+ /**
2
+ * MCP wire endpoints — the audience-routed JSON-RPC surfaces.
3
+ *
4
+ * POST <universalMcpPath> — agent+runtime tools (default `/mcp`)
5
+ * POST <pathPrefix>/:appId — per-tenant variant (opt-in via
6
+ * `perAppRouting`)
7
+ * POST /protocol — design-time spec/discovery tools
8
+ * POST /ops — operator-class management tools
9
+ * POST <service.path> — isolated MCP services (path IS
10
+ * the audience)
11
+ * GET/DELETE on each — 405 (stateless server; no
12
+ * streaming continuation /
13
+ * session-terminate verbs)
14
+ *
15
+ * Every route shares ONE request pipeline (`makeMcpHandler`): resolve
16
+ * identity via the AuthAdapter (anonymous services synthesize a
17
+ * builder identity on missing/invalid bearers), apply the per-app
18
+ * authorize hook, build a fresh `McpServer` + Streamable HTTP
19
+ * transport per request (stateless), and dispatch under the
20
+ * AsyncLocalStorage-scoped `HandlerContext`. The difference between
21
+ * routes is ONLY the handler set each exposes.
22
+ *
23
+ * See `docs/development/audience-routes.md` for the audience taxonomy
24
+ * (`agent` / `runtime` / `protocol` / `ops`) and the wire-name prefix
25
+ * rules.
26
+ */
27
+ import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
28
+ import { randomUUID } from "node:crypto";
29
+ import { resolveIdentity, UnauthenticatedError } from "./auth.js";
30
+ import { buildMcpServer } from "./build-mcp.js";
31
+ import { buildWwwAuthenticate, resolveIssuerUrl } from "./oauth.js";
32
+ /**
33
+ * Resolve the resource path that `WWW-Authenticate` should point at
34
+ * for the current request. Per-app `/mcp` requests
35
+ * get `${pathPrefix}/${appId}` so RFC 9728 discovery resolves to the
36
+ * per-app metadata; universal-route requests get `''` which collapses
37
+ * back to the universal `${issuer}/.well-known/oauth-protected-resource`.
38
+ *
39
+ * Defense in depth: even when `perAppRouting` is configured, we
40
+ * reject empty or whitespace-only `appId` values rather than emitting
41
+ * an obviously-wrong `${pathPrefix}//.well-known/...` URL — falling
42
+ * back to universal is the safer behavior.
43
+ */
44
+ function resolveWwwAuthResourcePath(req, perAppRouting) {
45
+ if (perAppRouting === undefined)
46
+ return "";
47
+ const { paramName, pathPrefix = "" } = perAppRouting;
48
+ const appId = req.params[paramName];
49
+ if (typeof appId !== "string" || appId.length === 0)
50
+ return "";
51
+ return `${pathPrefix}/${appId}`;
52
+ }
53
+ /**
54
+ * Mount the universal / per-app / protocol / ops / service MCP
55
+ * endpoints onto the express app. Returns nothing — the routes
56
+ * self-register.
57
+ */
58
+ export function mountMcpEndpoints(opts) {
59
+ const { app, logger, auth, info, handlers, mcpServices, als, appIdFromIdentity, universalMcpPath, perAppRouting, oauthEnabled, oauthIssuerUrl, errorMapper, buildMcpOptions, } = opts;
60
+ /**
61
+ * Audience filter — returns the subset of `handlers` whose
62
+ * `audience` tag intersects `allowed`. Read on route mounting so
63
+ * each MCP route exposes only its audience's tools.
64
+ *
65
+ * Handlers with `audience: undefined` default to ['agent'] — every
66
+ * such handler is agent-runtime callable.
67
+ */
68
+ const filterHandlersByAudience = (set, allowed) => set.filter((h) => {
69
+ const tags = h.audience ?? ["agent"];
70
+ return tags.some((t) => allowed.includes(t));
71
+ });
72
+ const makeMcpHandler = (routeHandlers, handlerOpts) => async (req, res) => {
73
+ const requestId = typeof req.headers["x-request-id"] === "string"
74
+ ? req.headers["x-request-id"]
75
+ : randomUUID();
76
+ const reqLogger = logger.child({ requestId });
77
+ // Auth is OPTIONAL on anonymous services and REQUIRED otherwise.
78
+ // Always attempt to resolve a presented credential: an anonymous
79
+ // service with a valid bearer still resolves to the real identity
80
+ // (so it can offer authenticated capabilities — e.g. `/dev`'s ops
81
+ // tools read `ctx.userId`), while a missing/unauthenticated
82
+ // credential falls back to the synthesized anonymous builder so
83
+ // public reads (docs, protocol) work bearer-less. This is what makes
84
+ // `source: 'anonymous'` distinguishable from an authenticated caller,
85
+ // per the `McpService.anonymous` contract — resolving a present
86
+ // bearer is the only way a handler can tell the two apart.
87
+ let identity;
88
+ try {
89
+ identity = await resolveIdentity(auth, req);
90
+ }
91
+ catch (err) {
92
+ if (err instanceof UnauthenticatedError) {
93
+ if (handlerOpts?.anonymous) {
94
+ identity = { identity: { kind: "builder" }, source: "anonymous" };
95
+ }
96
+ else {
97
+ // Diagnostic: did a Bearer credential actually reach the pod on
98
+ // this request? This splits "the client/transport never sent one"
99
+ // from "we received it but the adapter rejected it" — otherwise
100
+ // indistinguishable in `auth_failed`. Only a short, non-secret
101
+ // prefix is logged (a JWT header is public), never the credential.
102
+ const rawAuth = req.headers["authorization"];
103
+ const authHeaderPresent = typeof rawAuth === "string" && rawAuth.length > 0;
104
+ reqLogger.warn("auth_failed", {
105
+ reason: err.message,
106
+ authHeaderPresent,
107
+ authHeaderPrefix: authHeaderPresent ? rawAuth.slice(0, 12) : null,
108
+ // Bearer-stripped token length — a transit-truncation check
109
+ // (compare against the minted length); never the credential.
110
+ tokenLen: authHeaderPresent
111
+ ? rawAuth.replace(/^Bearer\s+/i, "").length
112
+ : 0,
113
+ path: req.path,
114
+ });
115
+ // OAuth-discovery clients (Claude Desktop, claude.ai, etc.)
116
+ // read this header to find the resource-metadata URL and
117
+ // begin the OAuth dance. Pure-bearer clients ignore it.
118
+ //
119
+ // Per-app routes point at the per-app resource-metadata
120
+ // document so RFC 9728 discovery resolves
121
+ // to a per-app `resource` URL. Universal routes keep the
122
+ // bare metadata path.
123
+ if (oauthEnabled) {
124
+ const wwwAuthResourcePath = resolveWwwAuthResourcePath(req, perAppRouting);
125
+ res.setHeader("WWW-Authenticate", buildWwwAuthenticate(resolveIssuerUrl(req, oauthIssuerUrl), wwwAuthResourcePath));
126
+ }
127
+ res.status(401).json({
128
+ jsonrpc: "2.0",
129
+ error: { code: -32000, message: err.message },
130
+ id: null,
131
+ });
132
+ return;
133
+ }
134
+ }
135
+ else {
136
+ reqLogger.error("auth_unexpected_error", { error: String(err) });
137
+ res.status(500).json({
138
+ jsonrpc: "2.0",
139
+ error: { code: -32603, message: "Internal server error" },
140
+ id: null,
141
+ });
142
+ return;
143
+ }
144
+ }
145
+ // Operator-surface guard: federated end-user identities (source:'oidc',
146
+ // minted by the OIDC verify adapter) must never reach operator-class
147
+ // routes (/ops) or design-time spec routes (/protocol). Audience
148
+ // filtering only shapes tools/list; it does NOT stop a direct
149
+ // tools/call, so this is a route-level authorization gate.
150
+ if (handlerOpts?.rejectFederated && identity.source === "oidc") {
151
+ reqLogger.warn("federated_identity_rejected", { route: req.path });
152
+ res.status(403).json({
153
+ jsonrpc: "2.0",
154
+ error: { code: -32000, message: "federated identities are not permitted on this route" },
155
+ id: null,
156
+ });
157
+ return;
158
+ }
159
+ // Per-tenant URL routing. When `perAppRouting`
160
+ // is configured AND the request matched the per-app path
161
+ // `/:${paramName}/mcp`, Express populates `req.params[paramName]`
162
+ // with the validated tenant id. Use it as `ctx.appId` for this
163
+ // request, overriding `appIdFromIdentity`. The universal `/mcp`
164
+ // route doesn't have the param so it falls through to the
165
+ // identity-based resolution.
166
+ const urlAppId = perAppRouting !== undefined ? req.params[perAppRouting.paramName] : undefined;
167
+ const hasUrlAppId = typeof urlAppId === "string" && urlAppId.length > 0;
168
+ // Per-app authorize hook — when the deployment configured
169
+ // `perAppRouting.authorize` AND the request matched the per-app
170
+ // path, invoke the callback. Throwing collapses to a 403 before
171
+ // the MCP handler ever sees the request, which is the boundary
172
+ // that prevents cross-user blueprint reads when pod tools bypass
173
+ // AppSync owner-auth via raw DDB. Universal-endpoint requests
174
+ // skip this entirely (no urlAppId).
175
+ if (hasUrlAppId && perAppRouting?.authorize) {
176
+ try {
177
+ await perAppRouting.authorize(urlAppId, identity);
178
+ }
179
+ catch (err) {
180
+ reqLogger.warn("per_app_authorize_denied", {
181
+ urlAppId,
182
+ reason: err instanceof Error ? err.message : String(err),
183
+ });
184
+ res.status(403).json({
185
+ jsonrpc: "2.0",
186
+ error: { code: -32000, message: "Forbidden" },
187
+ id: null,
188
+ });
189
+ return;
190
+ }
191
+ }
192
+ const ctx = {
193
+ appId: hasUrlAppId ? urlAppId : appIdFromIdentity(identity),
194
+ requestId,
195
+ // Identity is the canonical source of two mutually-exclusive
196
+ // hosted fields: `apiKeyHash` for kind=app, `userId` for kind=user.
197
+ // Threading them onto HandlerContext here means hosted handlers
198
+ // (the K8s ggui-protocol pod's billing gate + per-user blueprint
199
+ // scoping) can read identity directly without a parallel pod-only
200
+ // context shape; OSS handlers continue to ignore both fields.
201
+ ...(identity.identity.kind === "app" ? { apiKeyHash: identity.identity.apiKeyHash } : {}),
202
+ ...(identity.identity.kind === "user" ? { userId: identity.identity.userId } : {}),
203
+ };
204
+ reqLogger.debug?.("mcp_request", { appId: ctx.appId });
205
+ const mcp = buildMcpServer(info, routeHandlers, () => als.getStore() ?? ctx, reqLogger, {
206
+ ...buildMcpOptions,
207
+ });
208
+ const transport = new StreamableHTTPServerTransport({
209
+ sessionIdGenerator: undefined,
210
+ });
211
+ res.on("close", () => {
212
+ transport.close().catch(() => undefined);
213
+ mcp.close().catch(() => undefined);
214
+ });
215
+ try {
216
+ await mcp.connect(transport);
217
+ await als.run(ctx, () => transport.handleRequest(req, res, req.body));
218
+ }
219
+ catch (err) {
220
+ reqLogger.error("mcp_handle_failed", { error: String(err) });
221
+ if (!res.headersSent) {
222
+ let mapped;
223
+ if (errorMapper) {
224
+ try {
225
+ mapped = errorMapper(err);
226
+ }
227
+ catch (mapperErr) {
228
+ // Defensive: a thrown mapper degrades to the default 500
229
+ // rather than letting the inner failure escape the handler.
230
+ reqLogger.warn("error_mapper_failed", {
231
+ error: String(mapperErr),
232
+ });
233
+ }
234
+ }
235
+ if (mapped) {
236
+ res.status(mapped.status).json({
237
+ jsonrpc: "2.0",
238
+ error: { code: mapped.code, message: mapped.message },
239
+ id: null,
240
+ });
241
+ }
242
+ else {
243
+ res.status(500).json({
244
+ jsonrpc: "2.0",
245
+ error: { code: -32603, message: "Internal server error" },
246
+ id: null,
247
+ });
248
+ }
249
+ }
250
+ }
251
+ };
252
+ const methodNotAllowed = (_req, res) => {
253
+ res.status(405).json({
254
+ jsonrpc: "2.0",
255
+ error: {
256
+ code: -32000,
257
+ message: "Method not allowed (stateless server).",
258
+ },
259
+ id: null,
260
+ });
261
+ };
262
+ // Three audience-filtered handler sets. Each set is the
263
+ // subset of `handlers` whose `audience` tag intersects the route's
264
+ // allowed list. Handlers without an explicit tag default to
265
+ // ['agent'] — every such handler is agent-runtime callable.
266
+ const agentRouteHandlers = filterHandlersByAudience(handlers, ["agent", "runtime"]);
267
+ const protocolRouteHandlers = filterHandlersByAudience(handlers, ["protocol"]);
268
+ const opsRouteHandlers = filterHandlersByAudience(handlers, ["ops"]);
269
+ const agentMcpHandler = makeMcpHandler(agentRouteHandlers);
270
+ const protocolMcpHandler = makeMcpHandler(protocolRouteHandlers, { rejectFederated: true });
271
+ const opsMcpHandler = makeMcpHandler(opsRouteHandlers, { rejectFederated: true });
272
+ // Universal endpoint — `appId` resolved from the auth identity via
273
+ // `appIdFromIdentity`. Cloud `mcp.ggui.ai` deployments resolve this
274
+ // to `User.defaultAppId` via the auth-adapter; OSS deployments fall
275
+ // through to userId / DEFAULT_BUILDER_APP_ID.
276
+ //
277
+ // Path defaults to `/mcp` (Streamable HTTP convention). Cloud
278
+ // `mcp.ggui.ai` overrides to `/` so the bare-root URL is the
279
+ // universal endpoint — domain already says "mcp", no path repeat.
280
+ // Exposes audience tags ['agent', 'runtime'] — runtime tools stay
281
+ // routable on the same endpoint but invisible to the agent's
282
+ // `tools/list` via the `_meta.ui.visibility: ['app']` filter.
283
+ app.post(universalMcpPath, agentMcpHandler);
284
+ // Per-tenant endpoint — only mounted when the deployment opts in
285
+ // via `perAppRouting`. The same handler reads `req.params[paramName]`
286
+ // and uses it as `ctx.appId` for the request.
287
+ //
288
+ // When `pathPrefix` is set, the route mounts at
289
+ // `${pathPrefix}/:${paramName}` — cloud uses `/apps` so URLs are
290
+ // `mcp.ggui.ai/apps/<appId>`. The prefix segments per-tenant traffic
291
+ // from system routes (`/health`, `/oauth/*`, `/.well-known/*`,
292
+ // `/r/*`) so an opaque appId can never shadow a future static path.
293
+ //
294
+ // Without `pathPrefix`, the route mounts bare. The `paramPattern`
295
+ // constraint is the only collision defense — fine when the pattern
296
+ // guarantees non-collision (e.g. UUIDs).
297
+ //
298
+ // `path-to-regexp` v8 (express@5) dropped the `:param(pattern)`
299
+ // inline-regex syntax, so the pattern is enforced via a single
300
+ // `app.param` validator (anchored full-match) rather than baked into
301
+ // the route string. Registered once here; Express resolves it at
302
+ // dispatch for EVERY route declaring `paramName` — the per-app
303
+ // well-known route in the OAuth family AND this MCP route —
304
+ // regardless of registration order. A value failing the pattern
305
+ // 404s before any handler runs.
306
+ if (perAppRouting !== undefined) {
307
+ const { paramName, paramPattern, pathPrefix } = perAppRouting;
308
+ const appIdPattern = new RegExp(`^(?:${paramPattern})$`);
309
+ app.param(paramName, (_req, res, next, val) => {
310
+ if (typeof val !== "string" || !appIdPattern.test(val)) {
311
+ res.status(404).json({ error: "not_found" });
312
+ return;
313
+ }
314
+ next();
315
+ });
316
+ const route = pathPrefix !== undefined ? `${pathPrefix}/:${paramName}` : `/:${paramName}`;
317
+ app.post(route, agentMcpHandler);
318
+ }
319
+ // /protocol — design-time spec/discovery surface.
320
+ // Hosts the `audience: ['protocol']` tools (`ggui_describe_*`,
321
+ // `ggui_get_example_blueprints`, `ggui_validate_blueprint`,
322
+ // `ggui_list_available_primitives`, `ggui_get_blueprint_boilerplate`).
323
+ // Strips spec-discovery noise off the agent's runtime `tools/list`
324
+ // — agents that need format docs hit `/protocol` explicitly.
325
+ // Always mounted; the route has the same auth chain as `/mcp` for
326
+ // v1 (operators MAY narrow auth in their own middleware later).
327
+ // Empty when the deployment didn't wire any protocol-tagged handlers.
328
+ app.post("/protocol", protocolMcpHandler);
329
+ app.get("/protocol", methodNotAllowed);
330
+ app.delete("/protocol", methodNotAllowed);
331
+ // /ops — operator-class management surface. Hosts the
332
+ // `audience: ['ops']` tools (`ggui_set_provider_key`, `ggui_get_credit_balance`,
333
+ // etc.). Always mounted; same auth chain as `/mcp`. Empty when the
334
+ // deployment didn't wire any ops-tagged handlers.
335
+ app.post("/ops", opsMcpHandler);
336
+ app.get("/ops", methodNotAllowed);
337
+ app.delete("/ops", methodNotAllowed);
338
+ // Isolated MCP services — each at its own HTTP path with its own
339
+ // tool namespace. Bypasses audience filtering (the path IS the
340
+ // audience). Each service builds its own MCP request handler via
341
+ // `makeMcpHandler(svc.handlers)`, reusing the same auth chain +
342
+ // identity-resolution as the canonical routes — the difference is
343
+ // ONLY the handler set the route exposes.
344
+ //
345
+ // Validation already ran in the composer via `validateMcpServices`;
346
+ // here we just iterate the validated list.
347
+ for (const svc of mcpServices) {
348
+ // `anonymous: true` skips the auth chain and synthesizes a
349
+ // builder-kind identity with `source: 'anonymous'`. Default
350
+ // (undefined / false) preserves the auth-required posture of
351
+ // every canonical route.
352
+ const svcMcpHandler = makeMcpHandler(svc.handlers, svc.anonymous ? { anonymous: true } : undefined);
353
+ app.post(svc.path, svcMcpHandler);
354
+ app.get(svc.path, methodNotAllowed);
355
+ app.delete(svc.path, methodNotAllowed);
356
+ }
357
+ app.get(universalMcpPath, methodNotAllowed);
358
+ app.delete(universalMcpPath, methodNotAllowed);
359
+ }
@@ -20,45 +20,8 @@
20
20
  * `buildMcpServer` registration, `toolCount`, telemetry, and logging
21
21
  * all Just Work.
22
22
  */
23
- import type { HandlerContext, SharedHandler } from "@ggui-ai/mcp-server-handlers";
23
+ import type { SharedHandler } from "@ggui-ai/mcp-server-handlers";
24
24
  import type { ZodRawShape } from "zod";
25
- import type { WiredActionContext, WiredActionRouter } from "./render-channel.js";
26
- /**
27
- * Runtime ctx the mount-router hands the mount handler. Structurally a
28
- * superset of `HandlerContext` (so the mount's `handler(input, ctx)`
29
- * signature stays unchanged) PLUS the wired-action-only fields from
30
- * {@link WiredActionContext}.
31
- *
32
- * Why the type is exported: TS-authored mount tools that want to read
33
- * `ctx.sendPropsUpdate` / `ctx.renderId` import this and narrow their
34
- * `handler` parameter (e.g. `async handler(input, ctx) { const wired =
35
- * ctx as WiredMountContext; … }`). JS-authored mounts (.mjs) read the
36
- * fields structurally — they're present on the runtime object whether
37
- * or not the static type knows about them.
38
- *
39
- * The static `SharedHandler.handler` signature deliberately stays
40
- * narrow on `HandlerContext`. Widening that type would force every
41
- * shared handler (ggui-native + mounted) to acknowledge a wired-only
42
- * surface, even handlers that never run through the wired-action path
43
- * (e.g. `ggui_render`, blueprint search). The structural superset here
44
- * keeps the canonical contract narrow without sacrificing access for
45
- * mount tools that opt in.
46
- */
47
- /**
48
- * Intersection (not interface-extension) because `HandlerContext` declares
49
- * `renderId?` as optional — the canonical context shape any handler may
50
- * see — whereas `WiredActionContext` declares it as required (the
51
- * wired-action dispatcher always knows the active render at invocation
52
- * time). Interface-extends rejects "narrowing optional → required" via
53
- * TS2320 ("cannot simultaneously extend"), but an intersection composes
54
- * the two perfectly: optional ∧ required ≡ required.
55
- *
56
- * Surface for consumers stays identical — a TS-authored mount that types
57
- * its `handler` parameter as `WiredMountContext` reads `renderId: string`
58
- * (no `| undefined`) + every `HandlerContext` field
59
- * (`appId`, `requestId`, optional `apiKeyHash`).
60
- */
61
- export type WiredMountContext = Omit<HandlerContext, "renderId"> & WiredActionContext;
62
25
  /**
63
26
  * One named bundle of tool handlers the server should aggregate onto
64
27
  * its MCP surface.
@@ -111,7 +74,7 @@ export declare function validateServicePath(p: string): ServicePath;
111
74
  * MCP server with its own tool namespace.
112
75
  *
113
76
  * Use a **service** when the handler set is conceptually a distinct
114
- * MCP server (`mcp.ggui.ai/docs`, `mcp.ggui.ai/playground/todos`).
77
+ * MCP server (e.g. `<host>/docs`, `<host>/playground/todos`).
115
78
  * Use a **mount** when the handlers should appear alongside
116
79
  * ggui-native tools on the shared `/mcp` surface (fixtures, external
117
80
  * MCPs aggregated for one session's view).
@@ -200,41 +163,4 @@ export declare function validateMcpServices(services: ReadonlyArray<McpService>
200
163
  * `mounts` is empty, so callers never mutate the input reference.
201
164
  */
202
165
  export declare function composeHandlersWithMounts(baseHandlers: ReadonlyArray<SharedHandler<ZodRawShape, ZodRawShape>>, mounts: ReadonlyArray<McpServerMount> | undefined): ReadonlyArray<SharedHandler<ZodRawShape, ZodRawShape>>;
203
- /**
204
- * Build a {@link WiredActionRouter} that dispatches wired-action
205
- * hits to the matching mount handler's `handler(input, ctx)`. Zero-config composition for
206
- * OSS `ggui serve` — when the operator declares `ggui.json#mcpMounts`,
207
- * every mounted tool automatically becomes wire-dispatchable from
208
- * a generated UI's `useAction` without additional glue.
209
- *
210
- * Ownership + scoping:
211
- * - Only MOUNT handlers participate. ggui-native handlers
212
- * (`ggui_render`, `ggui_handshake`, etc.) are platform tools and
213
- * deliberately NOT exposed as wire-dispatchable actions — a
214
- * component that tried to dispatch `ggui_render` would bypass the
215
- * agentic-loop contract.
216
- * - Name collisions across mounts are prevented at aggregation
217
- * time by {@link composeHandlersWithMounts}, so the first-match
218
- * lookup here is safe.
219
- *
220
- * Context synthesis:
221
- * - Each invocation gets a fresh {@link HandlerContext} with the
222
- * caller-supplied `appId` (typically the session's appId) + a
223
- * fresh request id. Mount handlers that read from storage scope
224
- * to this appId, matching the `/mcp` ingress behavior.
225
- * - The session-channel dispatcher additionally hands a
226
- * {@link WiredActionContext}. The runtime ctx the mount handler
227
- * sees is a structural superset
228
- * ({@link WiredMountContext}) so a JS-authored mount can call
229
- * `ctx.sendPropsUpdate({...})` directly (closed over `ctx.renderId`).
230
- * The static
231
- * `SharedHandler.handler(input, ctx: HandlerContext)` shape stays
232
- * untouched — tooling that doesn't read the wired fields keeps its
233
- * existing types.
234
- *
235
- * Returns `null` when `mounts` is empty/absent, signaling the
236
- * composer to OMIT the `wiredActionRouter` opt entirely so servers
237
- * with no mounts behave as if the router did not exist.
238
- */
239
- export declare function composeWiredActionRouterFromMounts(mounts: ReadonlyArray<McpServerMount> | undefined, resolveContext: () => HandlerContext): WiredActionRouter | null;
240
166
  //# sourceMappingURL=mcp-mounts.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"mcp-mounts.d.ts","sourceRoot":"","sources":["../src/mcp-mounts.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,OAAO,KAAK,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAClF,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,KAAK,CAAC;AACvC,OAAO,KAAK,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAEjF;;;;;;;;;;;;;;;;;;;;GAoBG;AACH;;;;;;;;;;;;;GAaG;AACH,MAAM,MAAM,iBAAiB,GAAG,IAAI,CAAC,cAAc,EAAE,UAAU,CAAC,GAAG,kBAAkB,CAAC;AAEtF;;;;;;;;GAQG;AACH,MAAM,WAAW,cAAc;IAC7B;;;;;OAKG;IACH,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB;;;;;;OAMG;IACH,QAAQ,CAAC,QAAQ,EAAE,aAAa,CAAC,aAAa,CAAC,WAAW,EAAE,WAAW,CAAC,CAAC,CAAC;CAC3E;AAED;;;GAGG;AACH,MAAM,MAAM,WAAW,GAAG,MAAM,GAAG;IAAE,QAAQ,CAAC,OAAO,EAAE,aAAa,CAAA;CAAE,CAAC;AAsBvE;;;;;;;;;;GAUG;AACH,wBAAgB,mBAAmB,CAAC,CAAC,EAAE,MAAM,GAAG,WAAW,CAiB1D;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,MAAM,WAAW,UAAU;IACzB;;;;OAIG;IACH,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB;;;;OAIG;IACH,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB;;;;OAIG;IACH,QAAQ,CAAC,QAAQ,EAAE,aAAa,CAAC,aAAa,CAAC,WAAW,EAAE,WAAW,CAAC,CAAC,CAAC;IAC1E;;;;;;;;;;;;;;;;OAgBG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,CAAC;CAC9B;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,aAAa,CAAC,UAAU,CAAC,GAAG,SAAS,GAC9C,aAAa,CAAC,UAAU,CAAC,CAyC3B;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,yBAAyB,CACvC,YAAY,EAAE,aAAa,CAAC,aAAa,CAAC,WAAW,EAAE,WAAW,CAAC,CAAC,EACpE,MAAM,EAAE,aAAa,CAAC,cAAc,CAAC,GAAG,SAAS,GAChD,aAAa,CAAC,aAAa,CAAC,WAAW,EAAE,WAAW,CAAC,CAAC,CAoDxD;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AACH,wBAAgB,kCAAkC,CAChD,MAAM,EAAE,aAAa,CAAC,cAAc,CAAC,GAAG,SAAS,EACjD,cAAc,EAAE,MAAM,cAAc,GACnC,iBAAiB,GAAG,IAAI,CAyC1B"}
1
+ {"version":3,"file":"mcp-mounts.d.ts","sourceRoot":"","sources":["../src/mcp-mounts.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAClE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,KAAK,CAAC;AAEvC;;;;;;;;GAQG;AACH,MAAM,WAAW,cAAc;IAC7B;;;;;OAKG;IACH,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB;;;;;;OAMG;IACH,QAAQ,CAAC,QAAQ,EAAE,aAAa,CAAC,aAAa,CAAC,WAAW,EAAE,WAAW,CAAC,CAAC,CAAC;CAC3E;AAED;;;GAGG;AACH,MAAM,MAAM,WAAW,GAAG,MAAM,GAAG;IAAE,QAAQ,CAAC,OAAO,EAAE,aAAa,CAAA;CAAE,CAAC;AAsBvE;;;;;;;;;;GAUG;AACH,wBAAgB,mBAAmB,CAAC,CAAC,EAAE,MAAM,GAAG,WAAW,CAiB1D;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,MAAM,WAAW,UAAU;IACzB;;;;OAIG;IACH,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB;;;;OAIG;IACH,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB;;;;OAIG;IACH,QAAQ,CAAC,QAAQ,EAAE,aAAa,CAAC,aAAa,CAAC,WAAW,EAAE,WAAW,CAAC,CAAC,CAAC;IAC1E;;;;;;;;;;;;;;;;OAgBG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,CAAC;CAC9B;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,aAAa,CAAC,UAAU,CAAC,GAAG,SAAS,GAC9C,aAAa,CAAC,UAAU,CAAC,CAyC3B;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,yBAAyB,CACvC,YAAY,EAAE,aAAa,CAAC,aAAa,CAAC,WAAW,EAAE,WAAW,CAAC,CAAC,EACpE,MAAM,EAAE,aAAa,CAAC,cAAc,CAAC,GAAG,SAAS,GAChD,aAAa,CAAC,aAAa,CAAC,WAAW,EAAE,WAAW,CAAC,CAAC,CAoDxD"}
@@ -140,79 +140,3 @@ export function composeHandlersWithMounts(baseHandlers, mounts) {
140
140
  }
141
141
  return aggregated;
142
142
  }
143
- /**
144
- * Build a {@link WiredActionRouter} that dispatches wired-action
145
- * hits to the matching mount handler's `handler(input, ctx)`. Zero-config composition for
146
- * OSS `ggui serve` — when the operator declares `ggui.json#mcpMounts`,
147
- * every mounted tool automatically becomes wire-dispatchable from
148
- * a generated UI's `useAction` without additional glue.
149
- *
150
- * Ownership + scoping:
151
- * - Only MOUNT handlers participate. ggui-native handlers
152
- * (`ggui_render`, `ggui_handshake`, etc.) are platform tools and
153
- * deliberately NOT exposed as wire-dispatchable actions — a
154
- * component that tried to dispatch `ggui_render` would bypass the
155
- * agentic-loop contract.
156
- * - Name collisions across mounts are prevented at aggregation
157
- * time by {@link composeHandlersWithMounts}, so the first-match
158
- * lookup here is safe.
159
- *
160
- * Context synthesis:
161
- * - Each invocation gets a fresh {@link HandlerContext} with the
162
- * caller-supplied `appId` (typically the session's appId) + a
163
- * fresh request id. Mount handlers that read from storage scope
164
- * to this appId, matching the `/mcp` ingress behavior.
165
- * - The session-channel dispatcher additionally hands a
166
- * {@link WiredActionContext}. The runtime ctx the mount handler
167
- * sees is a structural superset
168
- * ({@link WiredMountContext}) so a JS-authored mount can call
169
- * `ctx.sendPropsUpdate({...})` directly (closed over `ctx.renderId`).
170
- * The static
171
- * `SharedHandler.handler(input, ctx: HandlerContext)` shape stays
172
- * untouched — tooling that doesn't read the wired fields keeps its
173
- * existing types.
174
- *
175
- * Returns `null` when `mounts` is empty/absent, signaling the
176
- * composer to OMIT the `wiredActionRouter` opt entirely so servers
177
- * with no mounts behave as if the router did not exist.
178
- */
179
- export function composeWiredActionRouterFromMounts(mounts, resolveContext) {
180
- if (!mounts || mounts.length === 0)
181
- return null;
182
- const byName = new Map();
183
- for (const mount of mounts) {
184
- for (const h of mount.handlers) {
185
- // First-write-wins matches `composeHandlersWithMounts`'s
186
- // collision-rejection order; the compose path throws before
187
- // reaching this builder, so a duplicate here is dead code.
188
- if (!byName.has(h.name))
189
- byName.set(h.name, h);
190
- }
191
- }
192
- return {
193
- has(toolName) {
194
- return byName.has(toolName);
195
- },
196
- async invoke(toolName, input, wiredCtx) {
197
- const handler = byName.get(toolName);
198
- if (!handler) {
199
- // Unreachable in normal use — the session channel has()-gates
200
- // before calling invoke. Thrown errors surface as TOOL_THREW
201
- // envelopes, so the caller still gets a canonical shape.
202
- throw new Error(`wiredActionRouter(mounts): no handler registered for '${toolName}'`);
203
- }
204
- // Synthesize the runtime ctx — structural superset of
205
- // HandlerContext + WiredActionContext. The static
206
- // `SharedHandler.handler` accepts `HandlerContext`; mounts that
207
- // need the wired fields read them off the same `ctx` argument
208
- // (TS via `WiredMountContext`, JS structurally).
209
- const baseCtx = resolveContext();
210
- const ctx = {
211
- ...baseCtx,
212
- renderId: wiredCtx.renderId,
213
- sendPropsUpdate: wiredCtx.sendPropsUpdate,
214
- };
215
- return handler.handler(input, ctx);
216
- },
217
- };
218
- }