@noodleseed/one 0.191.0 → 0.192.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.
@@ -1,12 +1,8 @@
1
- import { type McpHttpHandler, type McpServerFactory } from '@modelcontextprotocol/server';
1
+ import type { McpHttpHandler } from '@modelcontextprotocol/server';
2
2
  import type { ProtocolRequestContext, ServedArtifact } from '../sdk-server.js';
3
- import { type McpProtocolMode } from './versions.js';
3
+ export { createPlatformDualEraMcpHandler } from './platform-handler.js';
4
4
  /**
5
5
  * The single Noodle MCP entry: SDK-owned era classification/dispatch with a narrow Noodle response seam.
6
6
  */
7
7
  export declare function createDualEraMcpHandler(target: ServedArtifact, context?: ProtocolRequestContext): McpHttpHandler;
8
- /** Platform-wide SDK v2 handler used by artifact and first-party MCP surfaces alike. */
9
- export declare function createPlatformDualEraMcpHandler(factory: McpServerFactory, platformOptions?: {
10
- readonly protocolMode?: McpProtocolMode;
11
- }): McpHttpHandler;
12
8
  //# sourceMappingURL=handler.d.ts.map
@@ -1,10 +1,10 @@
1
1
  import { randomBytes } from 'node:crypto';
2
- import { createMcpHandler, isLegacyRequest, } from '@modelcontextprotocol/server';
3
2
  import { notify } from '../observation.js';
4
3
  import { RequestStateManager, requestStateSecretBox } from '../request-state.js';
4
+ import { createPlatformDualEraMcpHandlerInternal } from './platform-handler.js';
5
5
  import { protocolErrorObservation } from './response-observation.js';
6
6
  import { buildDualEraMcpServer } from './server.js';
7
- import { SERVED_MCP_PROTOCOL_VERSIONS } from './versions.js';
7
+ export { createPlatformDualEraMcpHandler } from './platform-handler.js';
8
8
  /**
9
9
  * The single Noodle MCP entry: SDK-owned era classification/dispatch with a narrow Noodle response seam.
10
10
  */
@@ -24,40 +24,6 @@ export function createDualEraMcpHandler(target, context = {}) {
24
24
  finish: (request) => requestObservations.delete(request),
25
25
  });
26
26
  }
27
- /** Platform-wide SDK v2 handler used by artifact and first-party MCP surfaces alike. */
28
- export function createPlatformDualEraMcpHandler(factory, platformOptions = {}) {
29
- return createPlatformDualEraMcpHandlerInternal(factory, platformOptions);
30
- }
31
- function createPlatformDualEraMcpHandlerInternal(factory, platformOptions = {}, hooks) {
32
- const sdkHandler = createMcpHandler(factory, { legacy: 'stateless' });
33
- return {
34
- ...sdkHandler,
35
- fetch: async (request, options) => {
36
- hooks?.begin(request);
37
- try {
38
- const legacy = await isLegacyRequest(request.clone(), options?.parsedBody);
39
- const requestBody = await readRequestBody(request, options?.parsedBody);
40
- if (platformOptions.protocolMode === 'legacy-only' && !legacy) {
41
- return new Response(JSON.stringify({
42
- jsonrpc: '2.0',
43
- id: requestId(requestBody),
44
- error: { code: -32601, message: 'Method not found' },
45
- }), { status: 200, headers: { 'content-type': 'application/json' } });
46
- }
47
- const response = await sdkHandler.fetch(request, {
48
- ...options,
49
- ...(requestBody === undefined ? {} : { parsedBody: requestBody }),
50
- });
51
- const shaped = await shapeNoodleResponse(response, requestBody, legacy);
52
- await observeResponseBestEffort(hooks, request, requestBody, shaped);
53
- return shaped;
54
- }
55
- finally {
56
- hooks?.finish(request);
57
- }
58
- },
59
- };
60
- }
61
27
  const processRequestState = new RequestStateManager(requestStateSecretBox(randomBytes(32)));
62
28
  function requestProtocolContext(context, requestContext) {
63
29
  return {
@@ -79,26 +45,6 @@ function observedProtocolContext(context, requestContext, requestObservations) {
79
45
  },
80
46
  };
81
47
  }
