poe-code 3.0.442-beta.1 → 3.0.443-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/packages/tiny-http-mcp-oauth-test-server/dist/index.js +22 -25
- package/packages/tiny-http-mcp-server/dist/index.d.ts +7 -8
- package/packages/tiny-http-mcp-server/dist/index.js +3 -4
- package/packages/tiny-http-mcp-server/dist/test-support.d.ts +1 -0
- package/packages/tiny-http-mcp-server/dist/test-support.js +38 -50
- package/packages/tiny-http-mcp-server/dist/testing.d.ts +2 -2
- package/packages/tiny-http-mcp-server/dist/testing.js +27 -25
- package/packages/tiny-http-mcp-server/package.json +4 -0
package/package.json
CHANGED
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
import http from "node:http";
|
|
2
2
|
import { createJwksTokenVerifier } from "mcp-oauth";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
3
|
+
import { TokenVerificationError } from "tiny-http-mcp-server";
|
|
4
|
+
import { createTestMcpServer, nodeFetch } from "tiny-http-mcp-server/test-support";
|
|
5
|
+
import { createOAuthTestServer } from "tiny-oauth-test-server";
|
|
5
6
|
import { hasOwnErrorCode } from "./error-codes.js";
|
|
6
7
|
const PROTECTED_RESOURCE_METADATA_PATH = "/.well-known/oauth-protected-resource";
|
|
7
8
|
function getProtectedResourceMetadataUrl(mcpUrl) {
|
|
8
9
|
const url = new URL(mcpUrl);
|
|
9
|
-
const protectedResourcePath = url.pathname === "/"
|
|
10
|
-
?
|
|
11
|
-
: url.pathname
|
|
10
|
+
const protectedResourcePath = url.pathname === "/"
|
|
11
|
+
? ""
|
|
12
|
+
: url.pathname.length > 1 && url.pathname.endsWith("/")
|
|
13
|
+
? url.pathname.slice(0, -1)
|
|
14
|
+
: url.pathname;
|
|
12
15
|
return new URL(`${PROTECTED_RESOURCE_METADATA_PATH}${protectedResourcePath}`, url).toString();
|
|
13
16
|
}
|
|
14
17
|
function normalizePath(path) {
|
|
@@ -165,12 +168,10 @@ export function createMcpOAuthTestServer(options = {}) {
|
|
|
165
168
|
}
|
|
166
169
|
listenPending = false;
|
|
167
170
|
throw lastError;
|
|
168
|
-
}
|
|
171
|
+
}
|
|
169
172
|
};
|
|
170
173
|
async function listenOnce(hostname, requestedPort) {
|
|
171
|
-
const oauthHostname = configuredIssuer === undefined
|
|
172
|
-
? hostname
|
|
173
|
-
: normalizeHostForListen(configuredIssuer.hostname);
|
|
174
|
+
const oauthHostname = configuredIssuer === undefined ? hostname : normalizeHostForListen(configuredIssuer.hostname);
|
|
174
175
|
const oauthPort = configuredIssuer === undefined
|
|
175
176
|
? await reservePort(oauthHostname)
|
|
176
177
|
: Number(configuredIssuer.port.length > 0 ? configuredIssuer.port : "80");
|
|
@@ -184,8 +185,7 @@ export function createMcpOAuthTestServer(options = {}) {
|
|
|
184
185
|
if (fixedPort === 0 && configuredResource === undefined) {
|
|
185
186
|
do {
|
|
186
187
|
fixedPort = await reservePort(hostname);
|
|
187
|
-
} while (fixedPort === oauthPort &&
|
|
188
|
-
normalizeHostForListen(hostname) === oauthHostname);
|
|
188
|
+
} while (fixedPort === oauthPort && normalizeHostForListen(hostname) === oauthHostname);
|
|
189
189
|
}
|
|
190
190
|
const oauth = createOAuthTestServer({
|
|
191
191
|
issuer,
|
|
@@ -193,31 +193,31 @@ export function createMcpOAuthTestServer(options = {}) {
|
|
|
193
193
|
staticClients: options.staticClients,
|
|
194
194
|
defaultAuthorization: {
|
|
195
195
|
autoApprove: options.autoApprove ?? false,
|
|
196
|
-
scopes
|
|
197
|
-
}
|
|
196
|
+
scopes
|
|
197
|
+
}
|
|
198
198
|
});
|
|
199
199
|
let oauthHandle;
|
|
200
200
|
let mcpHandle;
|
|
201
201
|
try {
|
|
202
202
|
oauthHandle = await oauth.listen({
|
|
203
203
|
port: oauthPort,
|
|
204
|
-
hostname: oauthHostname
|
|
204
|
+
hostname: oauthHostname
|
|
205
205
|
});
|
|
206
206
|
const resource = configuredResource ?? buildUrl(hostname, fixedPort, mcpPath);
|
|
207
207
|
const jwksVerifier = createJwksTokenVerifier({
|
|
208
208
|
jwksUrl: `${oauth.issuer}/.well-known/jwks.json`,
|
|
209
|
-
fetch: (input, init) => nodeFetch(input instanceof Request ? input.url : input, init)
|
|
209
|
+
fetch: (input, init) => nodeFetch(input instanceof Request ? input.url : input, init)
|
|
210
210
|
});
|
|
211
211
|
const verifier = {
|
|
212
212
|
async verify(input) {
|
|
213
213
|
if (isRevokedToken(oauth, input.token)) {
|
|
214
214
|
throw new TokenVerificationError({
|
|
215
215
|
error: "invalid_token",
|
|
216
|
-
errorDescription: "token revoked"
|
|
216
|
+
errorDescription: "token revoked"
|
|
217
217
|
});
|
|
218
218
|
}
|
|
219
219
|
return jwksVerifier.verify(input);
|
|
220
|
-
}
|
|
220
|
+
}
|
|
221
221
|
};
|
|
222
222
|
mcpHandle = await createTestMcpServer({
|
|
223
223
|
enableJsonResponse: true,
|
|
@@ -227,12 +227,12 @@ export function createMcpOAuthTestServer(options = {}) {
|
|
|
227
227
|
bearerMethodsSupported: ["header"],
|
|
228
228
|
scopesSupported: scopes,
|
|
229
229
|
requiredScopes: scopes,
|
|
230
|
-
verifier
|
|
231
|
-
}
|
|
230
|
+
verifier
|
|
231
|
+
}
|
|
232
232
|
}).listenHttp({
|
|
233
233
|
port: fixedPort,
|
|
234
234
|
hostname,
|
|
235
|
-
path: mcpPath
|
|
235
|
+
path: mcpPath
|
|
236
236
|
});
|
|
237
237
|
const prmUrl = getProtectedResourceMetadataUrl(mcpHandle.url);
|
|
238
238
|
const handle = {
|
|
@@ -245,16 +245,13 @@ export function createMcpOAuthTestServer(options = {}) {
|
|
|
245
245
|
if (currentHandle !== handle) {
|
|
246
246
|
return;
|
|
247
247
|
}
|
|
248
|
-
const results = await Promise.allSettled([
|
|
249
|
-
mcpHandle?.close(),
|
|
250
|
-
oauthHandle?.close(),
|
|
251
|
-
]);
|
|
248
|
+
const results = await Promise.allSettled([mcpHandle?.close(), oauthHandle?.close()]);
|
|
252
249
|
const rejected = results.find((result) => result.status === "rejected");
|
|
253
250
|
if (rejected !== undefined) {
|
|
254
251
|
throw rejected.reason;
|
|
255
252
|
}
|
|
256
253
|
currentHandle = null;
|
|
257
|
-
}
|
|
254
|
+
}
|
|
258
255
|
};
|
|
259
256
|
currentHandle = handle;
|
|
260
257
|
return handle;
|
|
@@ -1,14 +1,13 @@
|
|
|
1
|
-
export { createServer, defineSchema, Image, Audio, File, toContentBlocks, fileTypeFromBuffer, JSON_RPC_ERROR_CODES, ToolError
|
|
2
|
-
export type { Server, TypedSchema, ImageContent, AudioContent, EmbeddedResource, TextResourceContents, BlobResourceContents, ContentBlock, TextContent, FileTypeResult, ToolReturn, ServerOptions, ToolHandler, ToolDefinition, Tool, ToolAnnotations, ToolExecution, Icon, ContentAnnotations, ResourceLink, CallToolResult, PromptContentItem, PromptArgument, Prompt, PromptMessage, GetPromptResult, PromptHandler, PromptDefinition, Resource, ResourceTemplate, ResourceContents, ReadResourceResult, ResourceHandler, ResourceDefinition, ResourceTemplateDefinition, HandleResult, ContentItem, JSONSchema, JSONSchemaProperty, Transport, SDKTransport, JSONRPCRequest, JSONRPCResponse, JSONRPCError, JSONRPCMessage, JSONRPCNotification, InitializeResult
|
|
3
|
-
export { createExpressMiddleware, createExpressOAuthHandlers, createProtectedResourceMetadataRouter
|
|
1
|
+
export { createServer, defineSchema, Image, Audio, File, toContentBlocks, fileTypeFromBuffer, JSON_RPC_ERROR_CODES, ToolError } from "../../tiny-stdio-mcp-server/dist/index.js";
|
|
2
|
+
export type { Server, TypedSchema, ImageContent, AudioContent, EmbeddedResource, TextResourceContents, BlobResourceContents, ContentBlock, TextContent, FileTypeResult, ToolReturn, ServerOptions, ToolHandler, ToolDefinition, Tool, ToolAnnotations, ToolExecution, Icon, ContentAnnotations, ResourceLink, CallToolResult, PromptContentItem, PromptArgument, Prompt, PromptMessage, GetPromptResult, PromptHandler, PromptDefinition, Resource, ResourceTemplate, ResourceContents, ReadResourceResult, ResourceHandler, ResourceDefinition, ResourceTemplateDefinition, HandleResult, ContentItem, JSONSchema, JSONSchemaProperty, Transport, SDKTransport, JSONRPCRequest, JSONRPCResponse, JSONRPCError, JSONRPCMessage, JSONRPCNotification, InitializeResult } from "../../tiny-stdio-mcp-server/dist/index.js";
|
|
3
|
+
export { createExpressMiddleware, createExpressOAuthHandlers, createProtectedResourceMetadataRouter } from "./express-middleware.js";
|
|
4
4
|
export type { CreateExpressOAuthHandlersOptions } from "./express-middleware.js";
|
|
5
|
-
export { createHttpServer, createProtectedResourceMetadataDocument
|
|
5
|
+
export { createHttpServer, createProtectedResourceMetadataDocument } from "./http-server.js";
|
|
6
6
|
export type { HttpToolContext, HttpToolHandler, HttpListenOptions, HttpServer, HttpServerHandle, HttpTransportOptions, ProtectedResourceMetadataOptions, TinyHttpMcpServerOAuthOptions } from "./http-server.js";
|
|
7
7
|
export { TokenVerificationError } from "./auth.js";
|
|
8
|
-
export type { RequestAuthInfo, TokenVerifier, VerifiedAccessToken
|
|
8
|
+
export type { RequestAuthInfo, TokenVerifier, VerifiedAccessToken } from "./auth.js";
|
|
9
9
|
export { StreamableHttpTransport } from "./http-transport.js";
|
|
10
|
-
export type { HttpObservabilityEvent, HttpObservabilityOptions, StreamableHttpTransportOptions
|
|
10
|
+
export type { HttpObservabilityEvent, HttpObservabilityOptions, StreamableHttpTransportOptions } from "./http-transport.js";
|
|
11
11
|
export type { Session, SessionStore } from "./session.js";
|
|
12
12
|
export { createJwksTokenVerifier } from "../../mcp-oauth/dist/index.js";
|
|
13
|
-
export type { JwksTokenVerifier, JwksTokenVerifierOptions, JwksVerifiedAccessToken
|
|
14
|
-
export { createTestMcpServer, nodeFetch } from "./test-support.js";
|
|
13
|
+
export type { JwksTokenVerifier, JwksTokenVerifierOptions, JwksVerifiedAccessToken } from "../../mcp-oauth/dist/index.js";
|
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
export { createServer, defineSchema, Image, Audio, File, toContentBlocks, fileTypeFromBuffer, JSON_RPC_ERROR_CODES, ToolError
|
|
2
|
-
export { createExpressMiddleware, createExpressOAuthHandlers, createProtectedResourceMetadataRouter
|
|
3
|
-
export { createHttpServer, createProtectedResourceMetadataDocument
|
|
1
|
+
export { createServer, defineSchema, Image, Audio, File, toContentBlocks, fileTypeFromBuffer, JSON_RPC_ERROR_CODES, ToolError } from "tiny-stdio-mcp-server";
|
|
2
|
+
export { createExpressMiddleware, createExpressOAuthHandlers, createProtectedResourceMetadataRouter } from "./express-middleware.js";
|
|
3
|
+
export { createHttpServer, createProtectedResourceMetadataDocument } from "./http-server.js";
|
|
4
4
|
export { TokenVerificationError } from "./auth.js";
|
|
5
5
|
export { StreamableHttpTransport } from "./http-transport.js";
|
|
6
6
|
export { createJwksTokenVerifier } from "mcp-oauth";
|
|
7
|
-
export { createTestMcpServer, nodeFetch } from "./test-support.js";
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type HttpServer } from "./http-server.js";
|
|
2
|
+
export declare function installInMemoryHttp(): void;
|
|
2
3
|
export declare function nodeFetch(input: string | URL, init?: RequestInit): Promise<Response>;
|
|
3
4
|
export declare function createTestMcpServer(options?: Partial<{
|
|
4
5
|
name: string;
|
|
@@ -2,7 +2,7 @@ import http from "node:http";
|
|
|
2
2
|
import https from "node:https";
|
|
3
3
|
import { EventEmitter } from "node:events";
|
|
4
4
|
import { Readable } from "node:stream";
|
|
5
|
-
import { Audio, defineSchema, File, Image
|
|
5
|
+
import { Audio, defineSchema, File, Image } from "tiny-stdio-mcp-server";
|
|
6
6
|
import { createHttpServer } from "./http-server.js";
|
|
7
7
|
const TEST_PNG_BASE64 = "iVBORw0KGgo=";
|
|
8
8
|
const TEST_MP3_BASE64 = "SUQzBAAAAAA=";
|
|
@@ -14,7 +14,7 @@ function getInMemoryHttpState() {
|
|
|
14
14
|
target[IN_MEMORY_HTTP_STATE] = {
|
|
15
15
|
installed: false,
|
|
16
16
|
nextPort: 41_000,
|
|
17
|
-
servers: new Map()
|
|
17
|
+
servers: new Map()
|
|
18
18
|
};
|
|
19
19
|
}
|
|
20
20
|
return target[IN_MEMORY_HTTP_STATE];
|
|
@@ -25,13 +25,8 @@ function normalizeRequestHostname(hostname) {
|
|
|
25
25
|
}
|
|
26
26
|
return hostname;
|
|
27
27
|
}
|
|
28
|
-
function shouldUseInMemoryHttp() {
|
|
29
|
-
return process.env.VITEST !== undefined || process.env.NODE_ENV === "test";
|
|
30
|
-
}
|
|
31
28
|
function formatHostnameForOrigin(hostname) {
|
|
32
|
-
return hostname.includes(":") && !hostname.startsWith("[")
|
|
33
|
-
? `[${hostname}]`
|
|
34
|
-
: hostname;
|
|
29
|
+
return hostname.includes(":") && !hostname.startsWith("[") ? `[${hostname}]` : hostname;
|
|
35
30
|
}
|
|
36
31
|
function normalizeListenHostname(hostname) {
|
|
37
32
|
if (hostname === undefined || hostname.length === 0) {
|
|
@@ -46,7 +41,7 @@ function parseListenArgs(args) {
|
|
|
46
41
|
return {
|
|
47
42
|
port: options.port ?? 0,
|
|
48
43
|
hostname: normalizeListenHostname(options.host ?? options.hostname),
|
|
49
|
-
callback
|
|
44
|
+
callback
|
|
50
45
|
};
|
|
51
46
|
}
|
|
52
47
|
const port = typeof args[0] === "number" ? args[0] : 0;
|
|
@@ -54,13 +49,10 @@ function parseListenArgs(args) {
|
|
|
54
49
|
return {
|
|
55
50
|
port,
|
|
56
51
|
hostname: normalizeListenHostname(hostname),
|
|
57
|
-
callback
|
|
52
|
+
callback
|
|
58
53
|
};
|
|
59
54
|
}
|
|
60
|
-
function installInMemoryHttp() {
|
|
61
|
-
if (!shouldUseInMemoryHttp()) {
|
|
62
|
-
return;
|
|
63
|
-
}
|
|
55
|
+
export function installInMemoryHttp() {
|
|
64
56
|
const state = getInMemoryHttpState();
|
|
65
57
|
if (state.installed) {
|
|
66
58
|
return;
|
|
@@ -68,6 +60,7 @@ function installInMemoryHttp() {
|
|
|
68
60
|
state.installed = true;
|
|
69
61
|
const originalAddress = http.Server.prototype.address;
|
|
70
62
|
const originalClose = http.Server.prototype.close;
|
|
63
|
+
const originalFetch = globalThis.fetch.bind(globalThis);
|
|
71
64
|
http.Server.prototype.listen = function listen(...args) {
|
|
72
65
|
const { port: requestedPort, hostname, callback } = parseListenArgs(args);
|
|
73
66
|
const port = requestedPort === 0 ? state.nextPort++ : requestedPort;
|
|
@@ -78,9 +71,9 @@ function installInMemoryHttp() {
|
|
|
78
71
|
address: {
|
|
79
72
|
address,
|
|
80
73
|
family: address.includes(":") ? "IPv6" : "IPv4",
|
|
81
|
-
port
|
|
74
|
+
port
|
|
82
75
|
},
|
|
83
|
-
listening: true
|
|
76
|
+
listening: true
|
|
84
77
|
};
|
|
85
78
|
this[IN_MEMORY_HTTP_META] = meta;
|
|
86
79
|
state.servers.set(origin, this);
|
|
@@ -88,7 +81,7 @@ function installInMemoryHttp() {
|
|
|
88
81
|
configurable: true,
|
|
89
82
|
get() {
|
|
90
83
|
return this[IN_MEMORY_HTTP_META]?.listening ?? false;
|
|
91
|
-
}
|
|
84
|
+
}
|
|
92
85
|
});
|
|
93
86
|
queueMicrotask(() => {
|
|
94
87
|
this.emit("listening");
|
|
@@ -114,12 +107,21 @@ function installInMemoryHttp() {
|
|
|
114
107
|
});
|
|
115
108
|
return this;
|
|
116
109
|
};
|
|
110
|
+
globalThis.fetch = async (input, init = {}) => {
|
|
111
|
+
const normalized = normalizeFetchInput(input, init);
|
|
112
|
+
const inMemoryServer = getInMemoryServer(normalized.url);
|
|
113
|
+
if (inMemoryServer !== undefined) {
|
|
114
|
+
return fetchInMemory(inMemoryServer, normalized.url, normalized.init);
|
|
115
|
+
}
|
|
116
|
+
return originalFetch(input, init);
|
|
117
|
+
};
|
|
117
118
|
}
|
|
118
119
|
function getInMemoryServer(url) {
|
|
119
|
-
|
|
120
|
+
const state = globalThis[IN_MEMORY_HTTP_STATE];
|
|
121
|
+
if (state?.installed !== true) {
|
|
120
122
|
return undefined;
|
|
121
123
|
}
|
|
122
|
-
return
|
|
124
|
+
return state.servers.get(url.origin);
|
|
123
125
|
}
|
|
124
126
|
async function readFetchBody(body) {
|
|
125
127
|
if (body === undefined || body === null) {
|
|
@@ -149,14 +151,14 @@ async function fetchInMemory(server, url, init) {
|
|
|
149
151
|
method: init.method ?? "GET",
|
|
150
152
|
url: `${url.pathname}${url.search}`,
|
|
151
153
|
headers: Object.fromEntries(headers.entries()),
|
|
152
|
-
socket: {}
|
|
154
|
+
socket: {}
|
|
153
155
|
});
|
|
154
156
|
const response = new EventEmitter();
|
|
155
157
|
let streamController;
|
|
156
158
|
const responseBody = new ReadableStream({
|
|
157
159
|
start(controller) {
|
|
158
160
|
streamController = controller;
|
|
159
|
-
}
|
|
161
|
+
}
|
|
160
162
|
});
|
|
161
163
|
response.statusCode = 200;
|
|
162
164
|
response.statusMessage = "OK";
|
|
@@ -249,16 +251,13 @@ async function fetchInMemory(server, url, init) {
|
|
|
249
251
|
});
|
|
250
252
|
response.once("finish", resolve);
|
|
251
253
|
});
|
|
252
|
-
const responseContent = response.writableEnded
|
|
253
|
-
? Buffer.concat(response.chunks)
|
|
254
|
-
: responseBody;
|
|
254
|
+
const responseContent = response.writableEnded ? Buffer.concat(response.chunks) : responseBody;
|
|
255
255
|
return new Response(response.statusCode === 204 ? null : responseContent, {
|
|
256
256
|
status: response.statusCode,
|
|
257
257
|
statusText: response.statusMessage,
|
|
258
|
-
headers: response.headerValues
|
|
258
|
+
headers: response.headerValues
|
|
259
259
|
});
|
|
260
260
|
}
|
|
261
|
-
installInMemoryHttp();
|
|
262
261
|
function normalizeFetchInput(input, init = {}) {
|
|
263
262
|
if (input instanceof Request) {
|
|
264
263
|
return {
|
|
@@ -268,24 +267,13 @@ function normalizeFetchInput(input, init = {}) {
|
|
|
268
267
|
method: init.method ?? input.method,
|
|
269
268
|
headers: init.headers ?? input.headers,
|
|
270
269
|
body: init.body ?? input.body,
|
|
271
|
-
signal: init.signal ?? input.signal
|
|
272
|
-
}
|
|
270
|
+
signal: init.signal ?? input.signal
|
|
271
|
+
}
|
|
273
272
|
};
|
|
274
273
|
}
|
|
275
274
|
return {
|
|
276
275
|
url: new URL(String(input)),
|
|
277
|
-
init
|
|
278
|
-
};
|
|
279
|
-
}
|
|
280
|
-
if (shouldUseInMemoryHttp()) {
|
|
281
|
-
const originalFetch = globalThis.fetch.bind(globalThis);
|
|
282
|
-
globalThis.fetch = async (input, init = {}) => {
|
|
283
|
-
const normalized = normalizeFetchInput(input, init);
|
|
284
|
-
const inMemoryServer = getInMemoryServer(normalized.url);
|
|
285
|
-
if (inMemoryServer !== undefined) {
|
|
286
|
-
return fetchInMemory(inMemoryServer, normalized.url, normalized.init);
|
|
287
|
-
}
|
|
288
|
-
return originalFetch(input, init);
|
|
276
|
+
init
|
|
289
277
|
};
|
|
290
278
|
}
|
|
291
279
|
export async function nodeFetch(input, init = {}) {
|
|
@@ -302,7 +290,7 @@ export async function nodeFetch(input, init = {}) {
|
|
|
302
290
|
hostname: normalizeRequestHostname(url.hostname),
|
|
303
291
|
port: url.port.length > 0 ? Number(url.port) : url.protocol === "https:" ? 443 : 80,
|
|
304
292
|
path: `${url.pathname}${url.search}`,
|
|
305
|
-
headers: Object.fromEntries(headers.entries())
|
|
293
|
+
headers: Object.fromEntries(headers.entries())
|
|
306
294
|
}, (response) => {
|
|
307
295
|
const responseHeaders = new Headers();
|
|
308
296
|
for (const [key, value] of Object.entries(response.headers)) {
|
|
@@ -322,7 +310,7 @@ export async function nodeFetch(input, init = {}) {
|
|
|
322
310
|
resolve(new Response(body, {
|
|
323
311
|
status: response.statusCode ?? 0,
|
|
324
312
|
statusText: response.statusMessage ?? "",
|
|
325
|
-
headers: responseHeaders
|
|
313
|
+
headers: responseHeaders
|
|
326
314
|
}));
|
|
327
315
|
});
|
|
328
316
|
request.on("error", reject);
|
|
@@ -352,9 +340,9 @@ export async function nodeFetch(input, init = {}) {
|
|
|
352
340
|
export function createTestMcpServer(options = {}) {
|
|
353
341
|
const emptySchema = defineSchema({});
|
|
354
342
|
const textSchema = defineSchema({
|
|
355
|
-
text: { type: "string" }
|
|
343
|
+
text: { type: "string" }
|
|
356
344
|
});
|
|
357
|
-
return createHttpServer({
|
|
345
|
+
return (createHttpServer({
|
|
358
346
|
name: options.name ?? "conformance-test-server",
|
|
359
347
|
version: options.version ?? "1.0.0",
|
|
360
348
|
...(hasOwnProperty(options, "enableJsonResponse")
|
|
@@ -363,7 +351,7 @@ export function createTestMcpServer(options = {}) {
|
|
|
363
351
|
...(hasOwnProperty(options, "sessionIdGenerator")
|
|
364
352
|
? { sessionIdGenerator: options.sessionIdGenerator }
|
|
365
353
|
: {}),
|
|
366
|
-
...(hasOwnProperty(options, "oauth") ? { oauth: options.oauth } : {})
|
|
354
|
+
...(hasOwnProperty(options, "oauth") ? { oauth: options.oauth } : {})
|
|
367
355
|
})
|
|
368
356
|
.tool("echo", "Echo input text", textSchema, ({ text }) => String(text))
|
|
369
357
|
.tool("reverse", "Reverse input text", textSchema, ({ text }) => String(text).split("").reverse().join(""))
|
|
@@ -371,16 +359,16 @@ export function createTestMcpServer(options = {}) {
|
|
|
371
359
|
.tool("get_user", "Return a test user object", defineSchema({ id: { type: "string" } }), ({ id }) => ({
|
|
372
360
|
id: String(id),
|
|
373
361
|
name: "Alice",
|
|
374
|
-
role: "admin"
|
|
362
|
+
role: "admin"
|
|
375
363
|
}), {
|
|
376
364
|
type: "object",
|
|
377
365
|
properties: {
|
|
378
366
|
id: { type: "string" },
|
|
379
367
|
name: { type: "string" },
|
|
380
|
-
role: { type: "string" }
|
|
368
|
+
role: { type: "string" }
|
|
381
369
|
},
|
|
382
370
|
required: ["id", "name", "role"],
|
|
383
|
-
additionalProperties: false
|
|
371
|
+
additionalProperties: false
|
|
384
372
|
})
|
|
385
373
|
// Deliberately untyped fixture: exercises legacy JSON-array text fallback.
|
|
386
374
|
.tool("get_list", "Return a numeric array", emptySchema, () => [1, 2, 3])
|
|
@@ -390,7 +378,7 @@ export function createTestMcpServer(options = {}) {
|
|
|
390
378
|
.tool("get_mixed", "Return multiple content blocks", emptySchema, () => [
|
|
391
379
|
Image.fromBase64(TEST_PNG_BASE64, "image/png"),
|
|
392
380
|
"Caption for the image",
|
|
393
|
-
File.fromText("notes")
|
|
381
|
+
File.fromText("notes")
|
|
394
382
|
])
|
|
395
383
|
.tool("throw_sync", "Throw synchronously", emptySchema, () => {
|
|
396
384
|
throw new Error("sync boom");
|
|
@@ -403,7 +391,7 @@ export function createTestMcpServer(options = {}) {
|
|
|
403
391
|
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
404
392
|
return "done";
|
|
405
393
|
})
|
|
406
|
-
.tool("large_output", "Return 100KB of text", emptySchema, () => "x".repeat(100_000));
|
|
394
|
+
.tool("large_output", "Return 100KB of text", emptySchema, () => "x".repeat(100_000)));
|
|
407
395
|
}
|
|
408
396
|
function hasOwnProperty(value, key) {
|
|
409
397
|
return Object.prototype.hasOwnProperty.call(value, key);
|
|
@@ -2,7 +2,7 @@ import type { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
|
2
2
|
import type { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
|
3
3
|
import type { HttpServer, HttpServerHandle } from "./http-server.js";
|
|
4
4
|
import { type TokenVerifier } from "./auth.js";
|
|
5
|
-
export { createTestMcpServer, nodeFetch } from "./test-support.js";
|
|
5
|
+
export { createTestMcpServer, installInMemoryHttp, nodeFetch } from "./test-support.js";
|
|
6
6
|
export interface HttpTestPair {
|
|
7
7
|
client: Client;
|
|
8
8
|
transport: StreamableHTTPClientTransport;
|
|
@@ -56,4 +56,4 @@ export declare function createInMemoryTokenVerifier(options?: Partial<{
|
|
|
56
56
|
now: () => number;
|
|
57
57
|
}>): InMemoryTokenVerifier;
|
|
58
58
|
export declare function createHttpTestPair(server: HttpServer): Promise<HttpTestPair>;
|
|
59
|
-
export declare function createHttpTestPairWithTinyClient(server: HttpServer): Promise<TinyHttpTestPair
|
|
59
|
+
export declare function createHttpTestPairWithTinyClient(server: HttpServer): Promise<TinyHttpTestPair>;
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import { TokenVerificationError
|
|
2
|
-
import { nodeFetch } from "./test-support.js";
|
|
3
|
-
export { createTestMcpServer, nodeFetch } from "./test-support.js";
|
|
1
|
+
import { TokenVerificationError } from "./auth.js";
|
|
2
|
+
import { installInMemoryHttp, nodeFetch } from "./test-support.js";
|
|
3
|
+
export { createTestMcpServer, installInMemoryHttp, nodeFetch } from "./test-support.js";
|
|
4
4
|
function cloneVerifiedAccessToken(token) {
|
|
5
5
|
return {
|
|
6
6
|
...token,
|
|
7
7
|
audience: [...token.audience],
|
|
8
8
|
scopes: [...token.scopes],
|
|
9
|
-
claims: structuredClone(token.claims)
|
|
9
|
+
claims: structuredClone(token.claims)
|
|
10
10
|
};
|
|
11
11
|
}
|
|
12
12
|
export function createInMemoryTokenVerifier(options = {}) {
|
|
@@ -20,25 +20,25 @@ export function createInMemoryTokenVerifier(options = {}) {
|
|
|
20
20
|
if (token === undefined) {
|
|
21
21
|
throw new TokenVerificationError({
|
|
22
22
|
error: "invalid_token",
|
|
23
|
-
errorDescription: "unknown token"
|
|
23
|
+
errorDescription: "unknown token"
|
|
24
24
|
});
|
|
25
25
|
}
|
|
26
26
|
if (!input.authorizationServers.includes(token.issuer)) {
|
|
27
27
|
throw new TokenVerificationError({
|
|
28
28
|
error: "invalid_token",
|
|
29
|
-
errorDescription: "issuer mismatch"
|
|
29
|
+
errorDescription: "issuer mismatch"
|
|
30
30
|
});
|
|
31
31
|
}
|
|
32
32
|
if (!token.audience.includes(input.resource)) {
|
|
33
33
|
throw new TokenVerificationError({
|
|
34
34
|
error: "invalid_token",
|
|
35
|
-
errorDescription: "audience mismatch"
|
|
35
|
+
errorDescription: "audience mismatch"
|
|
36
36
|
});
|
|
37
37
|
}
|
|
38
38
|
if (token.expiresAt <= now()) {
|
|
39
39
|
throw new TokenVerificationError({
|
|
40
40
|
error: "invalid_token",
|
|
41
|
-
errorDescription: "token expired"
|
|
41
|
+
errorDescription: "token expired"
|
|
42
42
|
});
|
|
43
43
|
}
|
|
44
44
|
if (input.requiredScopes.length > 0 &&
|
|
@@ -46,11 +46,11 @@ export function createInMemoryTokenVerifier(options = {}) {
|
|
|
46
46
|
throw new TokenVerificationError({
|
|
47
47
|
error: "insufficient_scope",
|
|
48
48
|
errorDescription: "insufficient scope",
|
|
49
|
-
scope: input.requiredScopes
|
|
49
|
+
scope: input.requiredScopes
|
|
50
50
|
});
|
|
51
51
|
}
|
|
52
52
|
return cloneVerifiedAccessToken(token);
|
|
53
|
-
}
|
|
53
|
+
}
|
|
54
54
|
},
|
|
55
55
|
issueToken(input) {
|
|
56
56
|
const token = input.token ?? `test-token-${nextTokenId++}`;
|
|
@@ -68,13 +68,13 @@ export function createInMemoryTokenVerifier(options = {}) {
|
|
|
68
68
|
...(input.subject === undefined
|
|
69
69
|
? {}
|
|
70
70
|
: {
|
|
71
|
-
sub: input.subject
|
|
71
|
+
sub: input.subject
|
|
72
72
|
}),
|
|
73
73
|
...(input.clientId === undefined
|
|
74
74
|
? {}
|
|
75
75
|
: {
|
|
76
|
-
client_id: input.clientId
|
|
77
|
-
})
|
|
76
|
+
client_id: input.clientId
|
|
77
|
+
})
|
|
78
78
|
};
|
|
79
79
|
tokens.set(token, {
|
|
80
80
|
token,
|
|
@@ -86,16 +86,16 @@ export function createInMemoryTokenVerifier(options = {}) {
|
|
|
86
86
|
...(input.subject === undefined
|
|
87
87
|
? {}
|
|
88
88
|
: {
|
|
89
|
-
subject: input.subject
|
|
89
|
+
subject: input.subject
|
|
90
90
|
}),
|
|
91
91
|
...(input.clientId === undefined
|
|
92
92
|
? {}
|
|
93
93
|
: {
|
|
94
|
-
clientId: input.clientId
|
|
95
|
-
})
|
|
94
|
+
clientId: input.clientId
|
|
95
|
+
})
|
|
96
96
|
});
|
|
97
97
|
return token;
|
|
98
|
-
}
|
|
98
|
+
}
|
|
99
99
|
};
|
|
100
100
|
}
|
|
101
101
|
export async function createHttpTestPair(server) {
|
|
@@ -108,10 +108,11 @@ export async function createHttpTestPair(server) {
|
|
|
108
108
|
catch (error) {
|
|
109
109
|
throw new Error("createHttpTestPair requires @modelcontextprotocol/sdk; install it as a devDependency or use createHttpTestPairWithTinyClient", { cause: error });
|
|
110
110
|
}
|
|
111
|
+
installInMemoryHttp();
|
|
111
112
|
const handle = await server.listenHttp({ port: 0 });
|
|
112
113
|
const client = new sdkClient.Client({ name: "sdk-test-client", version: "1.0.0" });
|
|
113
114
|
const transport = new sdkTransport.StreamableHTTPClientTransport(new URL(handle.url), {
|
|
114
|
-
fetch: nodeFetch
|
|
115
|
+
fetch: nodeFetch
|
|
115
116
|
});
|
|
116
117
|
try {
|
|
117
118
|
await client.connect(transport);
|
|
@@ -131,7 +132,7 @@ export async function createHttpTestPair(server) {
|
|
|
131
132
|
if (rejected !== undefined) {
|
|
132
133
|
throw rejected.reason;
|
|
133
134
|
}
|
|
134
|
-
}
|
|
135
|
+
}
|
|
135
136
|
};
|
|
136
137
|
}
|
|
137
138
|
export async function createHttpTestPairWithTinyClient(server) {
|
|
@@ -139,13 +140,14 @@ export async function createHttpTestPairWithTinyClient(server) {
|
|
|
139
140
|
try {
|
|
140
141
|
tinyMcpClient = await import("tiny-mcp-client");
|
|
141
142
|
}
|
|
142
|
-
catch {
|
|
143
|
-
|
|
143
|
+
catch (error) {
|
|
144
|
+
throw new Error("createHttpTestPairWithTinyClient requires tiny-mcp-client; install tiny-mcp-client as a devDependency", { cause: error });
|
|
144
145
|
}
|
|
146
|
+
installInMemoryHttp();
|
|
145
147
|
const handle = await server.listenHttp({ port: 0 });
|
|
146
148
|
const requests = [];
|
|
147
149
|
const client = new tinyMcpClient.McpClient({
|
|
148
|
-
clientInfo: { name: "tiny-http-test-client", version: "1.0.0" }
|
|
150
|
+
clientInfo: { name: "tiny-http-test-client", version: "1.0.0" }
|
|
149
151
|
});
|
|
150
152
|
const transport = new tinyMcpClient.HttpTransport({
|
|
151
153
|
url: handle.url,
|
|
@@ -169,10 +171,10 @@ export async function createHttpTestPairWithTinyClient(server) {
|
|
|
169
171
|
method,
|
|
170
172
|
sessionId: headers.get("mcp-session-id"),
|
|
171
173
|
jsonRpcMethod,
|
|
172
|
-
responseContentType: response.headers.get("content-type")
|
|
174
|
+
responseContentType: response.headers.get("content-type")
|
|
173
175
|
});
|
|
174
176
|
return response;
|
|
175
|
-
}
|
|
177
|
+
}
|
|
176
178
|
});
|
|
177
179
|
await client.connect(transport);
|
|
178
180
|
return {
|
|
@@ -184,6 +186,6 @@ export async function createHttpTestPairWithTinyClient(server) {
|
|
|
184
186
|
cleanup: async () => {
|
|
185
187
|
await client.close();
|
|
186
188
|
await handle.close();
|
|
187
|
-
}
|
|
189
|
+
}
|
|
188
190
|
};
|
|
189
191
|
}
|