@webpieces/mcp-server 0.4.772 → 0.4.774

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 (42) hide show
  1. package/README.md +114 -8
  2. package/package.json +11 -5
  3. package/src/McpApiBinding.d.ts +14 -0
  4. package/src/McpApiBinding.js +42 -0
  5. package/src/McpApiBinding.js.map +1 -0
  6. package/src/McpApiDispatcher.d.ts +10 -19
  7. package/src/McpApiDispatcher.js +35 -44
  8. package/src/McpApiDispatcher.js.map +1 -1
  9. package/src/McpAuth.d.ts +11 -2
  10. package/src/McpAuth.js +5 -1
  11. package/src/McpAuth.js.map +1 -1
  12. package/src/McpAuthorityCompileAssertions.js.map +1 -1
  13. package/src/McpBindOptions.d.ts +17 -0
  14. package/src/McpBindOptions.js +36 -0
  15. package/src/McpBindOptions.js.map +1 -0
  16. package/src/McpDeployment.d.ts +11 -0
  17. package/src/McpDeployment.js +32 -0
  18. package/src/McpDeployment.js.map +1 -0
  19. package/src/McpInvocationContext.d.ts +14 -0
  20. package/src/McpInvocationContext.js +25 -0
  21. package/src/McpInvocationContext.js.map +1 -0
  22. package/src/McpToolRegistry.d.ts +7 -4
  23. package/src/McpToolRegistry.js +20 -16
  24. package/src/McpToolRegistry.js.map +1 -1
  25. package/src/WpMcpErrorTranslator.d.ts +69 -0
  26. package/src/WpMcpErrorTranslator.js +168 -0
  27. package/src/WpMcpErrorTranslator.js.map +1 -0
  28. package/src/WpMcpServer.d.ts +55 -24
  29. package/src/WpMcpServer.js +331 -154
  30. package/src/WpMcpServer.js.map +1 -1
  31. package/src/__tests__/McpHttpTestHarness.d.ts +40 -0
  32. package/src/__tests__/McpHttpTestHarness.js +158 -0
  33. package/src/__tests__/McpHttpTestHarness.js.map +1 -0
  34. package/src/__tests__/McpRemoteFixtures.d.ts +93 -0
  35. package/src/__tests__/McpRemoteFixtures.js +409 -0
  36. package/src/__tests__/McpRemoteFixtures.js.map +1 -0
  37. package/src/__tests__/WpMcpServerTestFixtures.d.ts +60 -0
  38. package/src/__tests__/WpMcpServerTestFixtures.js +269 -0
  39. package/src/__tests__/WpMcpServerTestFixtures.js.map +1 -0
  40. package/src/index.d.ts +10 -3
  41. package/src/index.js +18 -4
  42. package/src/index.js.map +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"WpMcpServer.js","sourceRoot":"","sources":["../../../../../packages/http/mcp-server/src/WpMcpServer.ts"],"names":[],"mappings":"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","sourcesContent":["import { Server } from '@modelcontextprotocol/sdk/server/index.js';\nimport {\n CallToolRequest,\n CallToolRequestSchema,\n CallToolResult,\n ErrorCode,\n ListToolsRequestSchema,\n McpError,\n} from '@modelcontextprotocol/sdk/types.js';\nimport {\n ApiErrorPayload,\n ApiUnauthorizedError,\n DtoValue,\n LogManager,\n toError,\n} from '@webpieces/core-util';\nimport { ApiFactory, ClassType, MintedJwt } from '@webpieces/http-routing';\nimport {\n MAX_MCP_ACCESS_TOKEN_LIFETIME_SECONDS,\n McpEndpointDescriptor,\n McpProtectedResourceMetadata,\n VerifiedMcpCredential,\n WpMcpServerConfig,\n} from './McpAuth';\nimport { McpApiDispatcher } from './McpApiDispatcher';\nimport { McpToolRegistry, RegisteredMcpTool } from './McpToolRegistry';\n\nconst log = LogManager.getLogger('WpMcpServer');\n\n/** The only error shape rendered into model-visible MCP content. */\nexport class ModelVisibleToolError {\n constructor(\n public readonly kind: string,\n public readonly message: string,\n public readonly requestId?: string,\n public readonly field?: string,\n public readonly callerMessage?: string,\n public readonly errorCode?: string,\n public readonly retryAfterSeconds?: number,\n ) {}\n}\n\n/**\n * Node-only MCP adapter. Build one Server per authenticated transport/session; every tool call still\n * re-enters the endpoint's ordinary AuthFilter, so tools/list visibility never grants access.\n */\nexport class WpMcpServer<TGrant, TMintRequest> {\n private readonly registry: McpToolRegistry;\n private readonly dispatcher: McpApiDispatcher;\n\n constructor(\n private readonly config: WpMcpServerConfig<TGrant, TMintRequest>,\n apiFactory: ApiFactory,\n apiClasses: readonly ClassType[],\n ) {\n this.registry = new McpToolRegistry(apiClasses);\n this.dispatcher = new McpApiDispatcher(apiFactory);\n }\n\n async build(accessToken: string): Promise<Server> {\n await this.verifyCredential(accessToken);\n return this.buildForAccessToken(accessToken);\n }\n\n protectedResourceMetadata(): McpProtectedResourceMetadata {\n return this.config.protectedResourceMetadata();\n }\n\n private buildForAccessToken(accessToken: string): Server {\n const server = new Server(\n // webpieces-disable no-anonymous-object-literals -- external MCP SDK request structure\n { name: this.config.name, version: this.config.version },\n // webpieces-disable no-anonymous-object-literals -- external MCP SDK capability structure\n { capabilities: { tools: {} } },\n );\n server.setRequestHandler(ListToolsRequestSchema, async () => {\n const credential = await this.verifyCredential(accessToken);\n return {\n tools: this.registry.tools\n .filter((tool: RegisteredMcpTool) => tool.isVisibleTo(credential.listingRoles))\n .map((tool: RegisteredMcpTool) => ({\n name: tool.name,\n description: tool.description,\n inputSchema: tool.inputSchema,\n outputSchema: tool.outputSchema,\n annotations: tool.annotations,\n })),\n };\n });\n server.setRequestHandler(CallToolRequestSchema, async (request: CallToolRequest) => {\n const tool = this.registry.find(request.params.name);\n if (!tool) {\n throw new McpError(ErrorCode.InvalidParams, `Unknown tool: ${request.params.name}`);\n }\n return this.call(tool, request.params.arguments ?? {}, accessToken);\n });\n return server;\n }\n\n private async verifyCredential(accessToken: string): Promise<VerifiedMcpCredential> {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- security boundary logs owner detail and exposes only a generic authentication failure\n try {\n const credential = await this.config.accessTokenAuthority.verifyAccessToken(\n accessToken,\n this.config.resource,\n );\n this.validateCredential(credential);\n return credential;\n } catch (err: unknown) {\n const error = toError(err);\n log.warn('MCP access-token verification rejected a request', error);\n throw new ApiUnauthorizedError('MCP access token rejected.', undefined, error);\n }\n }\n\n private validateCredential(credential: VerifiedMcpCredential): void {\n const now = Math.floor(Date.now() / 1000);\n if (credential.subject.trim() === '') {\n throw new Error('MCP access token has no subject.');\n }\n if (\n !Number.isFinite(credential.issuedAtEpochSeconds) ||\n !Number.isFinite(credential.expiresAtEpochSeconds) ||\n !Number.isFinite(credential.accountValidatedAtEpochSeconds)\n ) {\n throw new Error('MCP access token security timestamps must be finite.');\n }\n if (credential.resource !== this.config.resource) {\n throw new Error('MCP access token was not issued for this protected resource.');\n }\n if (!this.config.authorizationServers.includes(credential.issuer)) {\n throw new Error('MCP access token issuer is not trusted by this protected resource.');\n }\n if (credential.issuedAtEpochSeconds > now) {\n throw new Error('MCP access token was issued in the future.');\n }\n if (credential.expiresAtEpochSeconds <= now) {\n throw new Error('MCP access token has expired.');\n }\n if (\n credential.expiresAtEpochSeconds - credential.issuedAtEpochSeconds >\n MAX_MCP_ACCESS_TOKEN_LIFETIME_SECONDS\n ) {\n throw new Error('MCP access token lifetime exceeds 30 days.');\n }\n for (const scope of this.config.requiredScopes) {\n if (!credential.scopes.includes(scope)) {\n throw new Error(`MCP access token is missing required scope '${scope}'.`);\n }\n }\n if (\n credential.accountValidatedAtEpochSeconds > now ||\n now - credential.accountValidatedAtEpochSeconds > this.config.maxAccountValidationAgeSeconds\n ) {\n throw new Error('MCP account authorization state is not fresh enough for dispatch.');\n }\n }\n\n private async call(\n tool: RegisteredMcpTool,\n args: DtoValue,\n accessToken: string,\n ): Promise<CallToolResult> {\n let credential: VerifiedMcpCredential;\n // webpieces-disable no-unmanaged-exceptions -- MCP transport boundary normalizes authentication failures before they reach the model\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- MCP transport boundary\n try {\n credential = await this.verifyCredential(accessToken);\n } catch (err: unknown) {\n //const error = toError(err);\n throw new McpError(ErrorCode.InvalidRequest, 'Unauthorized');\n }\n let endpointJwt: MintedJwt;\n // webpieces-disable no-unmanaged-exceptions -- MCP transport boundary logs mint failures and exposes only a generic protocol error\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- MCP transport boundary\n try {\n const descriptor = new McpEndpointDescriptor(\n tool.name,\n tool.apiClass.name,\n tool.methodName,\n );\n const mintRequest = this.config.endpointMintRequest(credential, descriptor);\n endpointJwt = await this.config.endpointJwtAuthority.mint(mintRequest);\n this.validateEndpointJwt(endpointJwt, accessToken);\n } catch (err: unknown) {\n const error = toError(err);\n log.error(\n `MCP endpoint credential mint failed for ${tool.apiClass.name}.${tool.methodName}`,\n error,\n );\n throw new McpError(ErrorCode.InternalError, 'Internal Error');\n }\n const result = await this.dispatcher.call(tool, args ?? {}, endpointJwt.token);\n if (!result.success) return this.apiError(result.error, result.requestId);\n\n const outputFailure = this.registry.schemaBuilder.validate(tool.responseClass, result.value);\n if (outputFailure) {\n log.error(\n `MCP output schema violation for ${tool.apiClass.name}.${tool.methodName}: ${outputFailure.message}`,\n );\n return this.errorResult(\n new ModelVisibleToolError('implementation', 'Internal Error', result.requestId),\n );\n }\n let structured: Record<string, DtoValue>;\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- transport boundary sanitizes serialization failures\n try {\n structured = this.record(result.value);\n } catch (err: unknown) {\n const error = toError(err);\n log.error(\n `MCP response serialization failed for ${tool.apiClass.name}.${tool.methodName}`,\n error,\n );\n return this.errorResult(\n new ModelVisibleToolError('implementation', 'Internal Error', result.requestId),\n );\n }\n return {\n content: [{ type: 'text', text: JSON.stringify(structured) }],\n structuredContent: structured,\n };\n }\n\n private validateEndpointJwt(endpointJwt: MintedJwt, accessToken: string): void {\n const now = Math.floor(Date.now() / 1000);\n if (endpointJwt.token === accessToken) {\n throw new Error('MCP access-token passthrough to endpoint dispatch is forbidden.');\n }\n if (endpointJwt.expiresAtEpochSeconds <= now) {\n throw new Error('MCP endpoint JWT has already expired.');\n }\n if (\n endpointJwt.expiresAtEpochSeconds - now >\n this.config.maxEndpointJwtLifetimeSeconds\n ) {\n throw new Error('MCP endpoint JWT lifetime exceeds the configured one-hour ceiling.');\n }\n }\n\n private apiError(payload: ApiErrorPayload, requestId: string): CallToolResult {\n return this.errorResult(\n new ModelVisibleToolError(\n payload.kind,\n payload.message,\n requestId,\n payload.field,\n payload.callerMessage,\n payload.errorCode,\n payload.retryAfterSeconds,\n ),\n );\n }\n\n private errorResult(error: ModelVisibleToolError): CallToolResult {\n const structured = this.record(error);\n return {\n content: [{ type: 'text', text: JSON.stringify(structured) }],\n // `outputSchema` describes the endpoint's success DTO. MCP clients validate\n // structuredContent against it even when isError is true, so error details must stay\n // in text content until the protocol supports a separate error schema.\n isError: true,\n };\n }\n\n private record(value: DtoValue): Record<string, DtoValue> {\n const json = JSON.stringify(value);\n if (json === undefined) throw new Error('MCP structured content is not JSON serializable.');\n const parsed = JSON.parse(json) as DtoValue;\n if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {\n // webpieces-disable no-anonymous-object-literals -- external MCP structured-content wrapper\n return { value: parsed };\n }\n return parsed as Record<string, DtoValue>;\n }\n}\n"]}