82
- async function observeResponseBestEffort(hooks, request, requestBody, response) {
83
- try {
84
- await hooks?.observeResponse(request, requestBody, response);
85
- }
86
- catch {
87
- // Protocol analytics must never change the response it observes.
88
- }
89
- }
90
- async function readRequestBody(request, parsedBody) {
91
- if (parsedBody !== undefined)
92
- return parsedBody;
93
- if (request.method.toUpperCase() !== 'POST')
94
- return undefined;
95
- try {
96
- return await request.clone().json();
97
- }
98
- catch {
99
- return undefined;
100
- }
101
- }
102
48
  async function readResponseBody(response) {
103
49
  const contentType = response.headers.get('content-type');
104
50
  if (contentType?.split(';', 1)[0]?.trim().toLowerCase() !== 'application/json')
@@ -110,104 +56,4 @@ async function readResponseBody(response) {
110
56
  return undefined;
111
57
  }
112
58
  }
113
- async function shapeNoodleResponse(response, requestBody, legacy) {
114
- const contentType = response.headers.get('content-type');
115
- if (legacy && contentType?.split(';', 1)[0]?.trim().toLowerCase() === 'text/event-stream') {
116
- const eventStream = await response.text();
117
- const eventData = firstServerSentEventData(eventStream);
118
- if (eventData === undefined)
119
- return copyResponse(response, eventStream);
120
- const headers = new Headers(response.headers);
121
- headers.set('content-type', 'application/json');
122
- return shapeNoodleResponse(new Response(eventData, {
123
- status: response.status,
124
- statusText: response.statusText,
125
- headers,
126
- }), requestBody, legacy);
127
- }
128
- if (contentType?.split(';', 1)[0]?.trim().toLowerCase() !== 'application/json')
129
- return response;
130
- const text = await response.text();
131
- let body;
132
- try {
133
- body = JSON.parse(text);
134
- }
135
- catch {
136
- return copyResponse(response, text);
137
- }
138
- if (!isRecord(body))
139
- return copyResponse(response, text);
140
- const method = requestMethod(requestBody);
141
- const id = requestId(requestBody);
142
- const error = isRecord(body.error) ? body.error : undefined;
143
- const result = isRecord(body.result) ? body.result : undefined;
144
- let status = response.status;
145
- if (!legacy && method === 'initialize') {
146
- status = 400;
147
- body = {
148
- jsonrpc: '2.0',
149
- error: {
150
- code: -32022,
151
- message: 'Unsupported protocol version',
152
- data: {
153
- supported: [...SERVED_MCP_PROTOCOL_VERSIONS],
154
- requested: '2026-07-28',
155
- },
156
- },
157
- id,
158
- };
159
- }
160
- else if (error?.code === -32022 && isRecord(error.data)) {
161
- error.data.supported = [...SERVED_MCP_PROTOCOL_VERSIONS];
162
- }
163
- else if (legacy && error !== undefined && typeof error.code === 'number') {
164
- const code = method === 'resources/read' && error.code === -32602 ? -32002 : error.code;
165
- const rawMessage = typeof error.message === 'string'
166
- ? error.message.replace(/^MCP error -?\d+: /, '')
167
- : 'Protocol error';
168
- body = {
169
- jsonrpc: '2.0',
170
- id,
171
- error: {
172
- code,
173
- message: `MCP error ${code}: ${rawMessage}`,
174
- ...(error.data === undefined ? {} : { data: error.data }),
175
- },
176
- };
177
- }
178
- else if (!legacy && method === 'server/discover' && result !== undefined) {
179
- result.supportedVersions = [...SERVED_MCP_PROTOCOL_VERSIONS];
180
- }
181
- return copyResponse(response, JSON.stringify(body), status);
182
- }
183
- function copyResponse(response, body, status = response.status) {
184
- return new Response(body, {
185
- status,
186
- statusText: response.statusText,
187
- headers: response.headers,
188
- });
189
- }
190
- function firstServerSentEventData(body) {
191
- for (const event of body.split(/\r?\n\r?\n/)) {
192
- const data = event
193
- .split(/\r?\n/)
194
- .filter((line) => line.startsWith('data:'))
195
- .map((line) => line.slice(5).trimStart())
196
- .join('\n');
197
- if (data.length > 0)
198
- return data;
199
- }
200
- return undefined;
201
- }
202
- function requestMethod(body) {
203
- return isRecord(body) && typeof body.method === 'string' ? body.method : undefined;
204
- }
205
- function requestId(body) {
206
- if (!isRecord(body))
207
- return null;
208
- return typeof body.id === 'string' || typeof body.id === 'number' ? body.id : null;
209
- }
210
- function isRecord(value) {
211
- return typeof value === 'object' && value !== null && !Array.isArray(value);
212
- }
213
59
  //# sourceMappingURL=handler.js.map
