@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.
- package/node_modules/@noodle-borg/protocol/dist/v2/handler.d.ts +2 -6
- package/node_modules/@noodle-borg/protocol/dist/v2/handler.js +2 -156
- package/node_modules/@noodle-borg/protocol/dist/v2/platform-handler.d.ts +15 -0
- package/node_modules/@noodle-borg/protocol/dist/v2/platform-handler.js +157 -0
- package/package.json +1 -1
|
@@ -1,12 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { McpHttpHandler } from '@modelcontextprotocol/server';
|
|
2
2
|
import type { ProtocolRequestContext, ServedArtifact } from '../sdk-server.js';
|
|
3
|
-
|
|
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
|
-
|
|
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.
|
|
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",
|