@12-apps/mcp 3.2.0 → 3.2.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.
@@ -0,0 +1,382 @@
1
+ import { J as JsonSchema, G as GeneratedTool, D as DispatchConfig, a as DispatchResult, b as ToolAnnotations, R as RequestAuth, T as ToolManifest } from './generate-Dx3cK8th.js';
2
+ export { A as AuthResolver, c as GenerateOptions, O as OpenApiDocument, d as OpenApiOperation, e as OpenApiParameter, f as OpenApiRequestBody, g as OpenApiResponse, P as ParameterLocation, h as ToolParameter, i as generateTools } from './generate-Dx3cK8th.js';
3
+ export { A as AI_CAPABILITIES, a as AI_PERMISSION_MODEL, b as AiCapability, c as AiConnectPromptSpec, d as AiHostBrand, e as AiHostConfigureStage, f as AiHostGuide, g as AiHostLink, h as AiProvider, i as aiConnectPrompt, j as aiHostGuides, p as providerForHostId } from './guide-DV5MQbCg.js';
4
+
5
+ /**
6
+ * Raised when a JSON Schema cannot be turned into a flat, self-contained tool
7
+ * input — an unresolvable `$ref`, an unsupported pointer, or a recursive schema.
8
+ * The MCP tool surface is deliberately finite and flat (it is handed to an LLM
9
+ * and committed to the drift manifest), so recursion is rejected rather than
10
+ * silently truncated.
11
+ */
12
+ declare class UnsupportedSchemaError extends Error {
13
+ constructor(message: string);
14
+ }
15
+ /**
16
+ * Inline every local `$ref` in a JSON Schema and drop the now-empty `$defs`/
17
+ * `definitions` containers, yielding a flat, self-contained schema. Diamond reuse
18
+ * (the same definition referenced by sibling branches) is fine; only a true cycle
19
+ * — a definition that references itself up the resolution stack — is rejected.
20
+ *
21
+ * A schema with no `$ref`/definitions is returned structurally unchanged, so
22
+ * inlining an already-flat spec is a no-op (the drift gate stays a stable diff).
23
+ */
24
+ declare function inlineSchemaRefs(schema: JsonSchema): JsonSchema;
25
+
26
+ /** Raised when tool arguments cannot be routed onto the HTTP request. */
27
+ declare class DispatchInputError extends Error {
28
+ constructor(message: string);
29
+ }
30
+ /**
31
+ * Execute one generated tool by proxying to its HTTP endpoint, forwarding the
32
+ * caller's bearer verbatim. This function performs NO authorization — the
33
+ * endpoint does, exactly as it would for a first-party request. That is the whole
34
+ * point of the passthrough: the agent can do precisely what the user can.
35
+ */
36
+ declare function dispatchTool(tool: GeneratedTool, args: Record<string, unknown>, config: DispatchConfig): Promise<DispatchResult>;
37
+
38
+ /**
39
+ * The registry is the transport-agnostic seam between the generated tools and the
40
+ * MCP SDK. The consuming app owns the HTTP/JSON-RPC transport (mounting it at
41
+ * `/api/mcp`) and, per request, resolves {@link RequestAuth} and calls
42
+ * {@link ToolRegistry.listTools} / {@link ToolRegistry.callTool}. Keeping the
43
+ * SDK out of this package means the core stays testable and portable.
44
+ */
45
+ /** An MCP tool descriptor as advertised to clients (subset of the MCP schema). */
46
+ interface McpToolDescriptor {
47
+ name: string;
48
+ description: string;
49
+ inputSchema: JsonSchema;
50
+ outputSchema?: JsonSchema;
51
+ annotations: ToolAnnotations;
52
+ }
53
+ /**
54
+ * `_meta` key carrying the upstream HTTP status of a dispatched call.
55
+ *
56
+ * `isError` is one bit, and it collapses answers that mean opposite things: a
57
+ * 404 for a record that does not exist, a 403 a guard correctly refused, a
58
+ * domain refusal ("this store does not use comandas"), and a 500 where the route
59
+ * threw all arrive identical. Callers that need to tell "correctly refused" from
60
+ * "actually broken" — `mcp:smoke` above all — cannot, because the status is
61
+ * known at dispatch and then dropped. Publishing it under a namespaced `_meta`
62
+ * key (permitted by the MCP result schema) keeps `isError` as the agent-facing
63
+ * signal while making the distinction recoverable.
64
+ */
65
+ declare const HTTP_STATUS_META_KEY = "dispatch/httpStatus";
66
+ /** An MCP tool-call result (subset of the MCP schema). */
67
+ interface McpToolResult {
68
+ content: Array<{
69
+ type: "text";
70
+ text: string;
71
+ }>;
72
+ isError: boolean;
73
+ /** Machine-readable output matching the advertised outputSchema. */
74
+ structuredContent?: Record<string, unknown>;
75
+ /** Out-of-band metadata; carries {@link HTTP_STATUS_META_KEY} when dispatched. */
76
+ _meta?: Record<string, unknown>;
77
+ }
78
+ interface ToolRegistry {
79
+ listTools(auth?: RequestAuth): McpToolDescriptor[];
80
+ callTool(name: string, args: Record<string, unknown>, auth: RequestAuth): Promise<McpToolResult>;
81
+ }
82
+ interface RegistryOptions {
83
+ tools: GeneratedTool[];
84
+ /** Origin the tools proxy to (usually the app's own public URL). */
85
+ baseUrl: string;
86
+ fetchImpl?: typeof fetch;
87
+ /**
88
+ * Optional visibility filter — e.g. hide mutating tools, or tools whose
89
+ * required scope the caller lacks. Authorization is still enforced upstream;
90
+ * this only shapes what the agent is shown.
91
+ */
92
+ isVisible?: (tool: GeneratedTool, auth?: RequestAuth) => boolean;
93
+ }
94
+ declare function createToolRegistry(options: RegistryOptions): ToolRegistry;
95
+
96
+ /**
97
+ * The MCP JSON-RPC 2.0 request half of the Streamable HTTP transport.
98
+ *
99
+ * Implemented directly rather than via the MCP SDK because the SDK's transport is
100
+ * Node-`http` oriented, and a host serving Web `Request`/`Response` (a Next route
101
+ * handler, a Hono route) has no `http.IncomingMessage` to hand it. It covers the
102
+ * methods a client needs to discover and call tools: `initialize`, `tools/list`,
103
+ * `tools/call`, plus `ping`.
104
+ *
105
+ * WHAT IS MECHANISM AND LIVES HERE: the envelope, the method table, the error
106
+ * codes, the well-formedness rule, and the notification convention. None of it
107
+ * varies per host — it is JSON-RPC 2.0 and the MCP specification.
108
+ *
109
+ * WHAT IS VOCABULARY AND STAYS WITH THE HOST: the server's NAME, its version, and
110
+ * the `instructions` string an agent reads on connect. Those describe one
111
+ * particular product's tool surface, so they arrive as {@link McpJsonRpcOptions}
112
+ * rather than being written here.
113
+ */
114
+ /** The MCP protocol revision this transport implements. */
115
+ declare const MCP_PROTOCOL_VERSION = "2025-06-18";
116
+ /**
117
+ * JSON-RPC error code for "authentication required", returned by `tools/call`
118
+ * when the request carried no valid bearer. Outside the reserved -32768..-32000
119
+ * band's *defined* codes on purpose: it is an implementation-defined server
120
+ * error, and a host maps it to HTTP 401.
121
+ */
122
+ declare const UNAUTHORIZED_CODE = -32001;
123
+ interface JsonRpcRequest {
124
+ jsonrpc: "2.0";
125
+ id?: string | number | null;
126
+ method: string;
127
+ params?: unknown;
128
+ }
129
+ interface JsonRpcResponse {
130
+ jsonrpc: "2.0";
131
+ id: string | number | null;
132
+ result?: unknown;
133
+ error?: {
134
+ code: number;
135
+ message: string;
136
+ };
137
+ }
138
+ /** What a client is told it connected to, in `initialize`'s `serverInfo`. */
139
+ interface McpServerInfo {
140
+ /** The server's name, as a connected host displays it. */
141
+ name: string;
142
+ /**
143
+ * The advertised surface version.
144
+ *
145
+ * This is the ONLY signal a client gets that the tool surface changed: the
146
+ * transport is request/response only, so `notifications/tools/list_changed`
147
+ * can never be sent, and a host that cached `tools/list` at the handshake has
148
+ * no other reason to ask again. See `server/surface-lock.ts` for the guard
149
+ * that makes forgetting to move it a build error instead of a comment.
150
+ */
151
+ version: string;
152
+ }
153
+ interface McpJsonRpcOptions {
154
+ /** The host's identity, returned verbatim in `initialize`. */
155
+ serverInfo: McpServerInfo;
156
+ /**
157
+ * Server-level guidance surfaced to the model on `initialize` (the MCP spec's
158
+ * optional `instructions` field). Omitted from the result when absent, rather
159
+ * than sent empty — a blank string is a claim that there is guidance.
160
+ */
161
+ instructions?: string;
162
+ /**
163
+ * Override the advertised protocol revision. Defaults to
164
+ * {@link MCP_PROTOCOL_VERSION}; a host should not normally set it.
165
+ */
166
+ protocolVersion?: string;
167
+ }
168
+ /**
169
+ * Handle one MCP JSON-RPC request.
170
+ *
171
+ * Returns `null` for notifications (no id, no reply expected). `auth` is the
172
+ * verified caller identity, or `null` when the request carried no valid bearer —
173
+ * `tools/call` then returns {@link UNAUTHORIZED_CODE}, which the host surfaces as
174
+ * HTTP 401. Discovery (`initialize`, `ping`, `tools/list`) stays open, so a client
175
+ * can read the surface before it has a token.
176
+ */
177
+ declare function handleMcpJsonRpc(request: JsonRpcRequest, registry: ToolRegistry, auth: RequestAuth | null, options: McpJsonRpcOptions): Promise<JsonRpcResponse | null>;
178
+
179
+ /**
180
+ * The manifest is the committed source-of-truth artifact the CI drift gate
181
+ * (`mcp:check` → `12-apps/ci` `mcp-contract.yml`) diffs against a fresh
182
+ * regeneration. If an endpoint's schema changes without the manifest being
183
+ * regenerated, the diff fails the build — that is how the served MCP surface is
184
+ * kept in lockstep with the endpoint surface.
185
+ */
186
+ interface BuildManifestOptions {
187
+ /** Bumped intentionally on any tool-shape change (mirrors the golden catalog). */
188
+ version: number;
189
+ /** Human label for the spec, e.g. "acme web @ openapi.json". */
190
+ source: string;
191
+ }
192
+ declare function buildManifest(tools: GeneratedTool[], options: BuildManifestOptions): ToolManifest;
193
+ /**
194
+ * Canonical JSON for a manifest — deep-key-sorted and trailing-newline'd, so the
195
+ * committed artifact and a regeneration diff cleanly (no key-order or whitespace
196
+ * churn). `mcp:check` regenerates, serializes with this, and `git diff --exit-code`s.
197
+ */
198
+ declare function serializeManifest(manifest: ToolManifest): string;
199
+
200
+ /**
201
+ * Making a server's advertised version impossible to leave behind.
202
+ *
203
+ * THE PROBLEM, which every MCP server on this transport has. `tools/list` is
204
+ * answered on request and there is no server→client stream, so a server cannot
205
+ * push `notifications/tools/list_changed` — and one that declares
206
+ * `capabilities.tools.listChanged` without being able to send it is worse than
207
+ * one that is honest, because the host then stops checking for itself. What is
208
+ * left is `serverInfo.version` from the `initialize` handshake. A host caches the
209
+ * tool list against it, so a version that never moves gives it no reason to ever
210
+ * ask again: a tool that shipped stays invisible to every ALREADY CONNECTED
211
+ * client for as long as that connection lives.
212
+ *
213
+ * That is not a hypothetical. In the origin host a new tool reached production,
214
+ * answered on its route, and did not appear in a live connector — behind a
215
+ * `serverInfo.version` frozen at its initial value while ~280 tools were added
216
+ * underneath it. Nothing was broken; the only thing asking anyone to bump it was
217
+ * a comment, and a rule enforced by a comment is not enforced.
218
+ *
219
+ * THE MECHANISM. An app commits a lock recording WHICH surface its current
220
+ * version stands for. Its generator recomputes the digest and refuses to write
221
+ * the artifacts when the digest moved while the version did not, naming the
222
+ * value to set. Because the same generator run under `--check` is what the
223
+ * contract gate already diffs, the failure lands in CI and in a pre-push hook
224
+ * without a new job, without git history, and without any event-sensitivity.
225
+ *
226
+ * WHY A DIGEST OF THE SURFACE, NOT A PATHS FILTER. A `paths:` list over the
227
+ * server's own directory is wrong in both directions: it fires on edits no
228
+ * client can see (a comment in an auth helper) and misses real ones that enter
229
+ * from outside it (a schema whose ceiling is imported from a storage module).
230
+ * Hashing what the tools ARE — the canonical manifest serialization — has
231
+ * neither failure mode: it is exactly the bytes `tools/list` would return.
232
+ */
233
+ /** The committed record: which surface an app's current version stands for. */
234
+ interface SurfaceLock {
235
+ /** The app's surface version at the time `digest` was recorded. */
236
+ version: number;
237
+ /** Digest of the served tool surface (see {@link surfaceDigest}). */
238
+ digest: string;
239
+ }
240
+ /** Everything {@link surfaceLockProblem} needs to judge one generation. */
241
+ interface SurfaceLockCheck {
242
+ /** The lock as committed, or `null` when there is none to contradict. */
243
+ previous: SurfaceLock | null;
244
+ /** The surface version the app currently declares. */
245
+ version: number;
246
+ /** Digest of the surface being generated now. */
247
+ digest: string;
248
+ /**
249
+ * Where the app's version constant lives, repo-relative — quoted in the
250
+ * failure so the fix is a path and a value rather than a hunt.
251
+ */
252
+ versionLocation: string;
253
+ /** The constant's name, if the app does not use the default. */
254
+ versionName?: string;
255
+ }
256
+ /**
257
+ * Digest of a served tool surface — every tool's name, description, annotations
258
+ * and input/output schemas, in the manifest's own canonical (deep-key-sorted)
259
+ * serialization. Stable across unrelated reordering, and identical for two
260
+ * surfaces that a client could not tell apart.
261
+ */
262
+ declare function surfaceDigest(tools: GeneratedTool[], source: string): string;
263
+ /** Canonical JSON for a committed lock (trailing newline, like the manifest). */
264
+ declare function serializeSurfaceLock(lock: SurfaceLock): string;
265
+ /**
266
+ * Decide whether an app's current version may stand for its current surface.
267
+ *
268
+ * Returns the problem as a sentence ready to print, or `null` when the pair is
269
+ * consistent. Four outcomes, and three of them pass:
270
+ *
271
+ * - surface unchanged → fine, whatever the version did (a release bump with no
272
+ * surface change is legitimate and must not be blocked);
273
+ * - surface changed AND version moved → fine, that is the whole contract;
274
+ * - no lock to contradict (first run, or the file was deleted) → fine, it is
275
+ * simply recorded;
276
+ * - surface changed and version did not → the failure this exists for.
277
+ */
278
+ declare function surfaceLockProblem(check: SurfaceLockCheck): string | null;
279
+
280
+ /**
281
+ * OAuth 2.0 Protected Resource Metadata (RFC 9728), as required by the MCP
282
+ * authorization spec: the MCP endpoint is an OAuth *resource server*. Agent hosts
283
+ * (Claude.ai / ChatGPT connectors) discover where to obtain a token by reading
284
+ * `/.well-known/oauth-protected-resource`, and on a 401 the resource server points
285
+ * them at that document via a `WWW-Authenticate` challenge.
286
+ *
287
+ * This module only builds the discovery documents/headers — validating the
288
+ * resulting access token is the app's job (the {@link import("../types").AuthResolver}),
289
+ * because it depends on the app's authorization server and key material.
290
+ */
291
+ interface ProtectedResourceMetadataInput {
292
+ /** Canonical resource identifier — the MCP endpoint URL (the token audience). */
293
+ resource: string;
294
+ /** Authorization server issuer URLs that can mint tokens for this resource. */
295
+ authorizationServers: string[];
296
+ /** Scopes the resource server understands (advertised to clients). */
297
+ scopesSupported?: string[];
298
+ /** Human-facing docs URL for the protected resource, if any. */
299
+ resourceDocumentation?: string;
300
+ }
301
+ /** The RFC 9728 metadata document served at `/.well-known/oauth-protected-resource`. */
302
+ interface ProtectedResourceMetadata {
303
+ resource: string;
304
+ authorization_servers: string[];
305
+ bearer_methods_supported: string[];
306
+ scopes_supported?: string[];
307
+ resource_documentation?: string;
308
+ }
309
+ declare function buildProtectedResourceMetadata(input: ProtectedResourceMetadataInput): ProtectedResourceMetadata;
310
+ /**
311
+ * Build the `WWW-Authenticate` value for an unauthorized MCP response, pointing
312
+ * the client at the protected-resource metadata so it can start the OAuth flow.
313
+ * Per RFC 9728 §5.1 the challenge carries a `resource_metadata` parameter.
314
+ */
315
+ declare function bearerChallenge(params: {
316
+ resourceMetadataUrl: string;
317
+ error?: "invalid_token" | "insufficient_scope";
318
+ errorDescription?: string;
319
+ }): string;
320
+ /** Standard path for the protected-resource metadata document. */
321
+ declare const PROTECTED_RESOURCE_METADATA_PATH = "/.well-known/oauth-protected-resource";
322
+
323
+ /**
324
+ * OAuth 2.0 Authorization Server Metadata (RFC 8414), the discovery half that
325
+ * complements the RFC 9728 protected-resource metadata in `resource-metadata.ts`.
326
+ * Agent hosts (Claude.ai / ChatGPT connectors) read
327
+ * `/.well-known/oauth-authorization-server` to learn where to start the OAuth
328
+ * 2.1 Authorization Code + PKCE flow.
329
+ *
330
+ * This builder is a pure function of `(issuer, scopes)` with no Next.js/request
331
+ * coupling, so it can move verbatim into the future `@12-apps/mcp` extraction.
332
+ * It derives every endpoint from the same issuer origin the resource metadata
333
+ * advertises, so the two discovery documents cannot drift.
334
+ */
335
+ interface AuthorizationServerMetadataInput {
336
+ /** Authorization server issuer URL (origin) — also the resource issuer. */
337
+ issuer: string;
338
+ /** Scopes the authorization server advertises (from the shared scope source). */
339
+ scopesSupported: string[];
340
+ /**
341
+ * Client authentication methods the token endpoint accepts. Defaults to
342
+ * public PKCE clients (`none`) plus HTTP Basic client-secret auth.
343
+ */
344
+ tokenEndpointAuthMethods?: string[];
345
+ /**
346
+ * Where the endpoints are actually mounted, if not at the defaults below. A
347
+ * host that moves an endpoint MUST move it here too: this document is the only
348
+ * thing a connector reads before its first request, so a path that lies here is
349
+ * a flow that fails at the first hop (12-23 — `createApiMcpOauth` passes its
350
+ * resolved paths, so the two cannot disagree).
351
+ */
352
+ paths?: Partial<AuthorizationServerPaths>;
353
+ }
354
+ /** The endpoint paths this document advertises, relative to the issuer origin. */
355
+ interface AuthorizationServerPaths {
356
+ authorize: string;
357
+ token: string;
358
+ register: string;
359
+ jwks: string;
360
+ }
361
+ /** The RFC 8414 document served at `/.well-known/oauth-authorization-server`. */
362
+ interface AuthorizationServerMetadata {
363
+ issuer: string;
364
+ authorization_endpoint: string;
365
+ token_endpoint: string;
366
+ registration_endpoint: string;
367
+ jwks_uri: string;
368
+ scopes_supported: string[];
369
+ response_types_supported: string[];
370
+ grant_types_supported: string[];
371
+ code_challenge_methods_supported: string[];
372
+ token_endpoint_auth_methods_supported: string[];
373
+ }
374
+ /**
375
+ * Build the RFC 8414 authorization-server metadata document from an issuer
376
+ * origin and the supported scopes. Endpoints are derived from `issuer`; the
377
+ * OAuth 2.1 + PKCE contract fixes `response_types_supported`,
378
+ * `grant_types_supported`, and `code_challenge_methods_supported`.
379
+ */
380
+ declare function buildAuthorizationServerMetadata(input: AuthorizationServerMetadataInput): AuthorizationServerMetadata;
381
+
382
+ export { type AuthorizationServerMetadata, type AuthorizationServerMetadataInput, type AuthorizationServerPaths, type BuildManifestOptions, DispatchConfig, DispatchInputError, DispatchResult, GeneratedTool, HTTP_STATUS_META_KEY, type JsonRpcRequest, type JsonRpcResponse, JsonSchema, MCP_PROTOCOL_VERSION, type McpJsonRpcOptions, type McpServerInfo, type McpToolDescriptor, type McpToolResult, PROTECTED_RESOURCE_METADATA_PATH, type ProtectedResourceMetadata, type ProtectedResourceMetadataInput, type RegistryOptions, RequestAuth, type SurfaceLock, type SurfaceLockCheck, ToolAnnotations, ToolManifest, type ToolRegistry, UNAUTHORIZED_CODE, UnsupportedSchemaError, bearerChallenge, buildAuthorizationServerMetadata, buildManifest, buildProtectedResourceMetadata, createToolRegistry, dispatchTool, handleMcpJsonRpc, inlineSchemaRefs, serializeManifest, serializeSurfaceLock, surfaceDigest, surfaceLockProblem };