@heddleagent/execution-host-client 6.0.0-next.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (152) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +305 -0
  3. package/dist/authority/index.d.ts +3 -0
  4. package/dist/authority/index.d.ts.map +1 -0
  5. package/dist/authority/index.js +2 -0
  6. package/dist/authority/index.js.map +1 -0
  7. package/dist/authority/jose-execution-authority.d.ts +14 -0
  8. package/dist/authority/jose-execution-authority.d.ts.map +1 -0
  9. package/dist/authority/jose-execution-authority.js +266 -0
  10. package/dist/authority/jose-execution-authority.js.map +1 -0
  11. package/dist/authority/types.d.ts +63 -0
  12. package/dist/authority/types.d.ts.map +1 -0
  13. package/dist/authority/types.js +2 -0
  14. package/dist/authority/types.js.map +1 -0
  15. package/dist/contracts/index.d.ts +163 -0
  16. package/dist/contracts/index.d.ts.map +1 -0
  17. package/dist/contracts/index.js +149 -0
  18. package/dist/contracts/index.js.map +1 -0
  19. package/dist/conversation/durable-hosted-conversation-turn-service.d.ts +23 -0
  20. package/dist/conversation/durable-hosted-conversation-turn-service.d.ts.map +1 -0
  21. package/dist/conversation/durable-hosted-conversation-turn-service.js +273 -0
  22. package/dist/conversation/durable-hosted-conversation-turn-service.js.map +1 -0
  23. package/dist/conversation/hosted-conversation-turn-service.d.ts +17 -0
  24. package/dist/conversation/hosted-conversation-turn-service.d.ts.map +1 -0
  25. package/dist/conversation/hosted-conversation-turn-service.js +60 -0
  26. package/dist/conversation/hosted-conversation-turn-service.js.map +1 -0
  27. package/dist/conversation/index.d.ts +7 -0
  28. package/dist/conversation/index.d.ts.map +1 -0
  29. package/dist/conversation/index.js +5 -0
  30. package/dist/conversation/index.js.map +1 -0
  31. package/dist/conversation/lifecycle-types.d.ts +208 -0
  32. package/dist/conversation/lifecycle-types.d.ts.map +1 -0
  33. package/dist/conversation/lifecycle-types.js +92 -0
  34. package/dist/conversation/lifecycle-types.js.map +1 -0
  35. package/dist/conversation/types.d.ts +35 -0
  36. package/dist/conversation/types.d.ts.map +1 -0
  37. package/dist/conversation/types.js +11 -0
  38. package/dist/conversation/types.js.map +1 -0
  39. package/dist/http-sse/direct-http-execution-host.d.ts +12 -0
  40. package/dist/http-sse/direct-http-execution-host.d.ts.map +1 -0
  41. package/dist/http-sse/direct-http-execution-host.js +279 -0
  42. package/dist/http-sse/direct-http-execution-host.js.map +1 -0
  43. package/dist/http-sse/errors.d.ts +19 -0
  44. package/dist/http-sse/errors.d.ts.map +1 -0
  45. package/dist/http-sse/errors.js +29 -0
  46. package/dist/http-sse/errors.js.map +1 -0
  47. package/dist/http-sse/index.d.ts +4 -0
  48. package/dist/http-sse/index.d.ts.map +1 -0
  49. package/dist/http-sse/index.js +3 -0
  50. package/dist/http-sse/index.js.map +1 -0
  51. package/dist/http-sse/types.d.ts +20 -0
  52. package/dist/http-sse/types.d.ts.map +1 -0
  53. package/dist/http-sse/types.js +2 -0
  54. package/dist/http-sse/types.js.map +1 -0
  55. package/dist/index.d.ts +6 -0
  56. package/dist/index.d.ts.map +1 -0
  57. package/dist/index.js +6 -0
  58. package/dist/index.js.map +1 -0
  59. package/dist/mcp/errors.d.ts +11 -0
  60. package/dist/mcp/errors.d.ts.map +1 -0
  61. package/dist/mcp/errors.js +15 -0
  62. package/dist/mcp/errors.js.map +1 -0
  63. package/dist/mcp/index.d.ts +5 -0
  64. package/dist/mcp/index.d.ts.map +1 -0
  65. package/dist/mcp/index.js +3 -0
  66. package/dist/mcp/index.js.map +1 -0
  67. package/dist/mcp/jwt-capability-verifier.d.ts +12 -0
  68. package/dist/mcp/jwt-capability-verifier.d.ts.map +1 -0
  69. package/dist/mcp/jwt-capability-verifier.js +136 -0
  70. package/dist/mcp/jwt-capability-verifier.js.map +1 -0
  71. package/dist/mcp/node/index.d.ts +4 -0
  72. package/dist/mcp/node/index.d.ts.map +1 -0
  73. package/dist/mcp/node/index.js +3 -0
  74. package/dist/mcp/node/index.js.map +1 -0
  75. package/dist/mcp/node/json-toolset.d.ts +23 -0
  76. package/dist/mcp/node/json-toolset.d.ts.map +1 -0
  77. package/dist/mcp/node/json-toolset.js +94 -0
  78. package/dist/mcp/node/json-toolset.js.map +1 -0
  79. package/dist/mcp/node/streamable-http-mcp-service.d.ts +18 -0
  80. package/dist/mcp/node/streamable-http-mcp-service.d.ts.map +1 -0
  81. package/dist/mcp/node/streamable-http-mcp-service.js +229 -0
  82. package/dist/mcp/node/streamable-http-mcp-service.js.map +1 -0
  83. package/dist/mcp/node/types.d.ts +53 -0
  84. package/dist/mcp/node/types.d.ts.map +1 -0
  85. package/dist/mcp/node/types.js +2 -0
  86. package/dist/mcp/node/types.js.map +1 -0
  87. package/dist/mcp/types.d.ts +37 -0
  88. package/dist/mcp/types.d.ts.map +1 -0
  89. package/dist/mcp/types.js +2 -0
  90. package/dist/mcp/types.js.map +1 -0
  91. package/dist/node/authority-key.d.ts +14 -0
  92. package/dist/node/authority-key.d.ts.map +1 -0
  93. package/dist/node/authority-key.js +89 -0
  94. package/dist/node/authority-key.js.map +1 -0
  95. package/dist/node/direct-credentials.d.ts +21 -0
  96. package/dist/node/direct-credentials.d.ts.map +1 -0
  97. package/dist/node/direct-credentials.js +47 -0
  98. package/dist/node/direct-credentials.js.map +1 -0
  99. package/dist/node/http-service.d.ts +21 -0
  100. package/dist/node/http-service.d.ts.map +1 -0
  101. package/dist/node/http-service.js +389 -0
  102. package/dist/node/http-service.js.map +1 -0
  103. package/dist/node/index.d.ts +6 -0
  104. package/dist/node/index.d.ts.map +1 -0
  105. package/dist/node/index.js +4 -0
  106. package/dist/node/index.js.map +1 -0
  107. package/dist/node/types.d.ts +52 -0
  108. package/dist/node/types.d.ts.map +1 -0
  109. package/dist/node/types.js +2 -0
  110. package/dist/node/types.js.map +1 -0
  111. package/dist/testing/event-stream.d.ts +17 -0
  112. package/dist/testing/event-stream.d.ts.map +1 -0
  113. package/dist/testing/event-stream.js +83 -0
  114. package/dist/testing/event-stream.js.map +1 -0
  115. package/dist/testing/hosted-conversation-turn-store-conformance.d.ts +17 -0
  116. package/dist/testing/hosted-conversation-turn-store-conformance.d.ts.map +1 -0
  117. package/dist/testing/hosted-conversation-turn-store-conformance.js +137 -0
  118. package/dist/testing/hosted-conversation-turn-store-conformance.js.map +1 -0
  119. package/dist/testing/index.d.ts +5 -0
  120. package/dist/testing/index.d.ts.map +1 -0
  121. package/dist/testing/index.js +3 -0
  122. package/dist/testing/index.js.map +1 -0
  123. package/dist/testing/invocation.d.ts +20 -0
  124. package/dist/testing/invocation.d.ts.map +1 -0
  125. package/dist/testing/invocation.js +39 -0
  126. package/dist/testing/invocation.js.map +1 -0
  127. package/dist/testing/local-execution-host-contract-fixture.d.ts +21 -0
  128. package/dist/testing/local-execution-host-contract-fixture.d.ts.map +1 -0
  129. package/dist/testing/local-execution-host-contract-fixture.js +197 -0
  130. package/dist/testing/local-execution-host-contract-fixture.js.map +1 -0
  131. package/dist/testing/request.d.ts +10 -0
  132. package/dist/testing/request.d.ts.map +1 -0
  133. package/dist/testing/request.js +140 -0
  134. package/dist/testing/request.js.map +1 -0
  135. package/dist/testing/types.d.ts +42 -0
  136. package/dist/testing/types.d.ts.map +1 -0
  137. package/dist/testing/types.js +2 -0
  138. package/dist/testing/types.js.map +1 -0
  139. package/package.json +92 -0
  140. package/spec/v1/README.md +101 -0
  141. package/spec/v1/durable-hosted-conversation-lifecycle.md +139 -0
  142. package/spec/v1/fixtures/ambiguous-eof.sse +9 -0
  143. package/spec/v1/fixtures/authority.json +70 -0
  144. package/spec/v1/fixtures/cancelled.sse +9 -0
  145. package/spec/v1/fixtures/durable-conversation-lifecycle.json +890 -0
  146. package/spec/v1/fixtures/invalid-request-extra-field.json +8 -0
  147. package/spec/v1/fixtures/invalid-sequence-gap.sse +9 -0
  148. package/spec/v1/fixtures/manifest.json +53 -0
  149. package/spec/v1/fixtures/valid-request.json +7 -0
  150. package/spec/v1/fixtures/valid-result.sse +13 -0
  151. package/spec/v1/openapi.json +896 -0
  152. package/spec/v1/schema-bundle.json +1286 -0