1
+ {"version":3,"file":"WpMcpServer.js","sourceRoot":"","sources":["../../../../../packages/http/mcp-server/src/WpMcpServer.ts"],"names":[],"mappings":"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","sourcesContent":["import { createHash } from 'node:crypto';\nimport {\n AuthInfo,\n CallToolRequest,\n CallToolResult,\n createMcpHandler,\n ListToolsResult,\n McpHttpHandler,\n McpRequestContext,\n McpServer,\n ProgressToken,\n ServerContext,\n ServerOptions,\n Tool,\n Implementation,\n} from '@modelcontextprotocol/server';\nimport { NodeMcpRequestHandler, toNodeHandler } from '@modelcontextprotocol/node';\nimport { Express, json, NextFunction, Request, Response } from 'express';\nimport {\n ApiForbiddenError,\n ApiImplementationError,\n ApiUnauthorizedError,\n DtoValue,\n LogManager,\n toError,\n WebpiecesCoreHeaders,\n} from '@webpieces/core-util';\nimport { HttpRequest, RequestContext, RequestContextHeaders } from '@webpieces/core-context';\nimport { MintedJwt } from '@webpieces/http-routing';\nimport {\n MAX_MCP_ACCESS_TOKEN_LIFETIME_SECONDS,\n McpEndpointDescriptor,\n McpProtectedResourceMetadata,\n VerifiedMcpCredential,\n WpMcpServerConfig,\n} from './McpAuth';\nimport { McpApiDispatcher } from './McpApiDispatcher';\nimport { McpBindOptions } from './McpBindOptions';\nimport { McpInvocationContext, McpProgressReporter } from './McpInvocationContext';\nimport { McpToolRegistry, RegisteredMcpTool } from './McpToolRegistry';\nimport { McpFailureScope, WpMcpErrorTranslator } from './WpMcpErrorTranslator';\n\nconst log = LogManager.getLogger('WpMcpServer');\n\ntype AuthenticatedExpressRequest = Request & { auth?: AuthInfo };\n\n/** Facts established once per POST at the bind boundary and handed to the SDK handlers. */\nclass McpPostAuthentication {\n constructor(\n public readonly accessToken: string,\n public readonly credential: VerifiedMcpCredential,\n public readonly disconnectSignal: AbortSignal,\n ) {}\n}\n\n/**\n * The official SDK server with Webpieces-owned tools/list and tools/call handlers. It answers\n * `toolInputSchemaJson` from the Webpieces registry so the SDK's pre-dispatch SEP-2243\n * `Mcp-Param-*` header validation still runs without registering SDK-side tool callbacks.\n */\nclass WpSdkMcpServer extends McpServer {\n constructor(\n info: Implementation,\n options: ServerOptions,\n private readonly registry: McpToolRegistry,\n ) {\n super(info, options);\n }\n\n // webpieces-disable no-any-unknown -- signature is fixed by the SDK's JSON Schema record type\n override toolInputSchemaJson(name: string): Record<string, unknown> | undefined {\n return this.registry.find(name)?.inputSchema as Record<string, DtoValue> | undefined;\n }\n}\n\n/**\n * Modern-only MCP 2026-07-28 adapter around the official SDK's request-scoped HTTP handler.\n *\n * Error boundary: every failure is mapped by the one `WpMcpErrorTranslator`, and each entry point\n * has exactly one catch that only delegates to it: the bind HTTP handler (`toHttp`), tools/list\n * (`toProtocolError`) and tools/call (`toToolResult`). `subscriptions/listen` is served entirely by\n * the SDK's listen router, so Webpieces has no handler (and no catch) there. The external bearer is\n * verified exactly once per POST, at the HTTP boundary, before the SDK is involved.\n */\nexport class WpMcpServer<TGrant, TMintRequest> {\n private readonly dispatcher = new McpApiDispatcher();\n private readonly translator = new WpMcpErrorTranslator();\n private registry?: McpToolRegistry;\n private handler?: McpHttpHandler;\n private streamingHandler?: McpHttpHandler;\n private nodeHandler?: NodeMcpRequestHandler;\n private streamingNodeHandler?: NodeMcpRequestHandler;\n private revision?: string;\n\n constructor(private readonly config: WpMcpServerConfig<TGrant, TMintRequest>) {}\n\n bind(app: Express, options: McpBindOptions): void {\n if (this.handler) throw new Error('WpMcpServer.bind(...) may be called only once.');\n this.registry = new McpToolRegistry(options.bindings);\n this.revision = this.calculateRegistryRevision(this.registry);\n this.handler = this.createHandler(options, 'auto');\n this.streamingHandler = this.createHandler(options, 'sse');\n this.nodeHandler = toNodeHandler(this.handler, {\n onerror: (error: Error) => log.error('MCP Node transport failed', error),\n });\n this.streamingNodeHandler = toNodeHandler(this.streamingHandler, {\n onerror: (error: Error) => log.error('MCP streaming Node transport failed', error),\n });\n app.post(\n options.endpointPath,\n json(),\n async (req: Request, res: Response): Promise<void> =>\n this.handleHttp(req, res, options),\n // Express routes body-parser failures (bad JSON, body too large) to an error handler.\n (err: Error, req: Request, res: Response, _next: NextFunction): void =>\n RequestContext.run(() => {\n new RequestContextHeaders().fillFromRequest(this.toHttpRequest(req));\n this.translator.toHttpBodyFailure(\n err,\n res,\n this.httpScope(req),\n this.challenge(),\n );\n }),\n );\n }\n\n protectedResourceMetadata(): McpProtectedResourceMetadata {\n return this.config.protectedResourceMetadata();\n }\n\n /** Gracefully closes active listen/request streams; clients must reconnect and refresh lists. */\n async close(): Promise<void> {\n await Promise.all([this.handler?.close(), this.streamingHandler?.close()]);\n }\n\n /** Level-triggered invalidation; the authoritative list is always fetched again by clients. */\n toolsChanged(): void {\n this.handler?.notify.toolsChanged();\n }\n\n get registryRevision(): string | undefined {\n return this.revision;\n }\n\n private createHandler(options: McpBindOptions, responseMode: 'auto' | 'sse'): McpHttpHandler {\n return createMcpHandler(\n (context: McpRequestContext) => this.buildSdkServer(context, options),\n {\n legacy: 'reject',\n responseMode,\n bus: options.deployment.bus,\n maxSubscriptions: options.maxSubscriptions,\n keepAliveMs: options.keepAliveMs,\n onerror: (error: Error) => log.warn('MCP protocol request rejected', error),\n },\n );\n }\n\n /** Entry point #1: the one catch for everything before and around the SDK exchange. */\n private async handleHttp(req: Request, res: Response, options: McpBindOptions): Promise<void> {\n res.setHeader('X-Accel-Buffering', 'no');\n await RequestContext.run(async () => {\n new RequestContextHeaders().fillFromRequest(this.toHttpRequest(req));\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- MCP HTTP entry point; delegates only to WpMcpErrorTranslator\n try {\n await this.serveExpress(req, res, options);\n } catch (err: unknown) {\n const error = toError(err);\n this.translator.toHttp(error, res, this.httpScope(req), this.challenge());\n }\n });\n }\n\n private async serveExpress(\n req: Request,\n res: Response,\n options: McpBindOptions,\n ): Promise<void> {\n const origin = req.header('origin');\n if (origin && !options.allowedOrigins.includes(origin)) {\n throw new ApiForbiddenError(`MCP request Origin '${origin}' is not allowed.`);\n }\n const authentication = await this.authenticate(req);\n const request = req as AuthenticatedExpressRequest;\n request.auth = this.authInfo(authentication);\n const serve = this.hasProgressToken(req.body)\n ? this.streamingNodeHandler\n : this.nodeHandler;\n if (!serve) throw new ApiImplementationError('MCP Node handlers are not initialized.');\n await serve(request, res, req.body);\n }\n\n /** Verifies the external bearer once for this POST. Handlers never re-verify it. */\n private async authenticate(req: Request): Promise<McpPostAuthentication> {\n const match = req.header('authorization')?.match(/^Bearer ([^\\s]+)$/i);\n const token = match?.[1];\n if (!token) throw new ApiUnauthorizedError('MCP request has no bearer access token.');\n const credential = await this.config.accessTokenAuthority.verifyAccessToken(\n token,\n this.config.resource,\n );\n this.validateCredential(credential);\n return new McpPostAuthentication(token, credential, this.disconnectSignal(req));\n }\n\n private disconnectSignal(req: Request): AbortSignal {\n const disconnected = new AbortController();\n const res = req.res;\n const cancel = (): void => disconnected.abort();\n req.once('aborted', cancel);\n req.socket.once('close', cancel);\n res?.once('close', cancel);\n res?.once('finish', (): void => {\n req.off('aborted', cancel);\n req.socket.off('close', cancel);\n res.off('close', cancel);\n });\n return disconnected.signal;\n }\n\n private buildSdkServer(context: McpRequestContext, options: McpBindOptions): McpServer {\n const authentication = context.authInfo?.extra?.['webpiecesAuthentication'];\n if (context.era !== 'modern' || !(authentication instanceof McpPostAuthentication)) {\n throw new ApiImplementationError(\n 'WpMcpServer serves only MCP 2026-07-28 requests authenticated by its bind boundary.',\n );\n }\n const registry = this.requireRegistry();\n const server = new WpSdkMcpServer(\n { name: this.config.name, version: `${this.config.version}+${this.revision}` },\n {\n capabilities: { tools: { listChanged: true } },\n cacheHints: {\n 'tools/list': { ttlMs: options.deployment.ttlMs, cacheScope: 'private' },\n 'server/discover': { ttlMs: options.deployment.ttlMs, cacheScope: 'private' },\n },\n },\n registry,\n );\n server.server.removeRequestHandler('tools/list');\n server.server.removeRequestHandler('tools/call');\n server.server.setRequestHandler(\n 'tools/list',\n async (_request: object, sdkContext: ServerContext): Promise<ListToolsResult> =>\n this.handleListTools(authentication, sdkContext),\n );\n server.server.setRequestHandler(\n 'tools/call',\n async (request: CallToolRequest, sdkContext: ServerContext): Promise<CallToolResult> =>\n this.handleCallTool(server, request, authentication, sdkContext),\n );\n return server;\n }\n\n /** Entry point #2: tools/list has no tool-result channel, so failures are JSON-RPC errors. */\n private async handleListTools(\n authentication: McpPostAuthentication,\n sdkContext: ServerContext,\n ): Promise<ListToolsResult> {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- MCP tools/list entry point; delegates only to WpMcpErrorTranslator\n try {\n return this.listTools(authentication.credential);\n } catch (err: unknown) {\n const error = toError(err);\n throw this.translator.toProtocolError(error, this.sdkScope(sdkContext, 'tools/list'));\n }\n }\n\n private listTools(credential: VerifiedMcpCredential): ListToolsResult {\n const tools: Tool[] = [];\n for (const tool of this.requireRegistry().tools) {\n if (tool.isVisibleTo(credential.listingRoles)) tools.push(this.toolDefinition(tool));\n }\n return { tools };\n }\n\n private toolDefinition(tool: RegisteredMcpTool): Tool {\n return {\n name: tool.name,\n description: tool.description,\n inputSchema: tool.inputSchema as Tool['inputSchema'],\n outputSchema: tool.outputSchema as Tool['outputSchema'],\n annotations: tool.annotations,\n _meta: { webpiecesRegistryRevision: this.revision },\n };\n }\n\n /**\n * Entry point #3: an unknown tool is a JSON-RPC -32602; every failure after the tool is found\n * is an `isError: true` result, identically for local and remote bindings.\n */\n private async handleCallTool(\n server: WpSdkMcpServer,\n request: CallToolRequest,\n authentication: McpPostAuthentication,\n sdkContext: ServerContext,\n ): Promise<CallToolResult> {\n const name = request.params.name;\n const scope = this.sdkScope(sdkContext, 'tools/call', name);\n const tool = this.requireRegistry().find(name);\n if (!tool) throw this.translator.unknownTool(name, scope);\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- MCP tools/call entry point; delegates only to WpMcpErrorTranslator\n try {\n const result = await this.callTool(tool, request, authentication, sdkContext);\n result._meta = this.translator.resultMeta(scope.requestId);\n return server.server.projectCallToolResult(\n result,\n tool.outputSchema as Record<string, DtoValue>,\n );\n } catch (err: unknown) {\n const error = toError(err);\n return this.translator.toToolResult(error, scope);\n }\n }\n\n /** Everything a tool call does. No catch in here: failures propagate to handleCallTool. */\n private async callTool(\n tool: RegisteredMcpTool,\n request: CallToolRequest,\n authentication: McpPostAuthentication,\n sdkContext: ServerContext,\n ): Promise<CallToolResult> {\n const credential = authentication.credential;\n const endpointJwt =\n tool.binding.topology === 'local'\n ? await this.mintEndpointJwt(tool, authentication)\n : undefined;\n const invocation = new McpInvocationContext(\n sdkContext.mcpReq.id,\n tool.name,\n credential.subject,\n credential.listingRoles,\n AbortSignal.any([sdkContext.mcpReq.signal, authentication.disconnectSignal]),\n this.progressReporter(sdkContext),\n );\n const args = (request.params.arguments ?? {}) as DtoValue;\n const value = await this.dispatcher.call(\n tool,\n args,\n credential,\n invocation,\n endpointJwt?.token,\n );\n const outputFailure = this.requireRegistry().schemaBuilder.validate(\n tool.responseClass,\n value,\n );\n if (outputFailure) {\n throw new ApiImplementationError(\n `MCP output schema violation for ${tool.apiClass.name}.${tool.methodName}: ${outputFailure.message}`,\n );\n }\n const structured = this.record(value);\n return {\n content: [{ type: 'text', text: JSON.stringify(structured) }],\n structuredContent: structured,\n };\n }\n\n private async mintEndpointJwt(\n tool: RegisteredMcpTool,\n authentication: McpPostAuthentication,\n ): Promise<MintedJwt> {\n const descriptor = new McpEndpointDescriptor(\n tool.name,\n tool.apiClass.name,\n tool.methodName,\n );\n const minted = await this.config.endpointJwtAuthority.mint(\n this.config.endpointMintRequest(authentication.credential, descriptor),\n );\n const now = Math.floor(Date.now() / 1000);\n if (minted.token === authentication.accessToken) {\n throw new ApiImplementationError(\n `MCP access-token passthrough is forbidden (endpoint JWT for ${tool.name}).`,\n );\n }\n if (\n minted.expiresAtEpochSeconds <= now ||\n minted.expiresAtEpochSeconds - now > this.config.maxEndpointJwtLifetimeSeconds\n ) {\n throw new ApiImplementationError(\n `MCP endpoint JWT lifetime is invalid for ${tool.name}.`,\n );\n }\n return minted;\n }\n\n private validateCredential(credential: VerifiedMcpCredential): void {\n const now = Math.floor(Date.now() / 1000);\n const reject = (reason: string): never => {\n throw new ApiUnauthorizedError(`MCP access token rejected: ${reason}`);\n };\n if (credential.subject.trim() === '') reject('no subject');\n const stamps = [\n credential.issuedAtEpochSeconds,\n credential.expiresAtEpochSeconds,\n credential.accountValidatedAtEpochSeconds,\n ];\n if (!stamps.every(Number.isFinite)) reject('security timestamps must be finite');\n if (credential.resource !== this.config.resource) reject('resource mismatch');\n if (!this.config.authorizationServers.includes(credential.issuer)) {\n reject('issuer is not trusted');\n }\n if (credential.issuedAtEpochSeconds > now || credential.expiresAtEpochSeconds <= now) {\n reject('outside its valid time window');\n }\n const lifetime = credential.expiresAtEpochSeconds - credential.issuedAtEpochSeconds;\n if (lifetime > MAX_MCP_ACCESS_TOKEN_LIFETIME_SECONDS) reject('lifetime exceeds 30 days');\n for (const scope of this.config.requiredScopes) {\n if (!credential.scopes.includes(scope)) reject(`missing required scope '${scope}'`);\n }\n const validatedAge = now - credential.accountValidatedAtEpochSeconds;\n if (\n credential.accountValidatedAtEpochSeconds > now ||\n validatedAge > this.config.maxAccountValidationAgeSeconds\n ) {\n reject('account authorization state is not fresh enough for dispatch');\n }\n }\n\n private authInfo(authentication: McpPostAuthentication): AuthInfo {\n const credential = authentication.credential;\n return {\n token: authentication.accessToken,\n clientId: credential.subject,\n scopes: [...credential.scopes],\n expiresAt: credential.expiresAtEpochSeconds,\n resource: new URL(credential.resource),\n extra: { webpiecesAuthentication: authentication },\n };\n }\n\n private progressReporter(context: ServerContext): McpProgressReporter | undefined {\n const token = context.mcpReq._meta?.progressToken;\n if (token === undefined) return undefined;\n return async (progress: number, total?: number, message?: string): Promise<void> => {\n await context.mcpReq.notify({\n method: 'notifications/progress',\n params: this.progressParams(token, progress, total, message),\n });\n };\n }\n\n private progressParams(\n progressToken: ProgressToken,\n progress: number,\n total?: number,\n message?: string,\n ): Record<string, DtoValue> {\n const params: Record<string, DtoValue> = { progressToken, progress };\n if (total !== undefined) params['total'] = total;\n if (message !== undefined) params['message'] = message;\n return params;\n }\n\n private requestId(): string {\n return RequestContext.getUntrusted(WebpiecesCoreHeaders.REQUEST_ID) ?? 'missing-request-id';\n }\n\n private sdkScope(context: ServerContext, method: string, toolName?: string): McpFailureScope {\n return new McpFailureScope(this.requestId(), context.mcpReq.id, method, toolName);\n }\n\n private httpScope(req: Request): McpFailureScope {\n const body = this.bodyObject(req.body);\n const id = body?.['id'];\n const method = body?.['method'];\n const params = this.bodyObject(body?.['params']);\n const name = params?.['name'];\n return new McpFailureScope(\n this.requestId(),\n typeof id === 'string' || typeof id === 'number' ? id : null,\n typeof method === 'string' ? method : 'unknown',\n typeof name === 'string' ? name : undefined,\n );\n }\n\n private challenge(): string {\n return `Bearer resource_metadata=\"${this.config.resource}\"`;\n }\n\n private bodyObject(value: DtoValue | undefined): Record<string, DtoValue> | undefined {\n if (typeof value !== 'object' || value === null || Array.isArray(value)) return undefined;\n return value as Record<string, DtoValue>;\n }\n\n private hasProgressToken(body: DtoValue): boolean {\n const params = this.bodyObject(this.bodyObject(body)?.['params']);\n return this.bodyObject(params?.['_meta'])?.['progressToken'] !== undefined;\n }\n\n private toHttpRequest(req: Request): HttpRequest {\n const headers = new Map<string, string[]>();\n for (const [name, value] of Object.entries(req.headers)) {\n if (typeof value === 'string') headers.set(name, [value]);\n else if (Array.isArray(value)) headers.set(name, value);\n }\n return new HttpRequest(req.method, req.originalUrl || req.path, headers);\n }\n\n private calculateRegistryRevision(registry: McpToolRegistry): string {\n const surface = registry.tools.map((tool: RegisteredMcpTool) => [\n tool.name,\n tool.description,\n tool.inputSchema,\n tool.outputSchema,\n tool.annotations,\n ]);\n return createHash('sha256').update(JSON.stringify(surface)).digest('hex').slice(0, 12);\n }\n\n private requireRegistry(): McpToolRegistry {\n if (!this.registry) throw new Error('Call WpMcpServer.bind(...) before serving requests.');\n return this.registry;\n }\n\n private record(value: DtoValue): Record<string, DtoValue> {\n const jsonValue = JSON.stringify(value);\n if (jsonValue === undefined) {\n throw new ApiImplementationError('MCP structured content is not JSON serializable.');\n }\n const parsed = JSON.parse(jsonValue) as DtoValue;\n if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {\n return { value: parsed };\n }\n return parsed as Record<string, DtoValue>;\n }\n}\n"]}
@@ -0,0 +1,40 @@
1
+ import { Server } from 'node:http';
2
+ import { Express } from 'express';
3
+ export interface RpcResponse {
4
+ result?: Record<string, unknown>;
5
+ error?: {
6
+ code: number;
7
+ message: string;
8
+ data?: Record<string, unknown>;
9
+ };
10
+ }
11
+ export declare class McpPostReply {
12
+ readonly response: Response;
13
+ readonly payload: RpcResponse;
14
+ constructor(response: Response, payload: RpcResponse);
15
+ }
16
+ /** Starts and stops loopback HTTP servers for MCP specs. */
17
+ export declare class TestServers {
18
+ static listen(app: Express): Promise<Server>;
19
+ static urlOf(server: Server): string;
20
+ static close(server: Server): Promise<void>;
21
+ }
22
+ /** Speaks MCP 2026-07-28 Streamable HTTP to one bound endpoint, the way a real client would. */
23
+ export declare class McpHttpTestHarness {
24
+ readonly baseUrl: string;
25
+ private readonly endpointPath;
26
+ private nextId;
27
+ constructor(baseUrl: string, endpointPath: string);
28
+ request(method: string, params?: Record<string, unknown>): Record<string, unknown>;
29
+ /** `token` null sends no Authorization header; a string `body` is sent verbatim. */
30
+ post(body: Record<string, unknown> | string, token?: string | null, extraHeaders?: Record<string, string>, path?: string): Promise<McpPostReply>;
31
+ /** Opens a `subscriptions/listen` response stream and returns its reader. */
32
+ openListen(): Promise<ReadableStreamDefaultReader<Uint8Array>>;
33
+ readText(reader: ReadableStreamDefaultReader<Uint8Array>): Promise<string>;
34
+ toolNames(): Promise<string[]>;
35
+ callTool(name: string, args: Record<string, unknown>, token?: string): Promise<RpcResponse>;
36
+ resultOf(payload: RpcResponse): Record<string, unknown>;
37
+ structuredOf(payload: RpcResponse): Record<string, unknown>;
38
+ modelErrorOf(payload: RpcResponse): Record<string, unknown>;
39
+ private mirrorToolHeaders;
40
+ }
@@ -0,0 +1,158 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.McpHttpTestHarness = exports.TestServers = exports.McpPostReply = void 0;
4
+ const node_http_1 = require("node:http");
5
+ const server_1 = require("@modelcontextprotocol/server");
6
+ const vitest_1 = require("vitest");
7
+ const WpMcpServerTestFixtures_1 = require("./WpMcpServerTestFixtures");
8
+ class McpPostReply {
9
+ response;
10
+ payload;
11
+ constructor(response, payload) {
12
+ this.response = response;
13
+ this.payload = payload;
14
+ }
15
+ }
16
+ exports.McpPostReply = McpPostReply;
17
+ /** Starts and stops loopback HTTP servers for MCP specs. */
18
+ class TestServers {
19
+ static async listen(app) {
20
+ const server = (0, node_http_1.createServer)(app);
21
+ await new Promise((resolve, reject) => {
22
+ server.once('error', reject);
23
+ server.listen(0, '127.0.0.1', resolve);
24
+ });
25
+ return server;
26
+ }
27
+ static urlOf(server) {
28
+ const address = server.address();
29
+ if (!address || typeof address === 'string')
30
+ throw new Error('test server has no port');
31
+ return `http://127.0.0.1:${address.port}`;
32
+ }
33
+ static async close(server) {
34
+ await new Promise((resolve, reject) => {
35
+ server.close((error) => (error ? reject(error) : resolve()));
36
+ });
37
+ }
38
+ }
39
+ exports.TestServers = TestServers;
40
+ /** Speaks MCP 2026-07-28 Streamable HTTP to one bound endpoint, the way a real client would. */
41
+ class McpHttpTestHarness {
42
+ baseUrl;
43
+ endpointPath;
44
+ nextId = 0;
45
+ constructor(baseUrl, endpointPath) {
46
+ this.baseUrl = baseUrl;
47
+ this.endpointPath = endpointPath;
48
+ }
49
+ request(method, params = {}) {
50
+ return {
51
+ jsonrpc: '2.0',
52
+ id: ++this.nextId,
53
+ method,
54
+ params: {
55
+ ...params,
56
+ _meta: {
57
+ [server_1.PROTOCOL_VERSION_META_KEY]: WpMcpServerTestFixtures_1.MODERN_VERSION,
58
+ [server_1.CLIENT_INFO_META_KEY]: { name: 'webpieces-spec', version: '1.0.0' },
59
+ [server_1.CLIENT_CAPABILITIES_META_KEY]: {},
60
+ },
61
+ },
62
+ };
63
+ }
64
+ /** `token` null sends no Authorization header; a string `body` is sent verbatim. */
65
+ async post(body, token = 'mcp-user', extraHeaders = {}, path = this.endpointPath) {
66
+ const fields = typeof body === 'string' ? {} : body;
67
+ const headers = {
68
+ accept: 'application/json',
69
+ 'content-type': 'application/json',
70
+ 'mcp-protocol-version': WpMcpServerTestFixtures_1.MODERN_VERSION,
71
+ 'mcp-method': typeof fields['method'] === 'string' ? fields['method'] : 'unknown',
72
+ ...extraHeaders,
73
+ };
74
+ if (token !== null)
75
+ headers['authorization'] = `Bearer ${token}`;
76
+ this.mirrorToolHeaders(fields['params'], headers);
77
+ const response = await fetch(`${this.baseUrl}${path}`, {
78
+ method: 'POST',
79
+ headers,
80
+ body: typeof body === 'string' ? body : JSON.stringify(body),
81
+ });
82
+ const text = await response.text();
83
+ const payload = text.startsWith('{') ? JSON.parse(text) : {};
84
+ return new McpPostReply(response, payload);
85
+ }
86
+ /** Opens a `subscriptions/listen` response stream and returns its reader. */
87
+ async openListen() {
88
+ const body = this.request('subscriptions/listen', {
89
+ notifications: { toolsListChanged: true },
90
+ });
91
+ const response = await fetch(`${this.baseUrl}${this.endpointPath}`, {
92
+ method: 'POST',
93
+ headers: {
94
+ authorization: 'Bearer mcp-user',
95
+ accept: 'text/event-stream',
96
+ 'content-type': 'application/json',
97
+ 'mcp-protocol-version': WpMcpServerTestFixtures_1.MODERN_VERSION,
98
+ 'mcp-method': 'subscriptions/listen',
99
+ },
100
+ body: JSON.stringify(body),
101
+ });
102
+ (0, vitest_1.expect)(response.status).toBe(200);
103
+ const reader = response.body?.getReader();
104
+ if (!reader)
105
+ throw new Error('subscription response has no body');
106
+ return reader;
107
+ }
108
+ async readText(reader) {
109
+ const chunk = await reader.read();
110
+ return chunk.done ? '' : new TextDecoder().decode(chunk.value);
111
+ }
112
+ async toolNames() {
113
+ const reply = await this.post(this.request('tools/list'));
114
+ const tools = this.resultOf(reply.payload)['tools'];
115
+ return tools.map((tool) => String(tool['name']));
116
+ }
117
+ async callTool(name, args, token = 'mcp-user') {
118
+ return (await this.post(this.request('tools/call', { name, arguments: args }), token))
119
+ .payload;
120
+ }
121
+ resultOf(payload) {
122
+ (0, vitest_1.expect)(payload.error).toBeUndefined();
123
+ if (!payload.result)
124
+ throw new Error('MCP response has no result');
125
+ return payload.result;
126
+ }
127
+ structuredOf(payload) {
128
+ const value = this.resultOf(payload)['structuredContent'];
129
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
130
+ throw new Error(`missing structuredContent: ${JSON.stringify(payload)}`);
131
+ }
132
+ return value;
133
+ }
134
+ modelErrorOf(payload) {
135
+ const result = this.resultOf(payload);
136
+ const content = result['content'];
137
+ (0, vitest_1.expect)(result['isError']).toBe(true);
138
+ if (!Array.isArray(content) || typeof content[0]?.text !== 'string')
139
+ throw new Error('missing error content');
140
+ return JSON.parse(content[0].text);
141
+ }
142
+ mirrorToolHeaders(params, headers) {
143
+ if (!params || typeof params !== 'object' || Array.isArray(params))
144
+ return;
145
+ const name = params['name'];
146
+ if (typeof name === 'string' && headers['mcp-name'] === undefined)
147
+ headers['mcp-name'] = name;
148
+ const args = params['arguments'];
149
+ if (!args || typeof args !== 'object' || Array.isArray(args))
150
+ return;
151
+ const query = args['query'];
152
+ if (typeof query === 'string' && headers['mcp-param-query'] === undefined) {
153
+ headers['mcp-param-query'] = query;
154
+ }
155
+ }
156
+ }
157
+ exports.McpHttpTestHarness = McpHttpTestHarness;
158
+ //# sourceMappingURL=McpHttpTestHarness.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"McpHttpTestHarness.js","sourceRoot":"","sources":["../../../../../../packages/http/mcp-server/src/__tests__/McpHttpTestHarness.ts"],"names":[],"mappings":"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","sourcesContent":["import { createServer, Server } from 'node:http';\nimport {\n CLIENT_CAPABILITIES_META_KEY,\n CLIENT_INFO_META_KEY,\n PROTOCOL_VERSION_META_KEY,\n} from '@modelcontextprotocol/server';\nimport { Express } from 'express';\nimport { expect } from 'vitest';\nimport { MODERN_VERSION } from './WpMcpServerTestFixtures';\n\nexport interface RpcResponse {\n result?: Record<string, unknown>;\n error?: { code: number; message: string; data?: Record<string, unknown> };\n}\n\nexport class McpPostReply {\n constructor(\n public readonly response: Response,\n public readonly payload: RpcResponse,\n ) {}\n}\n\n/** Starts and stops loopback HTTP servers for MCP specs. */\nexport class TestServers {\n static async listen(app: Express): Promise<Server> {\n const server = createServer(app);\n await new Promise<void>((resolve: () => void, reject: (error: Error) => void) => {\n server.once('error', reject);\n server.listen(0, '127.0.0.1', resolve);\n });\n return server;\n }\n\n static urlOf(server: Server): string {\n const address = server.address();\n if (!address || typeof address === 'string') throw new Error('test server has no port');\n return `http://127.0.0.1:${address.port}`;\n }\n\n static async close(server: Server): Promise<void> {\n await new Promise<void>((resolve: () => void, reject: (error: Error) => void) => {\n server.close((error?: Error) => (error ? reject(error) : resolve()));\n });\n }\n}\n\n/** Speaks MCP 2026-07-28 Streamable HTTP to one bound endpoint, the way a real client would. */\nexport class McpHttpTestHarness {\n private nextId = 0;\n\n constructor(\n public readonly baseUrl: string,\n private readonly endpointPath: string,\n ) {}\n\n request(method: string, params: Record<string, unknown> = {}): Record<string, unknown> {\n return {\n jsonrpc: '2.0',\n id: ++this.nextId,\n method,\n params: {\n ...params,\n _meta: {\n [PROTOCOL_VERSION_META_KEY]: MODERN_VERSION,\n [CLIENT_INFO_META_KEY]: { name: 'webpieces-spec', version: '1.0.0' },\n [CLIENT_CAPABILITIES_META_KEY]: {},\n },\n },\n };\n }\n\n /** `token` null sends no Authorization header; a string `body` is sent verbatim. */\n async post(\n body: Record<string, unknown> | string,\n token: string | null = 'mcp-user',\n extraHeaders: Record<string, string> = {},\n path = this.endpointPath,\n ): Promise<McpPostReply> {\n const fields = typeof body === 'string' ? {} : body;\n const headers: Record<string, string> = {\n accept: 'application/json',\n 'content-type': 'application/json',\n 'mcp-protocol-version': MODERN_VERSION,\n 'mcp-method': typeof fields['method'] === 'string' ? fields['method'] : 'unknown',\n ...extraHeaders,\n };\n if (token !== null) headers['authorization'] = `Bearer ${token}`;\n this.mirrorToolHeaders(fields['params'], headers);\n const response = await fetch(`${this.baseUrl}${path}`, {\n method: 'POST',\n headers,\n body: typeof body === 'string' ? body : JSON.stringify(body),\n });\n const text = await response.text();\n const payload = text.startsWith('{') ? (JSON.parse(text) as RpcResponse) : {};\n return new McpPostReply(response, payload);\n }\n\n /** Opens a `subscriptions/listen` response stream and returns its reader. */\n async openListen(): Promise<ReadableStreamDefaultReader<Uint8Array>> {\n const body = this.request('subscriptions/listen', {\n notifications: { toolsListChanged: true },\n });\n const response = await fetch(`${this.baseUrl}${this.endpointPath}`, {\n method: 'POST',\n headers: {\n authorization: 'Bearer mcp-user',\n accept: 'text/event-stream',\n 'content-type': 'application/json',\n 'mcp-protocol-version': MODERN_VERSION,\n 'mcp-method': 'subscriptions/listen',\n },\n body: JSON.stringify(body),\n });\n expect(response.status).toBe(200);\n const reader = response.body?.getReader();\n if (!reader) throw new Error('subscription response has no body');\n return reader;\n }\n\n async readText(reader: ReadableStreamDefaultReader<Uint8Array>): Promise<string> {\n const chunk = await reader.read();\n return chunk.done ? '' : new TextDecoder().decode(chunk.value);\n }\n\n async toolNames(): Promise<string[]> {\n const reply = await this.post(this.request('tools/list'));\n const tools = this.resultOf(reply.payload)['tools'] as Array<Record<string, unknown>>;\n return tools.map((tool: Record<string, unknown>) => String(tool['name']));\n }\n\n async callTool(\n name: string,\n args: Record<string, unknown>,\n token = 'mcp-user',\n ): Promise<RpcResponse> {\n return (await this.post(this.request('tools/call', { name, arguments: args }), token))\n .payload;\n }\n\n resultOf(payload: RpcResponse): Record<string, unknown> {\n expect(payload.error).toBeUndefined();\n if (!payload.result) throw new Error('MCP response has no result');\n return payload.result;\n }\n\n structuredOf(payload: RpcResponse): Record<string, unknown> {\n const value = this.resultOf(payload)['structuredContent'];\n if (!value || typeof value !== 'object' || Array.isArray(value)) {\n throw new Error(`missing structuredContent: ${JSON.stringify(payload)}`);\n }\n return value as Record<string, unknown>;\n }\n\n modelErrorOf(payload: RpcResponse): Record<string, unknown> {\n const result = this.resultOf(payload);\n const content = result['content'];\n expect(result['isError']).toBe(true);\n if (!Array.isArray(content) || typeof content[0]?.text !== 'string')\n throw new Error('missing error content');\n return JSON.parse(content[0].text) as Record<string, unknown>;\n }\n\n private mirrorToolHeaders(params: unknown, headers: Record<string, string>): void {\n if (!params || typeof params !== 'object' || Array.isArray(params)) return;\n const name = (params as Record<string, unknown>)['name'];\n if (typeof name === 'string' && headers['mcp-name'] === undefined)\n headers['mcp-name'] = name;\n const args = (params as Record<string, unknown>)['arguments'];\n if (!args || typeof args !== 'object' || Array.isArray(args)) return;\n const query = (args as Record<string, unknown>)['query'];\n if (typeof query === 'string' && headers['mcp-param-query'] === undefined) {\n headers['mcp-param-query'] = query;\n }\n }\n}\n"]}
@@ -0,0 +1,93 @@
1
+ import 'reflect-metadata';
2
+ import { ApiError, ContextKey, ErrorTranslators, Filter, HttpResponseDto, LogLevel, Service } from '@webpieces/core-util';
3
+ import { MethodMeta, OidcHook, WpResponse } from '@webpieces/http-routing';
4
+ /** Contracts, controllers and test doubles for McpRemoteBinding.integration.spec.ts. */
5
+ export declare const REMOTE_USER: ContextKey<string, "trusted">;
6
+ export declare const REMOTE_ROLES: ContextKey<string, "trusted">;
7
+ export declare const REMOTE_SERVICE = "mcp-remote-integration";
8
+ export declare const REFUSED_SERVICE = "mcp-remote-refused";
9
+ export declare const GARBAGE_SERVICE = "mcp-remote-garbage";
10
+ export declare const OIDC_TOKEN = "oidc-for-mcp-remote-integration";
11
+ export declare const EXTERNAL_MCP_BEARER = "external-mcp-access-token";
12
+ export declare const LOCAL_ENDPOINT_JWT = "local-endpoint-jwt-that-must-not-cross-the-remote-hop";
13
+ export declare const GATEWAY_PATH = "/gateway/mcp";
14
+ export declare class RemoteRequest {
15
+ query: string;
16
+ constructor(query?: string);
17
+ }
18
+ export declare class RemoteResponse {
19
+ user: string;
20
+ roles: string;
21
+ result: string;
22
+ constructor(user?: string, roles?: string, result?: string);
23
+ }
24
+ export declare abstract class RemoteMcpApi {
25
+ search(_request: RemoteRequest): Promise<RemoteResponse>;
26
+ }
27
+ export declare abstract class MissingRemoteMcpApi {
28
+ search(_request: RemoteRequest): Promise<RemoteResponse>;
29
+ }
30
+ export declare abstract class RefusedRemoteApi {
31
+ search(_request: RemoteRequest): Promise<RemoteResponse>;
32
+ }
33
+ export declare abstract class GarbageRemoteApi {
34
+ search(_request: RemoteRequest): Promise<RemoteResponse>;
35
+ }
36
+ export declare abstract class OidcFailRemoteApi {
37
+ search(_request: RemoteRequest): Promise<RemoteResponse>;
38
+ }
39
+ export declare abstract class LocalThrowApi {
40
+ fail(_request: RemoteRequest): Promise<RemoteResponse>;
41
+ }
42
+ export declare abstract class RemoteThrowApi {
43
+ fail(_request: RemoteRequest): Promise<RemoteResponse>;
44
+ }
45
+ /** One named throw, run identically behind the local and the remote binding. */
46
+ export declare class ThrowCase {
47
+ readonly name: string;
48
+ readonly expectedKind: string;
49
+ readonly expectedLevel: LogLevel;
50
+ readonly raise: () => never;
51
+ constructor(name: string, expectedKind: string, expectedLevel: LogLevel, raise: () => never);
52
+ }
53
+ export declare const THROW_CASES: readonly ThrowCase[];
54
+ export declare function raiseFor(query: string): never;
55
+ export declare class RemoteMcpController extends RemoteMcpApi {
56
+ calls: number;
57
+ authorization?: string;
58
+ search(request: RemoteRequest): Promise<RemoteResponse>;
59
+ }
60
+ export declare class LocalThrowController extends LocalThrowApi {
61
+ fail(request: RemoteRequest): Promise<RemoteResponse>;
62
+ }
63
+ export declare class RemoteThrowController extends RemoteThrowApi {
64
+ fail(request: RemoteRequest): Promise<RemoteResponse>;
65
+ }
66
+ export declare class RecordingOidcMinter {
67
+ private readonly failure?;
68
+ private readonly token;
69
+ readonly audiences: string[];
70
+ constructor(failure?: Error | undefined, token?: string);
71
+ mintIdToken(audience: string): Promise<string>;
72
+ }
73
+ export declare class RecordingOidcVerifier extends OidcHook {
74
+ readonly tokens: string[];
75
+ readonly callerLists: string[][];
76
+ verifyOidc(token: string, callers: string[]): Promise<void>;
77
+ }
78
+ export declare class BoundaryProbeFilter extends Filter<MethodMeta, WpResponse<unknown>> {
79
+ calls: number;
80
+ sawLogIdentity: boolean;
81
+ sawTrustedUser?: string;
82
+ filter(meta: MethodMeta, next: Service<MethodMeta, WpResponse<unknown>>): Promise<WpResponse<unknown>>;
83
+ }
84
+ /**
85
+ * The documented gateway opt-out from http-client-node's "a dependency's 4xx is OUR 500" invariant:
86
+ * an MCP gateway relays a Webpieces peer's typed failure so the model sees the same kind it would see
87
+ * for a local binding. `relay = false` answers "not mine", which is the framework default.
88
+ */
89
+ export declare class RelayWebpiecesPeerErrors implements ErrorTranslators {
90
+ relay: boolean;
91
+ toWire(_error: Error): HttpResponseDto | undefined;
92
+ fromWire(response: HttpResponseDto): ApiError | undefined;
93
+ }