@ggui-ai/mcp-server 0.3.0-rc.0 → 0.5.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.
- package/dist/build-mcp.d.ts +1 -1
- package/dist/build-mcp.d.ts.map +1 -1
- package/dist/build-mcp.js +25 -0
- package/dist/console-chat-routes.d.ts +1 -1
- package/dist/console-chat-routes.d.ts.map +1 -1
- package/dist/console-chat-routes.js +22 -0
- package/dist/control-service.d.ts +160 -0
- package/dist/control-service.d.ts.map +1 -0
- package/dist/control-service.js +220 -0
- package/dist/index.d.ts +5 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -2
- package/dist/mcp-endpoint-routes.d.ts +25 -11
- package/dist/mcp-endpoint-routes.d.ts.map +1 -1
- package/dist/mcp-endpoint-routes.js +62 -65
- package/dist/mcp-mounts.d.ts +23 -9
- package/dist/mcp-mounts.d.ts.map +1 -1
- package/dist/mcp-mounts.js +31 -18
- package/dist/server.d.ts +67 -39
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +58 -63
- package/package.json +13 -13
package/dist/build-mcp.d.ts
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
*/
|
|
11
11
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
12
12
|
import { type ZodRawShape } from 'zod';
|
|
13
|
-
import type
|
|
13
|
+
import { type HandlerContext, type SharedHandler } from '@ggui-ai/mcp-server-handlers';
|
|
14
14
|
import type { Logger } from './logger.js';
|
|
15
15
|
import { type GguiRenderResourceTemplateOptions } from './mcp-apps-outbound.js';
|
|
16
16
|
export interface ServerInfo {
|
package/dist/build-mcp.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"build-mcp.d.ts","sourceRoot":"","sources":["../src/build-mcp.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAIpE,OAAO,EAAK,KAAK,WAAW,EAAE,MAAM,KAAK,CAAC;AAC1C,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"build-mcp.d.ts","sourceRoot":"","sources":["../src/build-mcp.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAIpE,OAAO,EAAK,KAAK,WAAW,EAAE,MAAM,KAAK,CAAC;AAC1C,OAAO,EAEL,KAAK,cAAc,EACnB,KAAK,aAAa,EACnB,MAAM,8BAA8B,CAAC;AACtC,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EAEL,KAAK,iCAAiC,EACvC,MAAM,wBAAwB,CAAC;AAEhC,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;CAC/B;AAED,MAAM,WAAW,qBAAqB;IACpC;;;;;;;;;;OAUG;IACH,QAAQ,CAAC,eAAe,CAAC,EAAE,OAAO,CAAC;IACnC;;;;OAIG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B;;;;;;;;;;OAUG;IACH,QAAQ,CAAC,aAAa,CAAC,EAAE,iCAAiC,CAAC;IAC3D;;;;;;;;;OASG;IACH,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAChC;;;;;;;;;;;;;;;;;;OAkBG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,aAAa,CAAC,KAAK,GAAG,MAAM,GAAG,SAAS,CAAC,CAAC;IAElE;;;;;;;;;;;;;;OAcG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAE/B;;;;;;;;;;;;;;;;;;OAkBG;IACH,QAAQ,CAAC,cAAc,CAAC,EAAE,aAAa,CAAC,CAAC,MAAM,EAAE,SAAS,KAAK,IAAI,CAAC,CAAC;CACtE;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAC5B,IAAI,EAAE,UAAU,EAChB,QAAQ,EAAE,aAAa,CAAC,aAAa,CAAC,WAAW,EAAE,WAAW,CAAC,CAAC,EAChE,UAAU,EAAE,MAAM,cAAc,EAChC,MAAM,EAAE,MAAM,EACd,IAAI,GAAE,qBAA0B,GAC/B,SAAS,CAiLX"}
|
package/dist/build-mcp.js
CHANGED
|
@@ -12,6 +12,7 @@ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
|
12
12
|
import { registerAppTool } from '@modelcontextprotocol/ext-apps/server';
|
|
13
13
|
import { isRecord } from '@ggui-ai/protocol';
|
|
14
14
|
import { z } from 'zod';
|
|
15
|
+
import { isHandlerFailure, } from '@ggui-ai/mcp-server-handlers';
|
|
15
16
|
import { installMcpAppsOutbound, } from './mcp-apps-outbound.js';
|
|
16
17
|
/**
|
|
17
18
|
* Build a fresh MCP server with every handler registered.
|
|
@@ -87,6 +88,30 @@ export function buildMcpServer(info, handlers, getContext, logger, opts = {}) {
|
|
|
87
88
|
const start = Date.now();
|
|
88
89
|
try {
|
|
89
90
|
const data = await handler.handler(input, ctx);
|
|
91
|
+
// First-class in-result failure channel. A handler that
|
|
92
|
+
// returns the `HandlerFailure` marker gets an `isError: true`
|
|
93
|
+
// TOOL RESULT (never a thrown/JSON-RPC error): the marker's
|
|
94
|
+
// `errorText` is the model-visible content, and its `data` is
|
|
95
|
+
// validated against the SAME outputSchema as a success — MCP
|
|
96
|
+
// SDK clients validate structuredContent against outputSchema
|
|
97
|
+
// even when isError is set, so the envelope stays
|
|
98
|
+
// schema-conformant. NO `_meta` on failures: `resultMeta` is
|
|
99
|
+
// not invoked, so no mount affordance / bootstrap slice is
|
|
100
|
+
// emitted for a failed call.
|
|
101
|
+
if (isHandlerFailure(data)) {
|
|
102
|
+
const validated = z.object(handler.outputSchema).parse(data.data);
|
|
103
|
+
logger.warn('tool_invoked', {
|
|
104
|
+
tool: handler.name,
|
|
105
|
+
appId: ctx.appId,
|
|
106
|
+
outcome: 'tool_error',
|
|
107
|
+
elapsedMs: Date.now() - start,
|
|
108
|
+
});
|
|
109
|
+
return {
|
|
110
|
+
isError: true,
|
|
111
|
+
structuredContent: validated,
|
|
112
|
+
content: [{ type: 'text', text: data.errorText }],
|
|
113
|
+
};
|
|
114
|
+
}
|
|
90
115
|
const validated = z.object(handler.outputSchema).parse(data);
|
|
91
116
|
// Per-result `_meta` — NOT merged into structuredContent, so
|
|
92
117
|
// agents that typecheck against the tool signature never see
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
* the exact Lane-1 chat-page spec assertion (`/OSS agent
|
|
44
44
|
* generation/`) without a copy change.
|
|
45
45
|
*/
|
|
46
|
-
import type
|
|
46
|
+
import { type SharedHandler } from "@ggui-ai/mcp-server-handlers";
|
|
47
47
|
import type { Express } from "express";
|
|
48
48
|
import type { ZodRawShape } from "zod";
|
|
49
49
|
import type { Logger } from "./logger.js";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"console-chat-routes.d.ts","sourceRoot":"","sources":["../src/console-chat-routes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4CG;AAEH,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"console-chat-routes.d.ts","sourceRoot":"","sources":["../src/console-chat-routes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4CG;AAEH,OAAO,EAEL,KAAK,aAAa,EACnB,MAAM,8BAA8B,CAAC;AACtC,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAEvC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,KAAK,CAAC;AAIvC,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAE1C,UAAU,YAAY;IACpB,iCAAiC;IACjC,QAAQ,CAAC,GAAG,EAAE,OAAO,CAAC;IACtB;;;OAGG;IACH,QAAQ,CAAC,aAAa,CAAC,EAAE,aAAa,CAAC,WAAW,EAAE,WAAW,CAAC,CAAC;IACjE,+DAA+D;IAC/D,QAAQ,CAAC,gBAAgB,CAAC,EAAE,aAAa,CAAC,WAAW,EAAE,WAAW,CAAC,CAAC;IACpE;;;;;OAKG;IACH,QAAQ,CAAC,aAAa,CAAC,EAAE;QACvB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;QACxB,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;QACzB,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;KAC1B,CAAC;IACF,yBAAyB;IACzB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,YAAY,GAAG,IAAI,CA2K/D"}
|
|
@@ -43,6 +43,7 @@
|
|
|
43
43
|
* the exact Lane-1 chat-page spec assertion (`/OSS agent
|
|
44
44
|
* generation/`) without a copy change.
|
|
45
45
|
*/
|
|
46
|
+
import { isHandlerFailure, } from "@ggui-ai/mcp-server-handlers";
|
|
46
47
|
import { randomUUID } from "node:crypto";
|
|
47
48
|
import { DEFAULT_BUILDER_APP_ID } from "./auth.js";
|
|
48
49
|
import { mintDevtoolCookie } from "./console-auth.js";
|
|
@@ -101,6 +102,27 @@ export function mountConsoleChatRoutes(opts) {
|
|
|
101
102
|
});
|
|
102
103
|
const handshakeId = hsRaw.handshakeId;
|
|
103
104
|
const raw = await renderHandler.handler({ handshakeId, contract: {} }, { appId: DEFAULT_BUILDER_APP_ID, requestId });
|
|
105
|
+
if (isHandlerFailure(raw)) {
|
|
106
|
+
// In-result failure envelope (generation failed / rejected).
|
|
107
|
+
// The marker's errorText is the honest self-correction
|
|
108
|
+
// surface — reuse it verbatim; no ui payload (nothing
|
|
109
|
+
// mountable landed for this turn).
|
|
110
|
+
logger.warn?.("console_chat_render_failed", {
|
|
111
|
+
threadId,
|
|
112
|
+
error: raw.errorText,
|
|
113
|
+
});
|
|
114
|
+
res.status(200).json({
|
|
115
|
+
threadId,
|
|
116
|
+
userMessage,
|
|
117
|
+
agentMessage: {
|
|
118
|
+
id: `msg-${randomUUID()}`,
|
|
119
|
+
role: "agent",
|
|
120
|
+
text: raw.errorText,
|
|
121
|
+
createdAt: now + 1,
|
|
122
|
+
},
|
|
123
|
+
});
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
104
126
|
const result = raw;
|
|
105
127
|
ui = {
|
|
106
128
|
sessionId: result.sessionId,
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `/control` — the control plane.
|
|
3
|
+
*
|
|
4
|
+
* ggui serves exactly TWO MCP surfaces:
|
|
5
|
+
*
|
|
6
|
+
* - **Data plane** — the agent routes (`universalMcpPath`, plus the
|
|
7
|
+
* per-tenant `${pathPrefix}/:appId` variant when configured). Carries
|
|
8
|
+
* the `agent` + `runtime` audiences: the tools an agent calls while a
|
|
9
|
+
* session is running.
|
|
10
|
+
* - **Control plane** — this service. Carries the `protocol` + `ops`
|
|
11
|
+
* audiences: design-time spec/discovery (anonymous) and operator-class
|
|
12
|
+
* account management (authenticated, state-changing calls
|
|
13
|
+
* confirm-gated).
|
|
14
|
+
*
|
|
15
|
+
* Audience TAGS stay normative — `audience` on a handler still declares
|
|
16
|
+
* its caller class, and the `ggui_protocol_*` / `ggui_ops_*` wire-name
|
|
17
|
+
* prefixes still encode it. What this module retires is one-HTTP-route-
|
|
18
|
+
* per-audience MOUNTING: `protocol` and `ops` land on the same path.
|
|
19
|
+
*
|
|
20
|
+
* WHY one route for two audiences: an HTTP route carries ONE auth
|
|
21
|
+
* posture. Design-time spec tools must answer bearer-less (an agent
|
|
22
|
+
* authoring a blueprint has no account yet); operator tools must not.
|
|
23
|
+
* Two routes forced a deployment to choose per route, which is why the
|
|
24
|
+
* two surfaces could never be pointed at by a single client config. A
|
|
25
|
+
* service mount is the seam that mixes both: the route is anonymous-
|
|
26
|
+
* capable, and each ops handler re-imposes auth for itself.
|
|
27
|
+
*
|
|
28
|
+
* The per-tool wrappers, in application order (innermost first):
|
|
29
|
+
*
|
|
30
|
+
* 1. {@link withConfirmGate} — state-changing ops only. Turns one
|
|
31
|
+
* call into a deliberate two-call sequence so an agent cannot
|
|
32
|
+
* silently mint credentials, spend credits, or delete resources.
|
|
33
|
+
* 2. {@link withAuthGate} — every ops tool. Rejects callers the auth
|
|
34
|
+
* adapter never authenticated (the anonymous synthetic), so an ops
|
|
35
|
+
* handler never runs against a phantom identity.
|
|
36
|
+
* 3. {@link stripAudience} — every tool. Service handlers MUST NOT
|
|
37
|
+
* carry an `audience` tag (the mount path IS the audience); the tag
|
|
38
|
+
* is consumed by the data-plane route filter and is meaningless
|
|
39
|
+
* inside a service, so dropping it is lossless.
|
|
40
|
+
*
|
|
41
|
+
* Ordering matters: the auth gate sits OUTSIDE the confirm gate, so an
|
|
42
|
+
* unauthenticated caller gets an auth error rather than a confirmation
|
|
43
|
+
* preview that leaks which operations exist against which account.
|
|
44
|
+
*/
|
|
45
|
+
import { type SharedHandler } from "@ggui-ai/mcp-server-handlers";
|
|
46
|
+
import { type ZodRawShape } from "zod";
|
|
47
|
+
import type { McpService } from "./mcp-mounts.js";
|
|
48
|
+
type AnyHandler = SharedHandler<ZodRawShape, ZodRawShape>;
|
|
49
|
+
/** Every audience tag a handler may declare. */
|
|
50
|
+
export type AudienceTag = "agent" | "runtime" | "protocol" | "ops";
|
|
51
|
+
/** HTTP path the control plane mounts at. */
|
|
52
|
+
export declare const CONTROL_PATH = "/control";
|
|
53
|
+
/** Service name the control plane reports in validation errors + telemetry. */
|
|
54
|
+
export declare const CONTROL_SERVICE_NAME = "control";
|
|
55
|
+
/** Audiences the data plane (agent routes) serves. */
|
|
56
|
+
export declare const DATA_PLANE_AUDIENCES: ReadonlyArray<AudienceTag>;
|
|
57
|
+
/** Audiences the control plane serves. */
|
|
58
|
+
export declare const CONTROL_PLANE_AUDIENCES: ReadonlyArray<AudienceTag>;
|
|
59
|
+
/**
|
|
60
|
+
* Return the subset of `set` whose `audience` tag intersects `allowed`.
|
|
61
|
+
* The single projection from audience tags to mount surfaces — both
|
|
62
|
+
* planes read it, so a handler can never land on both or on neither.
|
|
63
|
+
*
|
|
64
|
+
* Handlers with `audience: undefined` default to `['agent']`: an
|
|
65
|
+
* untagged handler is agent-callable, which keeps zero-config OSS
|
|
66
|
+
* deployments (whose handlers rarely tag anything) on the data plane.
|
|
67
|
+
*/
|
|
68
|
+
export declare function filterHandlersByAudience(set: ReadonlyArray<AnyHandler>, allowed: ReadonlyArray<AudienceTag>): ReadonlyArray<AnyHandler>;
|
|
69
|
+
/**
|
|
70
|
+
* Ops tools that answer in ONE call — reads, lists, and card-openers
|
|
71
|
+
* that change no state. Every other `ops`-tagged tool is treated as
|
|
72
|
+
* state-changing and confirm-gated.
|
|
73
|
+
*
|
|
74
|
+
* The classification is default-DENY on purpose. A curated
|
|
75
|
+
* "these ones are mutating" list has a silent failure mode: land a new
|
|
76
|
+
* state-changing ops tool, forget the list, and it ships un-gated with
|
|
77
|
+
* nothing to notice. Inverting it makes the forgotten case a visible
|
|
78
|
+
* annoyance (an extra confirmation round-trip on a read) instead of an
|
|
79
|
+
* invisible hole, which is the trade the Protocol and Contract Bar asks
|
|
80
|
+
* for — an unflagged gap is a failure in disguise.
|
|
81
|
+
*
|
|
82
|
+
* Deployments that register their own read-only ops tools extend this
|
|
83
|
+
* via `CreateGguiServerOptions.control.singleCallOps` rather than
|
|
84
|
+
* patching this set.
|
|
85
|
+
*
|
|
86
|
+
* `ggui_ops_setup_byok` is a deliberate entry: it LISTS provider-key
|
|
87
|
+
* status and returns the BYOK card — the set/remove tools are the real
|
|
88
|
+
* mutations. Confirm-gating it would break the card's inline render on
|
|
89
|
+
* the first call, because the confirmation preview does not carry the
|
|
90
|
+
* card's discriminant.
|
|
91
|
+
*/
|
|
92
|
+
export declare const SINGLE_CALL_OPS: ReadonlySet<string>;
|
|
93
|
+
/**
|
|
94
|
+
* Return a copy of the handler with the `audience` tag removed.
|
|
95
|
+
* `validateMcpServices` rejects service handlers that carry one.
|
|
96
|
+
*/
|
|
97
|
+
export declare function stripAudience<I extends ZodRawShape, O extends ZodRawShape, D>(h: SharedHandler<I, O, D>): SharedHandler<I, O, D>;
|
|
98
|
+
/**
|
|
99
|
+
* Require an authenticated caller.
|
|
100
|
+
*
|
|
101
|
+
* The control plane is anonymous-CAPABLE so design-time spec tools
|
|
102
|
+
* answer bearer-less. In that mode the transport synthesizes a builder
|
|
103
|
+
* identity (`authSource: 'anonymous'`) for requests that presented no
|
|
104
|
+
* credential — or presented one the adapter rejected. An un-gated ops
|
|
105
|
+
* handler would then run against that synthetic rather than refusing,
|
|
106
|
+
* which is how a caller with no account would read and write a phantom
|
|
107
|
+
* shared tenant.
|
|
108
|
+
*
|
|
109
|
+
* The gate is on `ctx.authSource`, not on which identity FIELDS are
|
|
110
|
+
* populated: every deployment tier proves identity differently
|
|
111
|
+
* (single-user builder, per-app key, per-user key, OIDC), and only the
|
|
112
|
+
* source distinguishes "the adapter authenticated this request" from
|
|
113
|
+
* "the transport let it through". Transports map
|
|
114
|
+
* {@link AuthRequiredError} to 401 so clients can prompt for sign-in.
|
|
115
|
+
*/
|
|
116
|
+
export declare function withAuthGate<I extends ZodRawShape, O extends ZodRawShape, D>(h: SharedHandler<I, O, D>): SharedHandler<I, O, D>;
|
|
117
|
+
/**
|
|
118
|
+
* Wrap a state-changing handler in a stateless two-call confirmation
|
|
119
|
+
* gate. The first call (no `confirm: true`) returns a preview and runs
|
|
120
|
+
* nothing; a second call carrying `confirm: true` delegates to the
|
|
121
|
+
* inner handler.
|
|
122
|
+
*
|
|
123
|
+
* The `confirm` input field and the confirmation output fields live
|
|
124
|
+
* only on this wrapper — the underlying shared handler is untouched, so
|
|
125
|
+
* neither the field nor its `.describe()` text reaches any other
|
|
126
|
+
* caller of the same handler.
|
|
127
|
+
*
|
|
128
|
+
* The declared output schema is the inner schema with every field made
|
|
129
|
+
* optional, PLUS the confirmation fields, so both the preview (only
|
|
130
|
+
* confirmation fields) and a real commit response (inner fields)
|
|
131
|
+
* validate against it.
|
|
132
|
+
*/
|
|
133
|
+
export declare function withConfirmGate<I extends ZodRawShape, O extends ZodRawShape, D>(h: SharedHandler<I, O, D>): SharedHandler<ZodRawShape, ZodRawShape>;
|
|
134
|
+
export interface BuildControlServiceArgs {
|
|
135
|
+
/**
|
|
136
|
+
* The server's fully composed handler list. The control plane filters
|
|
137
|
+
* it by audience itself, so callers hand over the same array the data
|
|
138
|
+
* plane reads — membership can never drift between the two.
|
|
139
|
+
*/
|
|
140
|
+
readonly handlers: ReadonlyArray<AnyHandler>;
|
|
141
|
+
/**
|
|
142
|
+
* Additional ops tool names that answer in one call. Merged with
|
|
143
|
+
* {@link SINGLE_CALL_OPS}; use it for deployment-registered read-only
|
|
144
|
+
* ops tools that would otherwise be confirm-gated by the default-deny
|
|
145
|
+
* rule.
|
|
146
|
+
*/
|
|
147
|
+
readonly singleCallOps?: ReadonlyArray<string>;
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Assemble the control plane: every `protocol`-tagged handler served
|
|
151
|
+
* as-is (anonymous), every `ops`-tagged handler auth-gated and — unless
|
|
152
|
+
* it is a known single-call read — confirm-gated.
|
|
153
|
+
*
|
|
154
|
+
* Always `anonymous: true`: the route must answer bearer-less for the
|
|
155
|
+
* design-time tools, and the per-handler auth gate is what keeps the
|
|
156
|
+
* ops half closed.
|
|
157
|
+
*/
|
|
158
|
+
export declare function buildControlService(args: BuildControlServiceArgs): McpService;
|
|
159
|
+
export {};
|
|
160
|
+
//# sourceMappingURL=control-service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"control-service.d.ts","sourceRoot":"","sources":["../src/control-service.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CG;AACH,OAAO,EAGL,KAAK,aAAa,EACnB,MAAM,8BAA8B,CAAC;AACtC,OAAO,EAAK,KAAK,WAAW,EAAmB,MAAM,KAAK,CAAC;AAC3D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAElD,KAAK,UAAU,GAAG,aAAa,CAAC,WAAW,EAAE,WAAW,CAAC,CAAC;AAE1D,gDAAgD;AAChD,MAAM,MAAM,WAAW,GAAG,OAAO,GAAG,SAAS,GAAG,UAAU,GAAG,KAAK,CAAC;AAEnE,6CAA6C;AAC7C,eAAO,MAAM,YAAY,aAAa,CAAC;AAEvC,+EAA+E;AAC/E,eAAO,MAAM,oBAAoB,YAAY,CAAC;AAE9C,sDAAsD;AACtD,eAAO,MAAM,oBAAoB,EAAE,aAAa,CAAC,WAAW,CAAwB,CAAC;AAErF,0CAA0C;AAC1C,eAAO,MAAM,uBAAuB,EAAE,aAAa,CAAC,WAAW,CAAuB,CAAC;AAEvF;;;;;;;;GAQG;AACH,wBAAgB,wBAAwB,CACtC,GAAG,EAAE,aAAa,CAAC,UAAU,CAAC,EAC9B,OAAO,EAAE,aAAa,CAAC,WAAW,CAAC,GAClC,aAAa,CAAC,UAAU,CAAC,CAK3B;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,eAAO,MAAM,eAAe,EAAE,WAAW,CAAC,MAAM,CAa9C,CAAC;AAEH;;;GAGG;AACH,wBAAgB,aAAa,CAAC,CAAC,SAAS,WAAW,EAAE,CAAC,SAAS,WAAW,EAAE,CAAC,EAC3E,CAAC,EAAE,aAAa,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,GACxB,aAAa,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAGxB;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,YAAY,CAAC,CAAC,SAAS,WAAW,EAAE,CAAC,SAAS,WAAW,EAAE,CAAC,EAC1E,CAAC,EAAE,aAAa,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,GACxB,aAAa,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAYxB;AASD;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,eAAe,CAAC,CAAC,SAAS,WAAW,EAAE,CAAC,SAAS,WAAW,EAAE,CAAC,EAC7E,CAAC,EAAE,aAAa,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,GACxB,aAAa,CAAC,WAAW,EAAE,WAAW,CAAC,CA+BzC;AAED,MAAM,WAAW,uBAAuB;IACtC;;;;OAIG;IACH,QAAQ,CAAC,QAAQ,EAAE,aAAa,CAAC,UAAU,CAAC,CAAC;IAC7C;;;;;OAKG;IACH,QAAQ,CAAC,aAAa,CAAC,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;CAChD;AAED;;;;;;;;GAQG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,uBAAuB,GAAG,UAAU,CAmB7E"}
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `/control` — the control plane.
|
|
3
|
+
*
|
|
4
|
+
* ggui serves exactly TWO MCP surfaces:
|
|
5
|
+
*
|
|
6
|
+
* - **Data plane** — the agent routes (`universalMcpPath`, plus the
|
|
7
|
+
* per-tenant `${pathPrefix}/:appId` variant when configured). Carries
|
|
8
|
+
* the `agent` + `runtime` audiences: the tools an agent calls while a
|
|
9
|
+
* session is running.
|
|
10
|
+
* - **Control plane** — this service. Carries the `protocol` + `ops`
|
|
11
|
+
* audiences: design-time spec/discovery (anonymous) and operator-class
|
|
12
|
+
* account management (authenticated, state-changing calls
|
|
13
|
+
* confirm-gated).
|
|
14
|
+
*
|
|
15
|
+
* Audience TAGS stay normative — `audience` on a handler still declares
|
|
16
|
+
* its caller class, and the `ggui_protocol_*` / `ggui_ops_*` wire-name
|
|
17
|
+
* prefixes still encode it. What this module retires is one-HTTP-route-
|
|
18
|
+
* per-audience MOUNTING: `protocol` and `ops` land on the same path.
|
|
19
|
+
*
|
|
20
|
+
* WHY one route for two audiences: an HTTP route carries ONE auth
|
|
21
|
+
* posture. Design-time spec tools must answer bearer-less (an agent
|
|
22
|
+
* authoring a blueprint has no account yet); operator tools must not.
|
|
23
|
+
* Two routes forced a deployment to choose per route, which is why the
|
|
24
|
+
* two surfaces could never be pointed at by a single client config. A
|
|
25
|
+
* service mount is the seam that mixes both: the route is anonymous-
|
|
26
|
+
* capable, and each ops handler re-imposes auth for itself.
|
|
27
|
+
*
|
|
28
|
+
* The per-tool wrappers, in application order (innermost first):
|
|
29
|
+
*
|
|
30
|
+
* 1. {@link withConfirmGate} — state-changing ops only. Turns one
|
|
31
|
+
* call into a deliberate two-call sequence so an agent cannot
|
|
32
|
+
* silently mint credentials, spend credits, or delete resources.
|
|
33
|
+
* 2. {@link withAuthGate} — every ops tool. Rejects callers the auth
|
|
34
|
+
* adapter never authenticated (the anonymous synthetic), so an ops
|
|
35
|
+
* handler never runs against a phantom identity.
|
|
36
|
+
* 3. {@link stripAudience} — every tool. Service handlers MUST NOT
|
|
37
|
+
* carry an `audience` tag (the mount path IS the audience); the tag
|
|
38
|
+
* is consumed by the data-plane route filter and is meaningless
|
|
39
|
+
* inside a service, so dropping it is lossless.
|
|
40
|
+
*
|
|
41
|
+
* Ordering matters: the auth gate sits OUTSIDE the confirm gate, so an
|
|
42
|
+
* unauthenticated caller gets an auth error rather than a confirmation
|
|
43
|
+
* preview that leaks which operations exist against which account.
|
|
44
|
+
*/
|
|
45
|
+
import { AuthRequiredError, } from "@ggui-ai/mcp-server-handlers";
|
|
46
|
+
import { z } from "zod";
|
|
47
|
+
/** HTTP path the control plane mounts at. */
|
|
48
|
+
export const CONTROL_PATH = "/control";
|
|
49
|
+
/** Service name the control plane reports in validation errors + telemetry. */
|
|
50
|
+
export const CONTROL_SERVICE_NAME = "control";
|
|
51
|
+
/** Audiences the data plane (agent routes) serves. */
|
|
52
|
+
export const DATA_PLANE_AUDIENCES = ["agent", "runtime"];
|
|
53
|
+
/** Audiences the control plane serves. */
|
|
54
|
+
export const CONTROL_PLANE_AUDIENCES = ["protocol", "ops"];
|
|
55
|
+
/**
|
|
56
|
+
* Return the subset of `set` whose `audience` tag intersects `allowed`.
|
|
57
|
+
* The single projection from audience tags to mount surfaces — both
|
|
58
|
+
* planes read it, so a handler can never land on both or on neither.
|
|
59
|
+
*
|
|
60
|
+
* Handlers with `audience: undefined` default to `['agent']`: an
|
|
61
|
+
* untagged handler is agent-callable, which keeps zero-config OSS
|
|
62
|
+
* deployments (whose handlers rarely tag anything) on the data plane.
|
|
63
|
+
*/
|
|
64
|
+
export function filterHandlersByAudience(set, allowed) {
|
|
65
|
+
return set.filter((h) => {
|
|
66
|
+
const tags = h.audience ?? ["agent"];
|
|
67
|
+
return tags.some((t) => allowed.includes(t));
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Ops tools that answer in ONE call — reads, lists, and card-openers
|
|
72
|
+
* that change no state. Every other `ops`-tagged tool is treated as
|
|
73
|
+
* state-changing and confirm-gated.
|
|
74
|
+
*
|
|
75
|
+
* The classification is default-DENY on purpose. A curated
|
|
76
|
+
* "these ones are mutating" list has a silent failure mode: land a new
|
|
77
|
+
* state-changing ops tool, forget the list, and it ships un-gated with
|
|
78
|
+
* nothing to notice. Inverting it makes the forgotten case a visible
|
|
79
|
+
* annoyance (an extra confirmation round-trip on a read) instead of an
|
|
80
|
+
* invisible hole, which is the trade the Protocol and Contract Bar asks
|
|
81
|
+
* for — an unflagged gap is a failure in disguise.
|
|
82
|
+
*
|
|
83
|
+
* Deployments that register their own read-only ops tools extend this
|
|
84
|
+
* via `CreateGguiServerOptions.control.singleCallOps` rather than
|
|
85
|
+
* patching this set.
|
|
86
|
+
*
|
|
87
|
+
* `ggui_ops_setup_byok` is a deliberate entry: it LISTS provider-key
|
|
88
|
+
* status and returns the BYOK card — the set/remove tools are the real
|
|
89
|
+
* mutations. Confirm-gating it would break the card's inline render on
|
|
90
|
+
* the first call, because the confirmation preview does not carry the
|
|
91
|
+
* card's discriminant.
|
|
92
|
+
*/
|
|
93
|
+
export const SINGLE_CALL_OPS = new Set([
|
|
94
|
+
"ggui_ops_get_credit_balance",
|
|
95
|
+
"ggui_ops_get_my_blueprint_source",
|
|
96
|
+
"ggui_ops_get_org_balance",
|
|
97
|
+
"ggui_ops_list_apps",
|
|
98
|
+
"ggui_ops_list_blueprints",
|
|
99
|
+
"ggui_ops_list_connector_keys",
|
|
100
|
+
"ggui_ops_list_credit_transactions",
|
|
101
|
+
"ggui_ops_list_my_apps",
|
|
102
|
+
"ggui_ops_list_my_blueprints",
|
|
103
|
+
"ggui_ops_list_orgs",
|
|
104
|
+
"ggui_ops_list_provider_keys",
|
|
105
|
+
"ggui_ops_setup_byok",
|
|
106
|
+
]);
|
|
107
|
+
/**
|
|
108
|
+
* Return a copy of the handler with the `audience` tag removed.
|
|
109
|
+
* `validateMcpServices` rejects service handlers that carry one.
|
|
110
|
+
*/
|
|
111
|
+
export function stripAudience(h) {
|
|
112
|
+
const { audience: _omit, ...rest } = h;
|
|
113
|
+
return rest;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Require an authenticated caller.
|
|
117
|
+
*
|
|
118
|
+
* The control plane is anonymous-CAPABLE so design-time spec tools
|
|
119
|
+
* answer bearer-less. In that mode the transport synthesizes a builder
|
|
120
|
+
* identity (`authSource: 'anonymous'`) for requests that presented no
|
|
121
|
+
* credential — or presented one the adapter rejected. An un-gated ops
|
|
122
|
+
* handler would then run against that synthetic rather than refusing,
|
|
123
|
+
* which is how a caller with no account would read and write a phantom
|
|
124
|
+
* shared tenant.
|
|
125
|
+
*
|
|
126
|
+
* The gate is on `ctx.authSource`, not on which identity FIELDS are
|
|
127
|
+
* populated: every deployment tier proves identity differently
|
|
128
|
+
* (single-user builder, per-app key, per-user key, OIDC), and only the
|
|
129
|
+
* source distinguishes "the adapter authenticated this request" from
|
|
130
|
+
* "the transport let it through". Transports map
|
|
131
|
+
* {@link AuthRequiredError} to 401 so clients can prompt for sign-in.
|
|
132
|
+
*/
|
|
133
|
+
export function withAuthGate(h) {
|
|
134
|
+
return {
|
|
135
|
+
...h,
|
|
136
|
+
async handler(input, ctx) {
|
|
137
|
+
if (ctx.authSource === undefined || ctx.authSource === "anonymous") {
|
|
138
|
+
throw new AuthRequiredError(`${h.name} is an operator tool and needs an authenticated caller. Present a bearer token this deployment accepts.`);
|
|
139
|
+
}
|
|
140
|
+
return h.handler(input, ctx);
|
|
141
|
+
},
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
/** Map every field of a Zod raw-shape to its `.optional()` form. */
|
|
145
|
+
function optionalize(shape) {
|
|
146
|
+
return Object.fromEntries(Object.entries(shape).map(([key, value]) => [key, value.optional()]));
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Wrap a state-changing handler in a stateless two-call confirmation
|
|
150
|
+
* gate. The first call (no `confirm: true`) returns a preview and runs
|
|
151
|
+
* nothing; a second call carrying `confirm: true` delegates to the
|
|
152
|
+
* inner handler.
|
|
153
|
+
*
|
|
154
|
+
* The `confirm` input field and the confirmation output fields live
|
|
155
|
+
* only on this wrapper — the underlying shared handler is untouched, so
|
|
156
|
+
* neither the field nor its `.describe()` text reaches any other
|
|
157
|
+
* caller of the same handler.
|
|
158
|
+
*
|
|
159
|
+
* The declared output schema is the inner schema with every field made
|
|
160
|
+
* optional, PLUS the confirmation fields, so both the preview (only
|
|
161
|
+
* confirmation fields) and a real commit response (inner fields)
|
|
162
|
+
* validate against it.
|
|
163
|
+
*/
|
|
164
|
+
export function withConfirmGate(h) {
|
|
165
|
+
const inputSchema = {
|
|
166
|
+
...h.inputSchema,
|
|
167
|
+
confirm: z
|
|
168
|
+
.boolean()
|
|
169
|
+
.optional()
|
|
170
|
+
.describe("Set true to actually perform this state-changing operation. Omitted/false returns a confirmation preview instead."),
|
|
171
|
+
};
|
|
172
|
+
const outputSchema = {
|
|
173
|
+
...optionalize(h.outputSchema),
|
|
174
|
+
confirmationRequired: z.boolean().optional(),
|
|
175
|
+
confirmationPrompt: z.string().optional(),
|
|
176
|
+
};
|
|
177
|
+
return {
|
|
178
|
+
...h,
|
|
179
|
+
description: `${h.description} (State-changing: the first call returns a confirmation prompt; re-call with confirm:true to proceed.)`,
|
|
180
|
+
inputSchema,
|
|
181
|
+
outputSchema,
|
|
182
|
+
async handler(input, ctx) {
|
|
183
|
+
if (input.confirm !== true) {
|
|
184
|
+
return {
|
|
185
|
+
confirmationRequired: true,
|
|
186
|
+
confirmationPrompt: `Calling ${h.name} will change account state. Show the human exactly what will happen and, only with their explicit approval, call ${h.name} again with confirm:true.`,
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
const { confirm: _confirm, ...rest } = input;
|
|
190
|
+
return h.handler(rest, ctx);
|
|
191
|
+
},
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Assemble the control plane: every `protocol`-tagged handler served
|
|
196
|
+
* as-is (anonymous), every `ops`-tagged handler auth-gated and — unless
|
|
197
|
+
* it is a known single-call read — confirm-gated.
|
|
198
|
+
*
|
|
199
|
+
* Always `anonymous: true`: the route must answer bearer-less for the
|
|
200
|
+
* design-time tools, and the per-handler auth gate is what keeps the
|
|
201
|
+
* ops half closed.
|
|
202
|
+
*/
|
|
203
|
+
export function buildControlService(args) {
|
|
204
|
+
const singleCall = new Set([...SINGLE_CALL_OPS, ...(args.singleCallOps ?? [])]);
|
|
205
|
+
const protocolTools = filterHandlersByAudience(args.handlers, ["protocol"]);
|
|
206
|
+
const opsTools = filterHandlersByAudience(args.handlers, ["ops"]);
|
|
207
|
+
const handlers = [
|
|
208
|
+
...protocolTools.map((h) => stripAudience(h)),
|
|
209
|
+
...opsTools.map((h) => {
|
|
210
|
+
const confirmed = singleCall.has(h.name) ? h : withConfirmGate(h);
|
|
211
|
+
return stripAudience(withAuthGate(confirmed));
|
|
212
|
+
}),
|
|
213
|
+
];
|
|
214
|
+
return {
|
|
215
|
+
name: CONTROL_SERVICE_NAME,
|
|
216
|
+
path: CONTROL_PATH,
|
|
217
|
+
handlers,
|
|
218
|
+
anonymous: true,
|
|
219
|
+
};
|
|
220
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -29,13 +29,15 @@ export type { HandlerContext, SharedHandler } from '@ggui-ai/mcp-server-handlers
|
|
|
29
29
|
export type { GadgetDescriptor, McpUiDisplayMode, GguiSession, SystemGguiSession, } from '@ggui-ai/protocol';
|
|
30
30
|
export type { GenerationDeps } from '@ggui-ai/mcp-server-handlers';
|
|
31
31
|
export { buildNoCredentialsGguiSession } from '@ggui-ai/mcp-server-handlers';
|
|
32
|
-
export { createGguiServer, defaultHandlers } from './server.js';
|
|
33
|
-
export type { CreateGguiServerOptions, GguiServer, } from './server.js';
|
|
32
|
+
export { buildOpsBundleHandlers, createGguiServer, defaultHandlers } from './server.js';
|
|
33
|
+
export type { CreateGguiServerOptions, GguiServer, OpsBundleDeps, } from './server.js';
|
|
34
|
+
export { buildControlService, CONTROL_PATH, filterHandlersByAudience, SINGLE_CALL_OPS, } from './control-service.js';
|
|
35
|
+
export type { AudienceTag, BuildControlServiceArgs } from './control-service.js';
|
|
34
36
|
export { FileSystemCodeStore } from './code-store-fs.js';
|
|
35
37
|
export type { FileSystemCodeStoreOptions } from './code-store-fs.js';
|
|
36
38
|
export type { McpServerMount } from './mcp-mounts.js';
|
|
37
39
|
export type { McpService, ServicePath } from './mcp-mounts.js';
|
|
38
|
-
export { validateMcpServices, validateServicePath } from './mcp-mounts.js';
|
|
40
|
+
export { validateMcpServices, validateServiceHandlers, validateServicePath, } from './mcp-mounts.js';
|
|
39
41
|
export { composePreviewReservedValidator, mergeReservedValidators, } from './reserved-validators.js';
|
|
40
42
|
export { checkRenderSchemaCompat, DEFAULT_SCHEMA_COMPAT_MODE, hasErrorFinding, SchemaCompatError, } from './schema-compat.js';
|
|
41
43
|
export type { SchemaCompatFinding, SchemaCompatMode, SchemaCompatReport, GguiSessionContractShape, ToolSchemaRef, } from './schema-compat.js';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,YAAY,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAKlF,YAAY,EACV,gBAAgB,EAChB,gBAAgB,EAChB,WAAW,EACX,iBAAiB,GAClB,MAAM,mBAAmB,CAAC;AAC3B,YAAY,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAKnE,OAAO,EAAE,6BAA6B,EAAE,MAAM,8BAA8B,CAAC;AAC7E,OAAO,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -30,12 +30,18 @@
|
|
|
30
30
|
// `/settings` URL) without taking a direct `@ggui-ai/mcp-server-handlers`
|
|
31
31
|
// dependency.
|
|
32
32
|
export { buildNoCredentialsGguiSession } from '@ggui-ai/mcp-server-handlers';
|
|
33
|
-
export { createGguiServer, defaultHandlers } from './server.js';
|
|
33
|
+
export { buildOpsBundleHandlers, createGguiServer, defaultHandlers } from './server.js';
|
|
34
|
+
// Control plane (`/control`) — the composition that projects every
|
|
35
|
+
// `protocol`- and `ops`-tagged handler onto one anonymous-capable
|
|
36
|
+
// route with per-tool auth + confirmation gates. `createGguiServer`
|
|
37
|
+
// mounts it automatically; the pieces are exported so deployments can
|
|
38
|
+
// assert their own control surface at boot.
|
|
39
|
+
export { buildControlService, CONTROL_PATH, filterHandlersByAudience, SINGLE_CALL_OPS, } from './control-service.js';
|
|
34
40
|
// Content-addressable code delivery (2026-05-03). FileSystemCodeStore
|
|
35
41
|
// is the OSS dev default; in-memory variant ships in
|
|
36
42
|
// `@ggui-ai/mcp-server-core/in-memory` for tests + ephemeral runs.
|
|
37
43
|
export { FileSystemCodeStore } from './code-store-fs.js';
|
|
38
|
-
export { validateMcpServices, validateServicePath } from './mcp-mounts.js';
|
|
44
|
+
export { validateMcpServices, validateServiceHandlers, validateServicePath, } from './mcp-mounts.js';
|
|
39
45
|
// Reserved-channel payload validator composition.
|
|
40
46
|
// `composePreviewReservedValidator` binds the A2UI adapter
|
|
41
47
|
// for `_ggui:preview`; `mergeReservedValidators` layers caller-provided
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* MCP wire endpoints — the
|
|
2
|
+
* MCP wire endpoints — the JSON-RPC surfaces.
|
|
3
3
|
*
|
|
4
|
-
* POST <universalMcpPath> — agent+runtime tools
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* POST /
|
|
4
|
+
* POST <universalMcpPath> — DATA PLANE: agent+runtime tools
|
|
5
|
+
* (default `/mcp`)
|
|
6
|
+
* POST <pathPrefix>/:appId — data plane, per-tenant variant
|
|
7
|
+
* (opt-in via `perAppRouting`)
|
|
8
|
+
* POST /control — CONTROL PLANE: design-time
|
|
9
|
+
* spec/discovery (anonymous) +
|
|
10
|
+
* operator management (authed)
|
|
9
11
|
* POST <service.path> — isolated MCP services (path IS
|
|
10
12
|
* the audience)
|
|
11
13
|
* GET/DELETE on each — 405 (stateless server; no
|
|
@@ -13,13 +15,18 @@
|
|
|
13
15
|
* session-terminate verbs)
|
|
14
16
|
*
|
|
15
17
|
* Every route shares ONE request pipeline (`makeMcpHandler`): resolve
|
|
16
|
-
* identity via the AuthAdapter (anonymous
|
|
18
|
+
* identity via the AuthAdapter (anonymous surfaces synthesize a
|
|
17
19
|
* builder identity on missing/invalid bearers), apply the per-app
|
|
18
20
|
* authorize hook, build a fresh `McpServer` + Streamable HTTP
|
|
19
21
|
* transport per request (stateless), and dispatch under the
|
|
20
22
|
* AsyncLocalStorage-scoped `HandlerContext`. The difference between
|
|
21
23
|
* routes is ONLY the handler set each exposes.
|
|
22
24
|
*
|
|
25
|
+
* Audience TAGS remain the normative caller-class declaration; what
|
|
26
|
+
* they no longer do is mint one HTTP route each. `protocol` and `ops`
|
|
27
|
+
* both mount on `/control` — see `./control-service.ts` for why (one
|
|
28
|
+
* route carries one auth posture; the control plane needs two).
|
|
29
|
+
*
|
|
23
30
|
* See `docs/development/audience-routes.md` for the audience taxonomy
|
|
24
31
|
* (`agent` / `runtime` / `protocol` / `ops`) and the wire-name prefix
|
|
25
32
|
* rules.
|
|
@@ -48,8 +55,15 @@ interface MountOptions {
|
|
|
48
55
|
readonly auth: AuthAdapter;
|
|
49
56
|
/** Server identity forwarded to every per-request `buildMcpServer`. */
|
|
50
57
|
readonly info: ServerInfo;
|
|
51
|
-
/** Full composed handler list (audience filtering happens here). */
|
|
58
|
+
/** Full composed handler list (data-plane audience filtering happens here). */
|
|
52
59
|
readonly handlers: ReadonlyArray<SharedHandler<ZodRawShape, ZodRawShape>>;
|
|
60
|
+
/**
|
|
61
|
+
* Control-plane handler set — already projected + wrapped by
|
|
62
|
+
* `buildControlService`. Passed pre-built rather than filtered here
|
|
63
|
+
* because the per-tool auth/confirm wrappers are composition, not
|
|
64
|
+
* transport.
|
|
65
|
+
*/
|
|
66
|
+
readonly controlHandlers: ReadonlyArray<SharedHandler<ZodRawShape, ZodRawShape>>;
|
|
53
67
|
/** Validated isolated-service list (`validateMcpServices` output). */
|
|
54
68
|
readonly mcpServices: ReadonlyArray<McpService>;
|
|
55
69
|
/** Request-scoped HandlerContext storage shared with the handlers. */
|
|
@@ -79,9 +93,9 @@ interface MountOptions {
|
|
|
79
93
|
readonly buildMcpOptions: BuildMcpServerOptions;
|
|
80
94
|
}
|
|
81
95
|
/**
|
|
82
|
-
* Mount the universal / per-app
|
|
83
|
-
* endpoints onto the express app. Returns nothing — the
|
|
84
|
-
* self-register.
|
|
96
|
+
* Mount the data-plane (universal / per-app), control-plane, and
|
|
97
|
+
* service MCP endpoints onto the express app. Returns nothing — the
|
|
98
|
+
* routes self-register.
|
|
85
99
|
*/
|
|
86
100
|
export declare function mountMcpEndpoints(opts: MountOptions): void;
|
|
87
101
|
export {};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mcp-endpoint-routes.d.ts","sourceRoot":"","sources":["../src/mcp-endpoint-routes.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"mcp-endpoint-routes.d.ts","sourceRoot":"","sources":["../src/mcp-endpoint-routes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;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;AAM7F,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,+EAA+E;IAC/E,QAAQ,CAAC,QAAQ,EAAE,aAAa,CAAC,aAAa,CAAC,WAAW,EAAE,WAAW,CAAC,CAAC,CAAC;IAC1E;;;;;OAKG;IACH,QAAQ,CAAC,eAAe,EAAE,aAAa,CAAC,aAAa,CAAC,WAAW,EAAE,WAAW,CAAC,CAAC,CAAC;IACjF,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,CA4U1D"}
|