@@ -0,0 +1,15 @@
1
+ import { type McpHttpHandler, type McpServerFactory } from '@modelcontextprotocol/server';
2
+ import { type McpProtocolMode } from './versions.js';
3
+ /** Platform-wide SDK v2 handler used by artifact and first-party MCP surfaces alike. */
4
+ export declare function createPlatformDualEraMcpHandler(factory: McpServerFactory, platformOptions?: {
5
+ readonly protocolMode?: McpProtocolMode;
6
+ }): McpHttpHandler;
7
+ export interface PlatformHandlerHooks {
8
+ readonly begin: (request: Request) => void;
9
+ readonly observeResponse: (request: Request, requestBody: unknown, response: Response) => void | Promise<void>;
10
+ readonly finish: (request: Request) => void;
11
+ }
12
+ export declare function createPlatformDualEraMcpHandlerInternal(factory: McpServerFactory, platformOptions?: {
13
+ readonly protocolMode?: McpProtocolMode;
14
+ }, hooks?: PlatformHandlerHooks): McpHttpHandler;
15
+ //# sourceMappingURL=platform-handler.d.ts.map
@@ -0,0 +1,157 @@
1
+ import { createMcpHandler, isLegacyRequest, } from '@modelcontextprotocol/server';
2
+ import { SERVED_MCP_PROTOCOL_VERSIONS } from './versions.js';
3
+ /** Platform-wide SDK v2 handler used by artifact and first-party MCP surfaces alike. */
4
+ export function createPlatformDualEraMcpHandler(factory, platformOptions = {}) {
5
+ return createPlatformDualEraMcpHandlerInternal(factory, platformOptions);
6
+ }
7
+ export function createPlatformDualEraMcpHandlerInternal(factory, platformOptions = {}, hooks) {
8
+ const sdkHandler = createMcpHandler(factory, { legacy: 'stateless' });
9
+ return {
10
+ ...sdkHandler,
11
+ fetch: async (request, options) => {
12
+ hooks?.begin(request);
13
+ try {
14
+ const legacy = await isLegacyRequest(request.clone(), options?.parsedBody);
15
+ const requestBody = await readRequestBody(request, options?.parsedBody);
16
+ if (platformOptions.protocolMode === 'legacy-only' && !legacy) {
17
+ return new Response(JSON.stringify({
18
+ jsonrpc: '2.0',
19
+ id: requestId(requestBody),
20
+ error: { code: -32601, message: 'Method not found' },
21
+ }), { status: 200, headers: { 'content-type': 'application/json' } });
22
+ }
23
+ const response = await sdkHandler.fetch(request, {
24
+ ...options,
25
+ ...(requestBody === undefined ? {} : { parsedBody: requestBody }),
26
+ });
27
+ const shaped = await shapeNoodleResponse(response, requestBody, legacy);
28
+ await observeResponseBestEffort(hooks, request, requestBody, shaped);
29
+ return shaped;
30
+ }
31
+ finally {
32
+ hooks?.finish(request);
33
+ }
34
+ },
35
+ };
36
+ }
37
+ async function observeResponseBestEffort(hooks, request, requestBody, response) {
38
+ try {
39
+ await hooks?.observeResponse(request, requestBody, response);
40
+ }
41
+ catch {
42
+ // Protocol analytics must never change the response it observes.
43
+ }
44
+ }
45
+ async function readRequestBody(request, parsedBody) {
46
+ if (parsedBody !== undefined)
47
+ return parsedBody;
48
+ if (request.method.toUpperCase() !== 'POST')
49
+ return undefined;
50
+ try {
51
+ return await request.clone().json();
52
+ }
53
+ catch {
54
+ return undefined;
55
+ }
56
+ }
57
+ async function shapeNoodleResponse(response, requestBody, legacy) {
58
+ const contentType = response.headers.get('content-type');
59
+ if (legacy && contentType?.split(';', 1)[0]?.trim().toLowerCase() === 'text/event-stream') {
60
+ const eventStream = await response.text();
61
+ const eventData = firstServerSentEventData(eventStream);
62
+ if (eventData === undefined)
63
+ return copyResponse(response, eventStream);
64
+ const headers = new Headers(response.headers);
65
+ headers.set('content-type', 'application/json');
66
+ return shapeNoodleResponse(new Response(eventData, {
67
+ status: response.status,
68
+ statusText: response.statusText,
69
+ headers,
70
+ }), requestBody, legacy);
71
+ }
72
+ if (contentType?.split(';', 1)[0]?.trim().toLowerCase() !== 'application/json')
73
+ return response;
74
+ const text = await response.text();
75
+ let body;
76
+ try {
77
+ body = JSON.parse(text);
78
+ }
79
+ catch {
80
+ return copyResponse(response, text);
81
+ }
82
+ if (!isRecord(body))
83
+ return copyResponse(response, text);
84
+ const method = requestMethod(requestBody);
85
+ const id = requestId(requestBody);
86
+ const error = isRecord(body.error) ? body.error : undefined;
87
+ const result = isRecord(body.result) ? body.result : undefined;
88
+ let status = response.status;
89
+ if (!legacy && method === 'initialize') {
90
+ status = 400;
91
+ body = {
92
+ jsonrpc: '2.0',
93
+ error: {
94
+ code: -32022,
95
+ message: 'Unsupported protocol version',
96
+ data: {
97
+ supported: [...SERVED_MCP_PROTOCOL_VERSIONS],
98
+ requested: '2026-07-28',
99
+ },
100
+ },
101
+ id,
102
+ };
103
+ }
104
+ else if (error?.code === -32022 && isRecord(error.data)) {
105
+ error.data.supported = [...SERVED_MCP_PROTOCOL_VERSIONS];
106
+ }
107
+ else if (legacy && error !== undefined && typeof error.code === 'number') {
108
+ const code = method === 'resources/read' && error.code === -32602 ? -32002 : error.code;
109
+ const rawMessage = typeof error.message === 'string'
110
+ ? error.message.replace(/^MCP error -?\d+: /, '')
111
+ : 'Protocol error';
112
+ body = {
113
+ jsonrpc: '2.0',
114
+ id,
115
+ error: {
116
+ code,
117
+ message: `MCP error ${code}: ${rawMessage}`,
118
+ ...(error.data === undefined ? {} : { data: error.data }),
119
+ },
120
+ };
121
+ }
122
+ else if (!legacy && method === 'server/discover' && result !== undefined) {
123
+ result.supportedVersions = [...SERVED_MCP_PROTOCOL_VERSIONS];
124
+ }
125
+ return copyResponse(response, JSON.stringify(body), status);
126
+ }
127
+ function copyResponse(response, body, status = response.status) {
128
+ return new Response(body, {
129
+ status,
130
+ statusText: response.statusText,
131
+ headers: response.headers,
132
+ });
133
+ }
134
+ function firstServerSentEventData(body) {
135
+ for (const event of body.split(/\r?\n\r?\n/)) {
136
+ const data = event
137
+ .split(/\r?\n/)
138
+ .filter((line) => line.startsWith('data:'))
139
+ .map((line) => line.slice(5).trimStart())
140
+ .join('\n');
141
+ if (data.length > 0)
142
+ return data;
143
+ }
144
+ return undefined;
145
+ }
146
+ function requestMethod(body) {
147
+ return isRecord(body) && typeof body.method === 'string' ? body.method : undefined;
148
+ }
149
+ function requestId(body) {
150
+ if (!isRecord(body))
151
+ return null;
152
+ return typeof body.id === 'string' || typeof body.id === 'number' ? body.id : null;
153
+ }
154
+ function isRecord(value) {
155
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
156
+ }
157
+ //# sourceMappingURL=platform-handler.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@noodleseed/one",
3
- "version": "0.191.0",
3
+ "version": "0.192.0",
4
4
  "private": false,
5
5
  "description": "Noodle CLI by Noodle Seed — author, run, and deploy declarative MCP servers. Embedding the assistant in your own web app is @noodleseed/assistant.",
6
6
  "license": "Apache-2.0",