@@ -0,0 +1,229 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import { StreamableHTTPServerTransport, } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
3
+ import { z } from 'zod';
4
+ import { McpCapabilityUnavailableError } from '../errors.js';
5
+ const DEFAULT_MAX_BODY_BYTES = 64 * 1_024;
6
+ const MAX_CAPABILITY_CHARACTERS = 4_096;
7
+ const REDACTED_HEADER_VALUE = '[REDACTED]';
8
+ class McpRequestBodyError extends Error {
9
+ statusCode;
10
+ constructor(statusCode) {
11
+ super('Invalid MCP request body.');
12
+ this.statusCode = statusCode;
13
+ }
14
+ }
15
+ /**
16
+ * Stateless official-SDK Streamable HTTP edge for adopter-owned MCP tools.
17
+ *
18
+ * Every request verifies the capability independently and owns fresh SDK
19
+ * resources. The injected toolset remains the sole owner of model-visible
20
+ * names, schemas, descriptions, and product operations.
21
+ */
22
+ export class NodeStreamableHttpMcpService {
23
+ #capabilityVerifier;
24
+ #toolset;
25
+ #maxBodyBytes;
26
+ #activeRequests = new Set();
27
+ #closed = false;
28
+ #closePromise;
29
+ constructor(config) {
30
+ this.#capabilityVerifier = config.capabilityVerifier;
31
+ this.#toolset = config.toolset;
32
+ this.#maxBodyBytes = z.number().int().min(1).max(1_048_576).parse(config.maxBodyBytes ?? DEFAULT_MAX_BODY_BYTES);
33
+ }
34
+ /** Handles one authenticated stateless MCP request at an adopter-owned path. */
35
+ async handle(request, response) {
36
+ response.setHeader('Cache-Control', 'no-store');
37
+ response.setHeader('Vary', 'Authorization');
38
+ response.setHeader('X-Content-Type-Options', 'nosniff');
39
+ if (this.#closed) {
40
+ request.resume();
41
+ writeJsonRpcError(response, 503, 'MCP service is unavailable.');
42
+ return;
43
+ }
44
+ if (request.method !== 'POST') {
45
+ request.resume();
46
+ writeJsonRpcError(response, 405, 'Method not allowed.');
47
+ return;
48
+ }
49
+ const assertion = takeBearer(request);
50
+ if (!assertion) {
51
+ request.resume();
52
+ writeJsonRpcError(response, 401, 'Authentication is required.', {
53
+ 'WWW-Authenticate': 'Bearer',
54
+ });
55
+ return;
56
+ }
57
+ const active = {
58
+ abortController: new AbortController(),
59
+ request,
60
+ response,
61
+ };
62
+ const cleanup = () => void this.#closeRequest(active);
63
+ request.once('aborted', cleanup);
64
+ response.once('close', cleanup);
65
+ this.#activeRequests.add(active);
66
+ try {
67
+ const capability = await this.#verifyCapability(assertion, response);
68
+ if (!capability) {
69
+ request.resume();
70
+ return;
71
+ }
72
+ let body;
73
+ try {
74
+ body = await readJsonBody(request, this.#maxBodyBytes, active.abortController.signal);
75
+ }
76
+ catch (error) {
77
+ const statusCode = error instanceof McpRequestBodyError
78
+ ? error.statusCode
79
+ : 400;
80
+ writeJsonRpcError(response, statusCode, 'Invalid MCP request.');
81
+ return;
82
+ }
83
+ active.server = new McpServer(this.#toolset.serverInfo);
84
+ active.transport = new StreamableHTTPServerTransport({
85
+ sessionIdGenerator: undefined,
86
+ enableJsonResponse: true,
87
+ });
88
+ this.#toolset.registerAllowedTools({
89
+ server: active.server,
90
+ capability,
91
+ requestSignal: active.abortController.signal,
92
+ });
93
+ await active.server.connect(active.transport);
94
+ await active.transport.handleRequest(request, response, body);
95
+ }
96
+ catch {
97
+ if (!response.headersSent && !response.destroyed) {
98
+ writeJsonRpcError(response, 500, 'MCP request failed.');
99
+ }
100
+ }
101
+ finally {
102
+ request.removeListener('aborted', cleanup);
103
+ response.removeListener('close', cleanup);
104
+ await this.#closeRequest(active);
105
+ }
106
+ }
107
+ /** Aborts active tools and closes their official SDK resources. */
108
+ close() {
109
+ if (!this.#closePromise) {
110
+ this.#closed = true;
111
+ this.#closePromise = Promise.all([...this.#activeRequests].map((request) => this.#closeRequest(request, true))).then(() => undefined);
112
+ }
113
+ return this.#closePromise;
114
+ }
115
+ async #verifyCapability(assertion, response) {
116
+ try {
117
+ return await this.#capabilityVerifier.verify(assertion);
118
+ }
119
+ catch (error) {
120
+ if (error instanceof McpCapabilityUnavailableError) {
121
+ writeJsonRpcError(response, 503, 'Authentication is temporarily unavailable.', { 'Retry-After': '1' });
122
+ return undefined;
123
+ }
124
+ writeJsonRpcError(response, 401, 'Authentication failed.', {
125
+ 'WWW-Authenticate': 'Bearer error="invalid_token"',
126
+ });
127
+ return undefined;
128
+ }
129
+ }
130
+ #closeRequest(resources, destroyIo = false) {
131
+ resources.abortController.abort(new Error('The owning MCP HTTP request closed.'));
132
+ if (destroyIo) {
133
+ resources.request.destroy();
134
+ resources.response.destroy();
135
+ }
136
+ resources.closing ??= Promise.all([
137
+ resources.transport?.close().catch(() => undefined),
138
+ resources.server?.close().catch(() => undefined),
139
+ ]).then(() => {
140
+ this.#activeRequests.delete(resources);
141
+ });
142
+ return resources.closing;
143
+ }
144
+ }
145
+ function takeBearer(request) {
146
+ const occurrences = request.rawHeaders.reduce((count, headerName, index) => count
147
+ + (index % 2 === 0 && headerName.toLowerCase() === 'authorization' ? 1 : 0), 0);
148
+ const value = request.headers.authorization;
149
+ request.headers.authorization = value === undefined
150
+ ? undefined
151
+ : REDACTED_HEADER_VALUE;
152
+ for (let index = 0; index < request.rawHeaders.length; index += 2) {
153
+ if (request.rawHeaders[index]?.toLowerCase() === 'authorization') {
154
+ request.rawHeaders[index + 1] = REDACTED_HEADER_VALUE;
155
+ }
156
+ }
157
+ if (occurrences !== 1 || Array.isArray(value)) {
158
+ return undefined;
159
+ }
160
+ const match = /^Bearer ([^\s]+)$/i.exec(value?.trim() ?? '');
161
+ const assertion = match?.[1];
162
+ return assertion
163
+ && assertion.length <= MAX_CAPABILITY_CHARACTERS
164
+ && /^[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$/.test(assertion)
165
+ ? assertion
166
+ : undefined;
167
+ }
168
+ async function readJsonBody(request, maxBodyBytes, signal) {
169
+ const contentType = request.headers['content-type']
170
+ ?.split(';', 1)[0]
171
+ ?.trim()
172
+ .toLowerCase();
173
+ if (contentType !== 'application/json') {
174
+ request.resume();
175
+ throw new McpRequestBodyError(415);
176
+ }
177
+ const contentLength = request.headers['content-length'];
178
+ if (contentLength !== undefined) {
179
+ const declaredLength = Number(contentLength);
180
+ if (!Number.isSafeInteger(declaredLength) || declaredLength < 0) {
181
+ request.resume();
182
+ throw new McpRequestBodyError(400);
183
+ }
184
+ if (declaredLength > maxBodyBytes) {
185
+ request.resume();
186
+ throw new McpRequestBodyError(413);
187
+ }
188
+ }
189
+ const chunks = [];
190
+ let receivedBytes = 0;
191
+ for await (const chunk of request) {
192
+ signal.throwIfAborted();
193
+ const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
194
+ receivedBytes += buffer.byteLength;
195
+ if (receivedBytes > maxBodyBytes) {
196
+ request.resume();
197
+ throw new McpRequestBodyError(413);
198
+ }
199
+ chunks.push(buffer);
200
+ }
201
+ if (!request.complete || receivedBytes === 0) {
202
+ throw new McpRequestBodyError(400);
203
+ }
204
+ try {
205
+ return JSON.parse(Buffer.concat(chunks, receivedBytes).toString('utf8'));
206
+ }
207
+ catch {
208
+ throw new McpRequestBodyError(400);
209
+ }
210
+ }
211
+ function writeJsonRpcError(response, statusCode, message, headers = {}) {
212
+ if (response.headersSent || response.destroyed) {
213
+ return;
214
+ }
215
+ const body = JSON.stringify({
216
+ jsonrpc: '2.0',
217
+ error: { code: -32_000, message },
218
+ id: null,
219
+ });
220
+ response.writeHead(statusCode, {
221
+ 'Cache-Control': 'no-store',
222
+ 'Content-Length': Buffer.byteLength(body),
223
+ 'Content-Type': 'application/json; charset=utf-8',
224
+ 'X-Content-Type-Options': 'nosniff',
225
+ ...headers,
226
+ });
227
+ response.end(body);
228
+ }
229
+ //# sourceMappingURL=streamable-http-mcp-service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"streamable-http-mcp-service.js","sourceRoot":"","sources":["../../../src/mcp/node/streamable-http-mcp-service.ts"],"names":[],"mappings":"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","sourcesContent":["import type { IncomingMessage, ServerResponse } from 'node:http';\nimport { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport {\n StreamableHTTPServerTransport,\n} from '@modelcontextprotocol/sdk/server/streamableHttp.js';\nimport { z } from 'zod';\nimport { McpCapabilityUnavailableError } from '../errors.js';\nimport type { VerifiedMcpCapability } from '../types.js';\nimport type {\n NodeStreamableHttpMcpServiceConfig,\n} from './types.js';\n\nconst DEFAULT_MAX_BODY_BYTES = 64 * 1_024;\nconst MAX_CAPABILITY_CHARACTERS = 4_096;\nconst REDACTED_HEADER_VALUE = '[REDACTED]';\n\ntype ActiveMcpRequest = {\n abortController: AbortController;\n request: IncomingMessage;\n response: ServerResponse;\n server?: McpServer;\n transport?: StreamableHTTPServerTransport;\n closing?: Promise<void>;\n};\n\nclass McpRequestBodyError extends Error {\n constructor(readonly statusCode: 400 | 413 | 415) {\n super('Invalid MCP request body.');\n }\n}\n\n/**\n * Stateless official-SDK Streamable HTTP edge for adopter-owned MCP tools.\n *\n * Every request verifies the capability independently and owns fresh SDK\n * resources. The injected toolset remains the sole owner of model-visible\n * names, schemas, descriptions, and product operations.\n */\nexport class NodeStreamableHttpMcpService<TTool extends string> {\n readonly #capabilityVerifier: NodeStreamableHttpMcpServiceConfig<TTool>[\n 'capabilityVerifier'\n ];\n readonly #toolset: NodeStreamableHttpMcpServiceConfig<TTool>['toolset'];\n readonly #maxBodyBytes: number;\n readonly #activeRequests = new Set<ActiveMcpRequest>();\n #closed = false;\n #closePromise: Promise<void> | undefined;\n\n constructor(config: NodeStreamableHttpMcpServiceConfig<TTool>) {\n this.#capabilityVerifier = config.capabilityVerifier;\n this.#toolset = config.toolset;\n this.#maxBodyBytes = z.number().int().min(1).max(1_048_576).parse(\n config.maxBodyBytes ?? DEFAULT_MAX_BODY_BYTES,\n );\n }\n\n /** Handles one authenticated stateless MCP request at an adopter-owned path. */\n async handle(\n request: IncomingMessage,\n response: ServerResponse,\n ): Promise<void> {\n response.setHeader('Cache-Control', 'no-store');\n response.setHeader('Vary', 'Authorization');\n response.setHeader('X-Content-Type-Options', 'nosniff');\n if (this.#closed) {\n request.resume();\n writeJsonRpcError(response, 503, 'MCP service is unavailable.');\n return;\n }\n if (request.method !== 'POST') {\n request.resume();\n writeJsonRpcError(response, 405, 'Method not allowed.');\n return;\n }\n\n const assertion = takeBearer(request);\n if (!assertion) {\n request.resume();\n writeJsonRpcError(response, 401, 'Authentication is required.', {\n 'WWW-Authenticate': 'Bearer',\n });\n return;\n }\n\n const active: ActiveMcpRequest = {\n abortController: new AbortController(),\n request,\n response,\n };\n const cleanup = () => void this.#closeRequest(active);\n request.once('aborted', cleanup);\n response.once('close', cleanup);\n this.#activeRequests.add(active);\n\n try {\n const capability = await this.#verifyCapability(assertion, response);\n if (!capability) {\n request.resume();\n return;\n }\n\n let body: unknown;\n try {\n body = await readJsonBody(\n request,\n this.#maxBodyBytes,\n active.abortController.signal,\n );\n } catch (error) {\n const statusCode = error instanceof McpRequestBodyError\n ? error.statusCode\n : 400;\n writeJsonRpcError(response, statusCode, 'Invalid MCP request.');\n return;\n }\n\n active.server = new McpServer(this.#toolset.serverInfo);\n active.transport = new StreamableHTTPServerTransport({\n sessionIdGenerator: undefined,\n enableJsonResponse: true,\n });\n this.#toolset.registerAllowedTools({\n server: active.server,\n capability,\n requestSignal: active.abortController.signal,\n });\n await active.server.connect(active.transport);\n await active.transport.handleRequest(request, response, body);\n } catch {\n if (!response.headersSent && !response.destroyed) {\n writeJsonRpcError(response, 500, 'MCP request failed.');\n }\n } finally {\n request.removeListener('aborted', cleanup);\n response.removeListener('close', cleanup);\n await this.#closeRequest(active);\n }\n }\n\n /** Aborts active tools and closes their official SDK resources. */\n close(): Promise<void> {\n if (!this.#closePromise) {\n this.#closed = true;\n this.#closePromise = Promise.all(\n [...this.#activeRequests].map((request) => this.#closeRequest(\n request,\n true,\n )),\n ).then(() => undefined);\n }\n return this.#closePromise;\n }\n\n async #verifyCapability(\n assertion: string,\n response: ServerResponse,\n ): Promise<VerifiedMcpCapability<TTool> | undefined> {\n try {\n return await this.#capabilityVerifier.verify(assertion);\n } catch (error) {\n if (error instanceof McpCapabilityUnavailableError) {\n writeJsonRpcError(\n response,\n 503,\n 'Authentication is temporarily unavailable.',\n { 'Retry-After': '1' },\n );\n return undefined;\n }\n writeJsonRpcError(response, 401, 'Authentication failed.', {\n 'WWW-Authenticate': 'Bearer error=\"invalid_token\"',\n });\n return undefined;\n }\n }\n\n #closeRequest(\n resources: ActiveMcpRequest,\n destroyIo = false,\n ): Promise<void> {\n resources.abortController.abort(\n new Error('The owning MCP HTTP request closed.'),\n );\n if (destroyIo) {\n resources.request.destroy();\n resources.response.destroy();\n }\n resources.closing ??= Promise.all([\n resources.transport?.close().catch(() => undefined),\n resources.server?.close().catch(() => undefined),\n ]).then(() => {\n this.#activeRequests.delete(resources);\n });\n return resources.closing;\n }\n}\n\nfunction takeBearer(request: IncomingMessage): string | undefined {\n const occurrences = request.rawHeaders.reduce(\n (count, headerName, index) => count\n + (index % 2 === 0 && headerName.toLowerCase() === 'authorization' ? 1 : 0),\n 0,\n );\n const value = request.headers.authorization;\n request.headers.authorization = value === undefined\n ? undefined\n : REDACTED_HEADER_VALUE;\n for (let index = 0; index < request.rawHeaders.length; index += 2) {\n if (request.rawHeaders[index]?.toLowerCase() === 'authorization') {\n request.rawHeaders[index + 1] = REDACTED_HEADER_VALUE;\n }\n }\n if (occurrences !== 1 || Array.isArray(value)) {\n return undefined;\n }\n const match = /^Bearer ([^\\s]+)$/i.exec(value?.trim() ?? '');\n const assertion = match?.[1];\n return assertion\n && assertion.length <= MAX_CAPABILITY_CHARACTERS\n && /^[A-Za-z0-9_-]+\\.[A-Za-z0-9_-]+\\.[A-Za-z0-9_-]+$/.test(assertion)\n ? assertion\n : undefined;\n}\n\nasync function readJsonBody(\n request: IncomingMessage,\n maxBodyBytes: number,\n signal: AbortSignal,\n): Promise<unknown> {\n const contentType = request.headers['content-type']\n ?.split(';', 1)[0]\n ?.trim()\n .toLowerCase();\n if (contentType !== 'application/json') {\n request.resume();\n throw new McpRequestBodyError(415);\n }\n const contentLength = request.headers['content-length'];\n if (contentLength !== undefined) {\n const declaredLength = Number(contentLength);\n if (!Number.isSafeInteger(declaredLength) || declaredLength < 0) {\n request.resume();\n throw new McpRequestBodyError(400);\n }\n if (declaredLength > maxBodyBytes) {\n request.resume();\n throw new McpRequestBodyError(413);\n }\n }\n\n const chunks: Buffer[] = [];\n let receivedBytes = 0;\n for await (const chunk of request) {\n signal.throwIfAborted();\n const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);\n receivedBytes += buffer.byteLength;\n if (receivedBytes > maxBodyBytes) {\n request.resume();\n throw new McpRequestBodyError(413);\n }\n chunks.push(buffer);\n }\n if (!request.complete || receivedBytes === 0) {\n throw new McpRequestBodyError(400);\n }\n try {\n return JSON.parse(\n Buffer.concat(chunks, receivedBytes).toString('utf8'),\n ) as unknown;\n } catch {\n throw new McpRequestBodyError(400);\n }\n}\n\nfunction writeJsonRpcError(\n response: ServerResponse,\n statusCode: number,\n message: string,\n headers: Record<string, string> = {},\n): void {\n if (response.headersSent || response.destroyed) {\n return;\n }\n const body = JSON.stringify({\n jsonrpc: '2.0',\n error: { code: -32_000, message },\n id: null,\n });\n response.writeHead(statusCode, {\n 'Cache-Control': 'no-store',\n 'Content-Length': Buffer.byteLength(body),\n 'Content-Type': 'application/json; charset=utf-8',\n 'X-Content-Type-Options': 'nosniff',\n ...headers,\n });\n response.end(body);\n}\n"]}
@@ -0,0 +1,53 @@
1
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import type { ToolAnnotations } from '@modelcontextprotocol/sdk/types.js';
3
+ import type { z } from 'zod';
4
+ import type { McpCapabilityVerifier, VerifiedMcpCapability } from '../types.js';
5
+ export type NodeMcpToolRegistrationContext<TTool extends string> = {
6
+ server: McpServer;
7
+ capability: VerifiedMcpCapability<TTool>;
8
+ requestSignal: AbortSignal;
9
+ };
10
+ /** Product-owned model-visible tools plugged into the generic MCP edge. */
11
+ export interface NodeMcpToolset<TTool extends string> {
12
+ readonly serverInfo: Readonly<{
13
+ name: string;
14
+ version: string;
15
+ }>;
16
+ registerAllowedTools(context: NodeMcpToolRegistrationContext<TTool>): void;
17
+ }
18
+ export type NodeMcpJsonToolExecutionContext<TTool extends string> = {
19
+ capability: VerifiedMcpCapability<TTool>;
20
+ signal: AbortSignal;
21
+ };
22
+ /**
23
+ * One product-owned JSON tool exposed through the standard Node MCP edge.
24
+ *
25
+ * The generic toolset owns capability admission, cancellation composition,
26
+ * lifetime checks, safe failures, and JSON result projection. The definition
27
+ * owns only product vocabulary, validation, annotations, and behavior.
28
+ */
29
+ export type NodeMcpJsonToolDefinition<TTool extends string, TInput extends Record<string, unknown>> = {
30
+ name: TTool;
31
+ title?: string;
32
+ description: string;
33
+ inputSchema: z.ZodType<TInput>;
34
+ annotations?: ToolAnnotations;
35
+ /** Stable model-visible text; thrown product errors are never reflected. */
36
+ failureMessage: string;
37
+ execute(input: TInput, context: NodeMcpJsonToolExecutionContext<TTool>): unknown | Promise<unknown>;
38
+ };
39
+ export type AnyNodeMcpJsonToolDefinition<TTool extends string> = NodeMcpJsonToolDefinition<TTool, Record<string, unknown>>;
40
+ export type NodeMcpJsonToolsetConfig<TTool extends string> = {
41
+ serverInfo: Readonly<{
42
+ name: string;
43
+ version: string;
44
+ }>;
45
+ tools: readonly AnyNodeMcpJsonToolDefinition<TTool>[];
46
+ now?: () => Date;
47
+ };
48
+ export type NodeStreamableHttpMcpServiceConfig<TTool extends string> = {
49
+ capabilityVerifier: McpCapabilityVerifier<TTool>;
50
+ toolset: NodeMcpToolset<TTool>;
51
+ maxBodyBytes?: number;
52
+ };
53
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/mcp/node/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACzE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,oCAAoC,CAAC;AAC1E,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAC7B,OAAO,KAAK,EACV,qBAAqB,EACrB,qBAAqB,EACtB,MAAM,aAAa,CAAC;AAErB,MAAM,MAAM,8BAA8B,CAAC,KAAK,SAAS,MAAM,IAAI;IACjE,MAAM,EAAE,SAAS,CAAC;IAClB,UAAU,EAAE,qBAAqB,CAAC,KAAK,CAAC,CAAC;IACzC,aAAa,EAAE,WAAW,CAAC;CAC5B,CAAC;AAEF,2EAA2E;AAC3E,MAAM,WAAW,cAAc,CAAC,KAAK,SAAS,MAAM;IAClD,QAAQ,CAAC,UAAU,EAAE,QAAQ,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACjE,oBAAoB,CAClB,OAAO,EAAE,8BAA8B,CAAC,KAAK,CAAC,GAC7C,IAAI,CAAC;CACT;AAED,MAAM,MAAM,+BAA+B,CAAC,KAAK,SAAS,MAAM,IAAI;IAClE,UAAU,EAAE,qBAAqB,CAAC,KAAK,CAAC,CAAC;IACzC,MAAM,EAAE,WAAW,CAAC;CACrB,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,MAAM,yBAAyB,CACnC,KAAK,SAAS,MAAM,EACpB,MAAM,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,IACpC;IACF,IAAI,EAAE,KAAK,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;IAC/B,WAAW,CAAC,EAAE,eAAe,CAAC;IAC9B,4EAA4E;IAC5E,cAAc,EAAE,MAAM,CAAC;IACvB,OAAO,CACL,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,+BAA+B,CAAC,KAAK,CAAC,GAC9C,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CAC/B,CAAC;AAEF,MAAM,MAAM,4BAA4B,CAAC,KAAK,SAAS,MAAM,IAC3D,yBAAyB,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;AAE5D,MAAM,MAAM,wBAAwB,CAAC,KAAK,SAAS,MAAM,IAAI;IAC3D,UAAU,EAAE,QAAQ,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACxD,KAAK,EAAE,SAAS,4BAA4B,CAAC,KAAK,CAAC,EAAE,CAAC;IACtD,GAAG,CAAC,EAAE,MAAM,IAAI,CAAC;CAClB,CAAC;AAEF,MAAM,MAAM,kCAAkC,CAAC,KAAK,SAAS,MAAM,IAAI;IACrE,kBAAkB,EAAE,qBAAqB,CAAC,KAAK,CAAC,CAAC;IACjD,OAAO,EAAE,cAAc,CAAC,KAAK,CAAC,CAAC;IAC/B,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/mcp/node/types.ts"],"names":[],"mappings":"","sourcesContent":["import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport type { ToolAnnotations } from '@modelcontextprotocol/sdk/types.js';\nimport type { z } from 'zod';\nimport type {\n McpCapabilityVerifier,\n VerifiedMcpCapability,\n} from '../types.js';\n\nexport type NodeMcpToolRegistrationContext<TTool extends string> = {\n server: McpServer;\n capability: VerifiedMcpCapability<TTool>;\n requestSignal: AbortSignal;\n};\n\n/** Product-owned model-visible tools plugged into the generic MCP edge. */\nexport interface NodeMcpToolset<TTool extends string> {\n readonly serverInfo: Readonly<{ name: string; version: string }>;\n registerAllowedTools(\n context: NodeMcpToolRegistrationContext<TTool>,\n ): void;\n}\n\nexport type NodeMcpJsonToolExecutionContext<TTool extends string> = {\n capability: VerifiedMcpCapability<TTool>;\n signal: AbortSignal;\n};\n\n/**\n * One product-owned JSON tool exposed through the standard Node MCP edge.\n *\n * The generic toolset owns capability admission, cancellation composition,\n * lifetime checks, safe failures, and JSON result projection. The definition\n * owns only product vocabulary, validation, annotations, and behavior.\n */\nexport type NodeMcpJsonToolDefinition<\n TTool extends string,\n TInput extends Record<string, unknown>,\n> = {\n name: TTool;\n title?: string;\n description: string;\n inputSchema: z.ZodType<TInput>;\n annotations?: ToolAnnotations;\n /** Stable model-visible text; thrown product errors are never reflected. */\n failureMessage: string;\n execute(\n input: TInput,\n context: NodeMcpJsonToolExecutionContext<TTool>,\n ): unknown | Promise<unknown>;\n};\n\nexport type AnyNodeMcpJsonToolDefinition<TTool extends string> =\n NodeMcpJsonToolDefinition<TTool, Record<string, unknown>>;\n\nexport type NodeMcpJsonToolsetConfig<TTool extends string> = {\n serverInfo: Readonly<{ name: string; version: string }>;\n tools: readonly AnyNodeMcpJsonToolDefinition<TTool>[];\n now?: () => Date;\n};\n\nexport type NodeStreamableHttpMcpServiceConfig<TTool extends string> = {\n capabilityVerifier: McpCapabilityVerifier<TTool>;\n toolset: NodeMcpToolset<TTool>;\n maxBodyBytes?: number;\n};\n"]}
@@ -0,0 +1,37 @@
1
+ import type { JWTVerifyGetKey } from 'jose';
2
+ import type { HostedExecutionWorkflow } from '../contracts/index.js';
3
+ export type McpInvocationScope = {
4
+ adopterId: string;
5
+ tenantId: string;
6
+ subjectId: string;
7
+ productSessionId: string;
8
+ runtimeSessionId: string;
9
+ invocationId: string;
10
+ workflow: HostedExecutionWorkflow;
11
+ };
12
+ export type VerifiedMcpCapability<TToolName extends string = string> = {
13
+ capabilityId: string;
14
+ serverId: string;
15
+ allowedTools: readonly TToolName[];
16
+ scope: McpInvocationScope;
17
+ issuedAt: string;
18
+ expiresAt: string;
19
+ };
20
+ export interface McpCapabilityVerifier<TToolName extends string = string> {
21
+ verify(assertion: string): Promise<VerifiedMcpCapability<TToolName>>;
22
+ }
23
+ export type JwtMcpCapabilityVerifierConfig<TToolName extends string> = {
24
+ issuer: string;
25
+ audience: string;
26
+ jwksUrl: URL;
27
+ trustedAdopterId: string;
28
+ serverId: string;
29
+ supportedTools: readonly TToolName[];
30
+ maxCapabilityAgeSeconds: number;
31
+ clockToleranceSeconds?: number;
32
+ };
33
+ export type JwtMcpCapabilityVerifierOptions = {
34
+ keyResolver?: JWTVerifyGetKey;
35
+ now?: () => Date;
36
+ };
37
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/mcp/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,MAAM,CAAC;AAC5C,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,uBAAuB,CAAC;AAErE,MAAM,MAAM,kBAAkB,GAAG;IAC/B,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,gBAAgB,EAAE,MAAM,CAAC;IACzB,gBAAgB,EAAE,MAAM,CAAC;IACzB,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,uBAAuB,CAAC;CACnC,CAAC;AAEF,MAAM,MAAM,qBAAqB,CAAC,SAAS,SAAS,MAAM,GAAG,MAAM,IAAI;IACrE,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,SAAS,SAAS,EAAE,CAAC;IACnC,KAAK,EAAE,kBAAkB,CAAC;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,MAAM,WAAW,qBAAqB,CAAC,SAAS,SAAS,MAAM,GAAG,MAAM;IACtE,MAAM,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,qBAAqB,CAAC,SAAS,CAAC,CAAC,CAAC;CACtE;AAED,MAAM,MAAM,8BAA8B,CAAC,SAAS,SAAS,MAAM,IAAI;IACrE,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,GAAG,CAAC;IACb,gBAAgB,EAAE,MAAM,CAAC;IACzB,QAAQ,EAAE,MAAM,CAAC;IACjB,cAAc,EAAE,SAAS,SAAS,EAAE,CAAC;IACrC,uBAAuB,EAAE,MAAM,CAAC;IAChC,qBAAqB,CAAC,EAAE,MAAM,CAAC;CAChC,CAAC;AAEF,MAAM,MAAM,+BAA+B,GAAG;IAC5C,WAAW,CAAC,EAAE,eAAe,CAAC;IAC9B,GAAG,CAAC,EAAE,MAAM,IAAI,CAAC;CAClB,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/mcp/types.ts"],"names":[],"mappings":"","sourcesContent":["import type { JWTVerifyGetKey } from 'jose';\nimport type { HostedExecutionWorkflow } from '../contracts/index.js';\n\nexport type McpInvocationScope = {\n adopterId: string;\n tenantId: string;\n subjectId: string;\n productSessionId: string;\n runtimeSessionId: string;\n invocationId: string;\n workflow: HostedExecutionWorkflow;\n};\n\nexport type VerifiedMcpCapability<TToolName extends string = string> = {\n capabilityId: string;\n serverId: string;\n allowedTools: readonly TToolName[];\n scope: McpInvocationScope;\n issuedAt: string;\n expiresAt: string;\n};\n\nexport interface McpCapabilityVerifier<TToolName extends string = string> {\n verify(assertion: string): Promise<VerifiedMcpCapability<TToolName>>;\n}\n\nexport type JwtMcpCapabilityVerifierConfig<TToolName extends string> = {\n issuer: string;\n audience: string;\n jwksUrl: URL;\n trustedAdopterId: string;\n serverId: string;\n supportedTools: readonly TToolName[];\n maxCapabilityAgeSeconds: number;\n clockToleranceSeconds?: number;\n};\n\nexport type JwtMcpCapabilityVerifierOptions = {\n keyResolver?: JWTVerifyGetKey;\n now?: () => Date;\n};\n"]}
@@ -0,0 +1,14 @@
1
+ import type { ExecutionAuthorityKeyPair } from '../authority/index.js';
2
+ /** Generates a non-persisted ES256 pair for tests and disposable local demos. */
3
+ export declare function generateEphemeralExecutionAuthorityKeyPair(): Promise<ExecutionAuthorityKeyPair>;
4
+ /**
5
+ * Creates a new owner-only ES256 private-JWK file for local development.
6
+ * Existing paths are never overwritten.
7
+ */
8
+ export declare function generateExecutionAuthorityKeyFile(filePath: string): Promise<void>;
9
+ /** Loads a non-exportable ES256 signing pair from an owner-only private JWK. */
10
+ export declare function loadExecutionAuthorityKeyPairFromFile(filePath: string): Promise<ExecutionAuthorityKeyPair>;
11
+ export declare class ExecutionAuthorityKeyFileError extends Error {
12
+ readonly name = "ExecutionAuthorityKeyFileError";
13
+ }
14
+ //# sourceMappingURL=authority-key.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"authority-key.d.ts","sourceRoot":"","sources":["../../src/node/authority-key.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,uBAAuB,CAAC;AAavE,iFAAiF;AACjF,wBAAsB,0CAA0C,IAAI,OAAO,CACzE,yBAAyB,CAC1B,CAMA;AAED;;;GAGG;AACH,wBAAsB,iCAAiC,CACrD,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,IAAI,CAAC,CAsCf;AAED,gFAAgF;AAChF,wBAAsB,qCAAqC,CACzD,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,yBAAyB,CAAC,CAoDpC;AAED,qBAAa,8BAA+B,SAAQ,KAAK;IACvD,QAAQ,CAAC,IAAI,oCAAoC;CAClD"}
@@ -0,0 +1,89 @@
1
+ import { constants } from 'node:fs';
2
+ import { open, unlink, } from 'node:fs/promises';
3
+ import { webcrypto } from 'node:crypto';
4
+ import { z } from 'zod';
5
+ const MAX_PRIVATE_JWK_BYTES = 16 * 1_024;
6
+ const ECDSA_P256 = { name: 'ECDSA', namedCurve: 'P-256' };
7
+ const Base64UrlSchema = z.string().min(1).regex(/^[A-Za-z0-9_-]+$/);
8
+ const PrivateP256JwkSchema = z.object({
9
+ kty: z.literal('EC'),
10
+ crv: z.literal('P-256'),
11
+ x: Base64UrlSchema,
12
+ y: Base64UrlSchema,
13
+ d: Base64UrlSchema,
14
+ }).passthrough();
15
+ /** Generates a non-persisted ES256 pair for tests and disposable local demos. */
16
+ export async function generateEphemeralExecutionAuthorityKeyPair() {
17
+ return webcrypto.subtle.generateKey(ECDSA_P256, false, ['sign', 'verify']);
18
+ }
19
+ /**
20
+ * Creates a new owner-only ES256 private-JWK file for local development.
21
+ * Existing paths are never overwritten.
22
+ */
23
+ export async function generateExecutionAuthorityKeyFile(filePath) {
24
+ const keyPair = await webcrypto.subtle.generateKey(ECDSA_P256, true, ['sign', 'verify']);
25
+ const jwk = PrivateP256JwkSchema.parse(await webcrypto.subtle.exportKey('jwk', keyPair.privateKey));
26
+ const body = `${JSON.stringify({
27
+ kty: jwk.kty,
28
+ crv: jwk.crv,
29
+ x: jwk.x,
30
+ y: jwk.y,
31
+ d: jwk.d,
32
+ })}\n`;
33
+ let file;
34
+ let created = false;
35
+ try {
36
+ file = await open(filePath, 'wx', 0o600);
37
+ created = true;
38
+ await file.writeFile(body, 'utf8');
39
+ await file.sync();
40
+ await file.close();
41
+ file = undefined;
42
+ }
43
+ catch {
44
+ if (created) {
45
+ await file?.close().catch(() => undefined);
46
+ file = undefined;
47
+ await unlink(filePath).catch(() => undefined);
48
+ }
49
+ throw new ExecutionAuthorityKeyFileError('Execution authority signing JWK could not be generated at the requested path.');
50
+ }
51
+ finally {
52
+ await file?.close().catch(() => undefined);
53
+ }
54
+ }
55
+ /** Loads a non-exportable ES256 signing pair from an owner-only private JWK. */
56
+ export async function loadExecutionAuthorityKeyPairFromFile(filePath) {
57
+ let file;
58
+ try {
59
+ file = await open(filePath, constants.O_RDONLY | constants.O_NOFOLLOW);
60
+ const metadata = await file.stat();
61
+ if (!metadata.isFile()
62
+ || metadata.size <= 0
63
+ || metadata.size > MAX_PRIVATE_JWK_BYTES) {
64
+ throw new ExecutionAuthorityKeyFileError('Execution authority signing JWK must be a small regular file.');
65
+ }
66
+ if (process.platform !== 'win32' && (metadata.mode & 0o077) !== 0) {
67
+ throw new ExecutionAuthorityKeyFileError('Execution authority signing JWK must not be group- or world-accessible.');
68
+ }
69
+ const jwk = PrivateP256JwkSchema.parse(JSON.parse(await file.readFile('utf8')));
70
+ const privateKey = await webcrypto.subtle.importKey('jwk', { kty: jwk.kty, crv: jwk.crv, x: jwk.x, y: jwk.y, d: jwk.d }, ECDSA_P256, false, ['sign']);
71
+ const publicKey = await webcrypto.subtle.importKey('jwk', { kty: jwk.kty, crv: jwk.crv, x: jwk.x, y: jwk.y }, ECDSA_P256, true, ['verify']);
72
+ await file.close();
73
+ file = undefined;
74
+ return { privateKey, publicKey };
75
+ }
76
+ catch (error) {
77
+ if (error instanceof ExecutionAuthorityKeyFileError) {
78
+ throw error;
79
+ }
80
+ throw new ExecutionAuthorityKeyFileError('Execution authority signing JWK could not be loaded.');
81
+ }
82
+ finally {
83
+ await file?.close().catch(() => undefined);
84
+ }
85
+ }
86
+ export class ExecutionAuthorityKeyFileError extends Error {
87
+ name = 'ExecutionAuthorityKeyFileError';
88
+ }
89
+ //# sourceMappingURL=authority-key.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"authority-key.js","sourceRoot":"","sources":["../../src/node/authority-key.ts"],"names":[],"mappings":"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","sourcesContent":["import { constants } from 'node:fs';\nimport {\n open,\n unlink,\n type FileHandle,\n} from 'node:fs/promises';\nimport { webcrypto } from 'node:crypto';\nimport { z } from 'zod';\nimport type { ExecutionAuthorityKeyPair } from '../authority/index.js';\n\nconst MAX_PRIVATE_JWK_BYTES = 16 * 1_024;\nconst ECDSA_P256 = { name: 'ECDSA', namedCurve: 'P-256' } as const;\nconst Base64UrlSchema = z.string().min(1).regex(/^[A-Za-z0-9_-]+$/);\nconst PrivateP256JwkSchema = z.object({\n kty: z.literal('EC'),\n crv: z.literal('P-256'),\n x: Base64UrlSchema,\n y: Base64UrlSchema,\n d: Base64UrlSchema,\n}).passthrough();\n\n/** Generates a non-persisted ES256 pair for tests and disposable local demos. */\nexport async function generateEphemeralExecutionAuthorityKeyPair(): Promise<\n ExecutionAuthorityKeyPair\n> {\n return webcrypto.subtle.generateKey(\n ECDSA_P256,\n false,\n ['sign', 'verify'],\n );\n}\n\n/**\n * Creates a new owner-only ES256 private-JWK file for local development.\n * Existing paths are never overwritten.\n */\nexport async function generateExecutionAuthorityKeyFile(\n filePath: string,\n): Promise<void> {\n const keyPair = await webcrypto.subtle.generateKey(\n ECDSA_P256,\n true,\n ['sign', 'verify'],\n );\n const jwk = PrivateP256JwkSchema.parse(\n await webcrypto.subtle.exportKey('jwk', keyPair.privateKey),\n );\n const body = `${JSON.stringify({\n kty: jwk.kty,\n crv: jwk.crv,\n x: jwk.x,\n y: jwk.y,\n d: jwk.d,\n })}\\n`;\n\n let file: FileHandle | undefined;\n let created = false;\n try {\n file = await open(filePath, 'wx', 0o600);\n created = true;\n await file.writeFile(body, 'utf8');\n await file.sync();\n await file.close();\n file = undefined;\n } catch {\n if (created) {\n await file?.close().catch(() => undefined);\n file = undefined;\n await unlink(filePath).catch(() => undefined);\n }\n throw new ExecutionAuthorityKeyFileError(\n 'Execution authority signing JWK could not be generated at the requested path.',\n );\n } finally {\n await file?.close().catch(() => undefined);\n }\n}\n\n/** Loads a non-exportable ES256 signing pair from an owner-only private JWK. */\nexport async function loadExecutionAuthorityKeyPairFromFile(\n filePath: string,\n): Promise<ExecutionAuthorityKeyPair> {\n let file: FileHandle | undefined;\n try {\n file = await open(\n filePath,\n constants.O_RDONLY | constants.O_NOFOLLOW,\n );\n const metadata = await file.stat();\n if (\n !metadata.isFile()\n || metadata.size <= 0\n || metadata.size > MAX_PRIVATE_JWK_BYTES\n ) {\n throw new ExecutionAuthorityKeyFileError(\n 'Execution authority signing JWK must be a small regular file.',\n );\n }\n if (process.platform !== 'win32' && (metadata.mode & 0o077) !== 0) {\n throw new ExecutionAuthorityKeyFileError(\n 'Execution authority signing JWK must not be group- or world-accessible.',\n );\n }\n const jwk = PrivateP256JwkSchema.parse(\n JSON.parse(await file.readFile('utf8')) as unknown,\n );\n const privateKey = await webcrypto.subtle.importKey(\n 'jwk',\n { kty: jwk.kty, crv: jwk.crv, x: jwk.x, y: jwk.y, d: jwk.d },\n ECDSA_P256,\n false,\n ['sign'],\n );\n const publicKey = await webcrypto.subtle.importKey(\n 'jwk',\n { kty: jwk.kty, crv: jwk.crv, x: jwk.x, y: jwk.y },\n ECDSA_P256,\n true,\n ['verify'],\n );\n await file.close();\n file = undefined;\n return { privateKey, publicKey };\n } catch (error) {\n if (error instanceof ExecutionAuthorityKeyFileError) {\n throw error;\n }\n throw new ExecutionAuthorityKeyFileError(\n 'Execution authority signing JWK could not be loaded.',\n );\n } finally {\n await file?.close().catch(() => undefined);\n }\n}\n\nexport class ExecutionAuthorityKeyFileError extends Error {\n readonly name = 'ExecutionAuthorityKeyFileError';\n}\n"]}
@@ -0,0 +1,21 @@
1
+ import type { HostedConversationModelCredentialProvider } from '../conversation/index.js';
2
+ export type DirectExecutionHostCredentialEnvironmentNames = {
3
+ localToken: string;
4
+ modelApiKey: string;
5
+ };
6
+ /**
7
+ * Non-enumerable direct-host and model credentials for a local/reviewed
8
+ * deployment. Production secret retrieval may construct the same class.
9
+ */
10
+ export declare class DirectExecutionHostCredentials implements HostedConversationModelCredentialProvider {
11
+ #private;
12
+ constructor(input: {
13
+ localToken: string;
14
+ modelApiKey: string;
15
+ });
16
+ /** Takes configured values out of the process environment before parsing. */
17
+ static takeFromEnvironment(environment: NodeJS.ProcessEnv, rawNames: DirectExecutionHostCredentialEnvironmentNames): DirectExecutionHostCredentials;
18
+ localToken(): string;
19
+ resolveModelApiKey(): Promise<string>;
20
+ }
21
+ //# sourceMappingURL=direct-credentials.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"direct-credentials.d.ts","sourceRoot":"","sources":["../../src/node/direct-credentials.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,yCAAyC,EAC1C,MAAM,0BAA0B,CAAC;AASlC,MAAM,MAAM,6CAA6C,GAAG;IAC1D,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;CACrB,CAAC;AAEF;;;GAGG;AACH,qBAAa,8BACb,YAAW,yCAAyC;;gBAItC,KAAK,EAAE;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAA;KAAE;IAS9D,6EAA6E;IAC7E,MAAM,CAAC,mBAAmB,CACxB,WAAW,EAAE,MAAM,CAAC,UAAU,EAC9B,QAAQ,EAAE,6CAA6C,GACtD,8BAA8B;IAmBjC,UAAU,IAAI,MAAM;IAId,kBAAkB,IAAI,OAAO,CAAC,MAAM,CAAC;CAG5C"}
@@ -0,0 +1,47 @@
1
+ import { z } from 'zod';
2
+ const LocalTokenSchema = z.string().trim().min(32).max(4_096);
3
+ const ModelApiKeySchema = z.string().trim().min(8).max(4_096);
4
+ const EnvironmentNameSchema = z.string().min(1).max(128).regex(/^[A-Za-z_][A-Za-z0-9_]*$/, 'must be an environment variable name');
5
+ /**
6
+ * Non-enumerable direct-host and model credentials for a local/reviewed
7
+ * deployment. Production secret retrieval may construct the same class.
8
+ */
9
+ export class DirectExecutionHostCredentials {
10
+ #localToken;
11
+ #modelApiKey;
12
+ constructor(input) {
13
+ try {
14
+ this.#localToken = LocalTokenSchema.parse(input.localToken);
15
+ this.#modelApiKey = ModelApiKeySchema.parse(input.modelApiKey);
16
+ }
17
+ catch {
18
+ throw new Error('Direct Execution Host credentials are invalid.');
19
+ }
20
+ }
21
+ /** Takes configured values out of the process environment before parsing. */
22
+ static takeFromEnvironment(environment, rawNames) {
23
+ const names = z.object({
24
+ localToken: EnvironmentNameSchema,
25
+ modelApiKey: EnvironmentNameSchema,
26
+ }).strict().refine((value) => value.localToken !== value.modelApiKey, {
27
+ message: 'credential environment names must be distinct',
28
+ }).parse(rawNames);
29
+ const input = {
30
+ localToken: environment[names.localToken],
31
+ modelApiKey: environment[names.modelApiKey],
32
+ };
33
+ delete environment[names.localToken];
34
+ delete environment[names.modelApiKey];
35
+ return new DirectExecutionHostCredentials({
36
+ localToken: input.localToken ?? '',
37
+ modelApiKey: input.modelApiKey ?? '',
38
+ });
39
+ }
40
+ localToken() {
41
+ return this.#localToken;
42
+ }
43
+ async resolveModelApiKey() {
44
+ return this.#modelApiKey;
45
+ }
46
+ }
47
+ //# sourceMappingURL=direct-credentials.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"direct-credentials.js","sourceRoot":"","sources":["../../src/node/direct-credentials.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAKxB,MAAM,gBAAgB,GAAG,CAAC,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;AAC9D,MAAM,iBAAiB,GAAG,CAAC,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;AAC9D,MAAM,qBAAqB,GAAG,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,KAAK,CAC5D,0BAA0B,EAC1B,sCAAsC,CACvC,CAAC;AAOF;;;GAGG;AACH,MAAM,OAAO,8BAA8B;IAEhC,WAAW,CAAS;IACpB,YAAY,CAAS;IAE9B,YAAY,KAAkD;QAC5D,IAAI,CAAC;YACH,IAAI,CAAC,WAAW,GAAG,gBAAgB,CAAC,KAAK,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;YAC5D,IAAI,CAAC,YAAY,GAAG,iBAAiB,CAAC,KAAK,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC;QACjE,CAAC;QAAC,MAAM,CAAC;YACP,MAAM,IAAI,KAAK,CAAC,gDAAgD,CAAC,CAAC;QACpE,CAAC;IACH,CAAC;IAED,6EAA6E;IAC7E,MAAM,CAAC,mBAAmB,CACxB,WAA8B,EAC9B,QAAuD;QAEvD,MAAM,KAAK,GAAG,CAAC,CAAC,MAAM,CAAC;YACrB,UAAU,EAAE,qBAAqB;YACjC,WAAW,EAAE,qBAAqB;SACnC,CAAC,CAAC,MAAM,EAAE,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,UAAU,KAAK,KAAK,CAAC,WAAW,EAAE;YACpE,OAAO,EAAE,+CAA+C;SACzD,CAAC,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;QACnB,MAAM,KAAK,GAAG;YACZ,UAAU,EAAE,WAAW,CAAC,KAAK,CAAC,UAAU,CAAC;YACzC,WAAW,EAAE,WAAW,CAAC,KAAK,CAAC,WAAW,CAAC;SAC5C,CAAC;QACF,OAAO,WAAW,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;QACrC,OAAO,WAAW,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC;QACtC,OAAO,IAAI,8BAA8B,CAAC;YACxC,UAAU,EAAE,KAAK,CAAC,UAAU,IAAI,EAAE;YAClC,WAAW,EAAE,KAAK,CAAC,WAAW,IAAI,EAAE;SACrC,CAAC,CAAC;IACL,CAAC;IAED,UAAU;QACR,OAAO,IAAI,CAAC,WAAW,CAAC;IAC1B,CAAC;IAED,KAAK,CAAC,kBAAkB;QACtB,OAAO,IAAI,CAAC,YAAY,CAAC;IAC3B,CAAC;CACF","sourcesContent":["import { z } from 'zod';\nimport type {\n HostedConversationModelCredentialProvider,\n} from '../conversation/index.js';\n\nconst LocalTokenSchema = z.string().trim().min(32).max(4_096);\nconst ModelApiKeySchema = z.string().trim().min(8).max(4_096);\nconst EnvironmentNameSchema = z.string().min(1).max(128).regex(\n /^[A-Za-z_][A-Za-z0-9_]*$/,\n 'must be an environment variable name',\n);\n\nexport type DirectExecutionHostCredentialEnvironmentNames = {\n localToken: string;\n modelApiKey: string;\n};\n\n/**\n * Non-enumerable direct-host and model credentials for a local/reviewed\n * deployment. Production secret retrieval may construct the same class.\n */\nexport class DirectExecutionHostCredentials\nimplements HostedConversationModelCredentialProvider {\n readonly #localToken: string;\n readonly #modelApiKey: string;\n\n constructor(input: { localToken: string; modelApiKey: string }) {\n try {\n this.#localToken = LocalTokenSchema.parse(input.localToken);\n this.#modelApiKey = ModelApiKeySchema.parse(input.modelApiKey);\n } catch {\n throw new Error('Direct Execution Host credentials are invalid.');\n }\n }\n\n /** Takes configured values out of the process environment before parsing. */\n static takeFromEnvironment(\n environment: NodeJS.ProcessEnv,\n rawNames: DirectExecutionHostCredentialEnvironmentNames,\n ): DirectExecutionHostCredentials {\n const names = z.object({\n localToken: EnvironmentNameSchema,\n modelApiKey: EnvironmentNameSchema,\n }).strict().refine((value) => value.localToken !== value.modelApiKey, {\n message: 'credential environment names must be distinct',\n }).parse(rawNames);\n const input = {\n localToken: environment[names.localToken],\n modelApiKey: environment[names.modelApiKey],\n };\n delete environment[names.localToken];\n delete environment[names.modelApiKey];\n return new DirectExecutionHostCredentials({\n localToken: input.localToken ?? '',\n modelApiKey: input.modelApiKey ?? '',\n });\n }\n\n localToken(): string {\n return this.#localToken;\n }\n\n async resolveModelApiKey(): Promise<string> {\n return this.#modelApiKey;\n }\n}\n"]}
@@ -0,0 +1,21 @@
1
+ import type { IncomingMessage, ServerResponse } from 'node:http';
2
+ import type { NodeExecutionAdopterHttpHandler, NodeExecutionAdopterHttpServiceConfig } from './types.js';
3
+ export declare const DEFAULT_ADOPTER_JWKS_PATH = "/.well-known/jwks.json";
4
+ export declare const DEFAULT_ADOPTER_CONVERSATION_TURNS_PATH = "/hosted-execution/conversation-turns";
5
+ /**
6
+ * Standard Node HTTP edge for adopter-side JWKS and hosted conversations.
7
+ *
8
+ * The service owns bounded parsing, credential-header scrubbing, SSE framing,
9
+ * disconnect cancellation, safe failures, and graceful shutdown. Product
10
+ * authentication, authorization, identity selection, and persistence stay in
11
+ * adopter callbacks.
12
+ */
13
+ export declare class NodeExecutionAdopterHttpService<Principal> implements NodeExecutionAdopterHttpHandler {
14
+ #private;
15
+ constructor(config: NodeExecutionAdopterHttpServiceConfig<Principal>);
16
+ handle(request: IncomingMessage, response: ServerResponse): boolean;
17
+ handleJwks(request: IncomingMessage, response: ServerResponse): void;
18
+ handleConversationTurn(request: IncomingMessage, response: ServerResponse): void;
19
+ close(): Promise<void>;
20
+ }
21
+ //# sourceMappingURL=http-service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http-service.d.ts","sourceRoot":"","sources":["../../src/node/http-service.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,eAAe,EACf,cAAc,EACf,MAAM,WAAW,CAAC;AAInB,OAAO,KAAK,EAEV,+BAA+B,EAE/B,qCAAqC,EAEtC,MAAM,YAAY,CAAC;AAEpB,eAAO,MAAM,yBAAyB,2BAA2B,CAAC;AAClE,eAAO,MAAM,uCAAuC,yCACZ,CAAC;AA4BzC;;;;;;;GAOG;AACH,qBAAa,+BAA+B,CAAC,SAAS,CACtD,YAAW,+BAA+B;;gBAwB5B,MAAM,EAAE,qCAAqC,CAAC,SAAS,CAAC;IAkBpE,MAAM,CAAC,OAAO,EAAE,eAAe,EAAE,QAAQ,EAAE,cAAc,GAAG,OAAO;IAanE,UAAU,CAAC,OAAO,EAAE,eAAe,EAAE,QAAQ,EAAE,cAAc,GAAG,IAAI;IAwBpE,sBAAsB,CACpB,OAAO,EAAE,eAAe,EACxB,QAAQ,EAAE,cAAc,GACvB,IAAI;IA0CP,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAmJvB"}