Skip to main content
Glama

FastMCP

by yamato-snow
FastMCP.test.ts36.1 kB
import { FastMCP, FastMCPSession, UserError, imageContent } from "./FastMCP.js"; import { z } from "zod"; import { test, expect, vi } from "vitest"; import { Client } from "@modelcontextprotocol/sdk/client/index.js"; import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js"; import { getRandomPort } from "get-port-please"; import { setTimeout as delay } from "timers/promises"; import { CreateMessageRequestSchema, ErrorCode, ListRootsRequestSchema, LoggingMessageNotificationSchema, McpError, PingRequestSchema, Root, } from "@modelcontextprotocol/sdk/types.js"; import { createEventSource, EventSourceClient } from 'eventsource-client'; const runWithTestServer = async ({ run, client: createClient, server: createServer, }: { server?: () => Promise<FastMCP>; client?: () => Promise<Client>; run: ({ client, server, }: { client: Client; server: FastMCP; session: FastMCPSession; }) => Promise<void>; }) => { const port = await getRandomPort(); const server = createServer ? await createServer() : new FastMCP({ name: "Test", version: "1.0.0", }); await server.start({ transportType: "sse", sse: { endpoint: "/sse", port, }, }); try { const client = createClient ? await createClient() : new Client( { name: "example-client", version: "1.0.0", }, { capabilities: {}, }, ); const transport = new SSEClientTransport( new URL(`http://localhost:${port}/sse`), ); const session = await new Promise<FastMCPSession>((resolve) => { server.on("connect", (event) => { resolve(event.session); }); client.connect(transport); }); await run({ client, server, session }); } finally { await server.stop(); } return port; }; test("adds tools", async () => { await runWithTestServer({ server: async () => { const server = new FastMCP({ name: "Test", version: "1.0.0", }); server.addTool({ name: "add", description: "Add two numbers", parameters: z.object({ a: z.number(), b: z.number(), }), execute: async (args) => { return String(args.a + args.b); }, }); return server; }, run: async ({ client }) => { expect(await client.listTools()).toEqual({ tools: [ { name: "add", description: "Add two numbers", inputSchema: { additionalProperties: false, $schema: "http://json-schema.org/draft-07/schema#", type: "object", properties: { a: { type: "number" }, b: { type: "number" }, }, required: ["a", "b"], }, }, ], }); }, }); }); test("calls a tool", async () => { await runWithTestServer({ server: async () => { const server = new FastMCP({ name: "Test", version: "1.0.0", }); server.addTool({ name: "add", description: "Add two numbers", parameters: z.object({ a: z.number(), b: z.number(), }), execute: async (args) => { return String(args.a + args.b); }, }); return server; }, run: async ({ client }) => { expect( await client.callTool({ name: "add", arguments: { a: 1, b: 2, }, }), ).toEqual({ content: [{ type: "text", text: "3" }], }); }, }); }); test("returns a list", async () => { await runWithTestServer({ server: async () => { const server = new FastMCP({ name: "Test", version: "1.0.0", }); server.addTool({ name: "add", description: "Add two numbers", parameters: z.object({ a: z.number(), b: z.number(), }), execute: async () => { return { content: [ { type: "text", text: "a" }, { type: "text", text: "b" }, ], }; }, }); return server; }, run: async ({ client }) => { expect( await client.callTool({ name: "add", arguments: { a: 1, b: 2, }, }), ).toEqual({ content: [ { type: "text", text: "a" }, { type: "text", text: "b" }, ], }); }, }); }); test("returns an image", async () => { await runWithTestServer({ server: async () => { const server = new FastMCP({ name: "Test", version: "1.0.0", }); server.addTool({ name: "add", description: "Add two numbers", parameters: z.object({ a: z.number(), b: z.number(), }), execute: async () => { return imageContent({ buffer: Buffer.from( "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=", "base64", ), }); }, }); return server; }, run: async ({ client }) => { expect( await client.callTool({ name: "add", arguments: { a: 1, b: 2, }, }), ).toEqual({ content: [ { type: "image", data: "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=", mimeType: "image/png", }, ], }); }, }); }); test("handles UserError errors", async () => { await runWithTestServer({ server: async () => { const server = new FastMCP({ name: "Test", version: "1.0.0", }); server.addTool({ name: "add", description: "Add two numbers", parameters: z.object({ a: z.number(), b: z.number(), }), execute: async () => { throw new UserError("Something went wrong"); }, }); return server; }, run: async ({ client }) => { expect( await client.callTool({ name: "add", arguments: { a: 1, b: 2, }, }), ).toEqual({ content: [{ type: "text", text: "Something went wrong" }], isError: true, }); }, }); }); test("calling an unknown tool throws McpError with MethodNotFound code", async () => { await runWithTestServer({ server: async () => { const server = new FastMCP({ name: "Test", version: "1.0.0", }); return server; }, run: async ({ client }) => { try { await client.callTool({ name: "add", arguments: { a: 1, b: 2, }, }); } catch (error) { expect(error).toBeInstanceOf(McpError); // @ts-expect-error - we know that error is an McpError expect(error.code).toBe(ErrorCode.MethodNotFound); } }, }); }); test("tracks tool progress", async () => { await runWithTestServer({ server: async () => { const server = new FastMCP({ name: "Test", version: "1.0.0", }); server.addTool({ name: "add", description: "Add two numbers", parameters: z.object({ a: z.number(), b: z.number(), }), execute: async (args, { reportProgress }) => { reportProgress({ progress: 0, total: 10, }); await delay(100); return String(args.a + args.b); }, }); return server; }, run: async ({ client }) => { const onProgress = vi.fn(); await client.callTool( { name: "add", arguments: { a: 1, b: 2, }, }, undefined, { onprogress: onProgress, }, ); expect(onProgress).toHaveBeenCalledTimes(1); expect(onProgress).toHaveBeenCalledWith({ progress: 0, total: 10, }); }, }); }); test("sets logging levels", async () => { await runWithTestServer({ run: async ({ client, session }) => { await client.setLoggingLevel("debug"); expect(session.loggingLevel).toBe("debug"); await client.setLoggingLevel("info"); expect(session.loggingLevel).toBe("info"); }, }); }); test("sends logging messages to the client", async () => { await runWithTestServer({ server: async () => { const server = new FastMCP({ name: "Test", version: "1.0.0", }); server.addTool({ name: "add", description: "Add two numbers", parameters: z.object({ a: z.number(), b: z.number(), }), execute: async (args, { log }) => { log.debug("debug message", { foo: "bar", }); log.error("error message"); log.info("info message"); log.warn("warn message"); return String(args.a + args.b); }, }); return server; }, run: async ({ client }) => { const onLog = vi.fn(); client.setNotificationHandler( LoggingMessageNotificationSchema, (message) => { if (message.method === "notifications/message") { onLog({ level: message.params.level, ...(message.params.data ?? {}), }); } }, ); await client.callTool({ name: "add", arguments: { a: 1, b: 2, }, }); expect(onLog).toHaveBeenCalledTimes(4); expect(onLog).toHaveBeenNthCalledWith(1, { level: "debug", message: "debug message", context: { foo: "bar", }, }); expect(onLog).toHaveBeenNthCalledWith(2, { level: "error", message: "error message", }); expect(onLog).toHaveBeenNthCalledWith(3, { level: "info", message: "info message", }); expect(onLog).toHaveBeenNthCalledWith(4, { level: "warning", message: "warn message", }); }, }); }); test("adds resources", async () => { await runWithTestServer({ server: async () => { const server = new FastMCP({ name: "Test", version: "1.0.0", }); server.addResource({ uri: "file:///logs/app.log", name: "Application Logs", mimeType: "text/plain", async load() { return { text: "Example log content", }; }, }); return server; }, run: async ({ client }) => { expect(await client.listResources()).toEqual({ resources: [ { uri: "file:///logs/app.log", name: "Application Logs", mimeType: "text/plain", }, ], }); }, }); }); test("clients reads a resource", async () => { await runWithTestServer({ server: async () => { const server = new FastMCP({ name: "Test", version: "1.0.0", }); server.addResource({ uri: "file:///logs/app.log", name: "Application Logs", mimeType: "text/plain", async load() { return { text: "Example log content", }; }, }); return server; }, run: async ({ client }) => { expect( await client.readResource({ uri: "file:///logs/app.log", }), ).toEqual({ contents: [ { uri: "file:///logs/app.log", name: "Application Logs", text: "Example log content", mimeType: "text/plain", }, ], }); }, }); }); test("clients reads a resource that returns multiple resources", async () => { await runWithTestServer({ server: async () => { const server = new FastMCP({ name: "Test", version: "1.0.0", }); server.addResource({ uri: "file:///logs/app.log", name: "Application Logs", mimeType: "text/plain", async load() { return [ { text: "a", }, { text: "b", }, ]; }, }); return server; }, run: async ({ client }) => { expect( await client.readResource({ uri: "file:///logs/app.log", }), ).toEqual({ contents: [ { uri: "file:///logs/app.log", name: "Application Logs", text: "a", mimeType: "text/plain", }, { uri: "file:///logs/app.log", name: "Application Logs", text: "b", mimeType: "text/plain", }, ], }); }, }); }); test("adds prompts", async () => { await runWithTestServer({ server: async () => { const server = new FastMCP({ name: "Test", version: "1.0.0", }); server.addPrompt({ name: "git-commit", description: "Generate a Git commit message", arguments: [ { name: "changes", description: "Git diff or description of changes", required: true, }, ], load: async (args) => { return `Generate a concise but descriptive commit message for these changes:\n\n${args.changes}`; }, }); return server; }, run: async ({ client }) => { expect( await client.getPrompt({ name: "git-commit", arguments: { changes: "foo", }, }), ).toEqual({ description: "Generate a Git commit message", messages: [ { role: "user", content: { type: "text", text: "Generate a concise but descriptive commit message for these changes:\n\nfoo", }, }, ], }); expect(await client.listPrompts()).toEqual({ prompts: [ { name: "git-commit", description: "Generate a Git commit message", arguments: [ { name: "changes", description: "Git diff or description of changes", required: true, }, ], }, ], }); }, }); }); test("uses events to notify server of client connect/disconnect", async () => { const port = await getRandomPort(); const server = new FastMCP({ name: "Test", version: "1.0.0", }); const onConnect = vi.fn(); const onDisconnect = vi.fn(); server.on("connect", onConnect); server.on("disconnect", onDisconnect); await server.start({ transportType: "sse", sse: { endpoint: "/sse", port, }, }); const client = new Client( { name: "example-client", version: "1.0.0", }, { capabilities: {}, }, ); const transport = new SSEClientTransport( new URL(`http://localhost:${port}/sse`), ); await client.connect(transport); await delay(100); expect(onConnect).toHaveBeenCalledTimes(1); expect(onDisconnect).toHaveBeenCalledTimes(0); expect(server.sessions).toEqual([expect.any(FastMCPSession)]); await client.close(); await delay(100); expect(onConnect).toHaveBeenCalledTimes(1); expect(onDisconnect).toHaveBeenCalledTimes(1); await server.stop(); }); test("handles multiple clients", async () => { const port = await getRandomPort(); const server = new FastMCP({ name: "Test", version: "1.0.0", }); await server.start({ transportType: "sse", sse: { endpoint: "/sse", port, }, }); const client1 = new Client( { name: "example-client", version: "1.0.0", }, { capabilities: {}, }, ); const transport1 = new SSEClientTransport( new URL(`http://localhost:${port}/sse`), ); await client1.connect(transport1); const client2 = new Client( { name: "example-client", version: "1.0.0", }, { capabilities: {}, }, ); const transport2 = new SSEClientTransport( new URL(`http://localhost:${port}/sse`), ); await client2.connect(transport2); await delay(100); expect(server.sessions).toEqual([ expect.any(FastMCPSession), expect.any(FastMCPSession), ]); await server.stop(); }); test("session knows about client capabilities", async () => { await runWithTestServer({ client: async () => { const client = new Client( { name: "example-client", version: "1.0.0", }, { capabilities: { roots: { listChanged: true, }, }, }, ); client.setRequestHandler(ListRootsRequestSchema, () => { return { roots: [ { uri: "file:///home/user/projects/frontend", name: "Frontend Repository", }, ], }; }); return client; }, run: async ({ session }) => { expect(session.clientCapabilities).toEqual({ roots: { listChanged: true, }, }); }, }); }); test("session knows about roots", async () => { await runWithTestServer({ client: async () => { const client = new Client( { name: "example-client", version: "1.0.0", }, { capabilities: { roots: { listChanged: true, }, }, }, ); client.setRequestHandler(ListRootsRequestSchema, () => { return { roots: [ { uri: "file:///home/user/projects/frontend", name: "Frontend Repository", }, ], }; }); return client; }, run: async ({ session }) => { expect(session.roots).toEqual([ { uri: "file:///home/user/projects/frontend", name: "Frontend Repository", }, ]); }, }); }); test("session listens to roots changes", async () => { let clientRoots: Root[] = [ { uri: "file:///home/user/projects/frontend", name: "Frontend Repository", }, ]; await runWithTestServer({ client: async () => { const client = new Client( { name: "example-client", version: "1.0.0", }, { capabilities: { roots: { listChanged: true, }, }, }, ); client.setRequestHandler(ListRootsRequestSchema, () => { return { roots: clientRoots, }; }); return client; }, run: async ({ session, client }) => { expect(session.roots).toEqual([ { uri: "file:///home/user/projects/frontend", name: "Frontend Repository", }, ]); clientRoots.push({ uri: "file:///home/user/projects/backend", name: "Backend Repository", }); await client.sendRootsListChanged(); const onRootsChanged = vi.fn(); session.on("rootsChanged", onRootsChanged); await delay(100); expect(session.roots).toEqual([ { uri: "file:///home/user/projects/frontend", name: "Frontend Repository", }, { uri: "file:///home/user/projects/backend", name: "Backend Repository", }, ]); expect(onRootsChanged).toHaveBeenCalledTimes(1); expect(onRootsChanged).toHaveBeenCalledWith({ roots: [ { uri: "file:///home/user/projects/frontend", name: "Frontend Repository", }, { uri: "file:///home/user/projects/backend", name: "Backend Repository", }, ], }); }, }); }); test("session sends pings to the client", async () => { await runWithTestServer({ run: async ({ client }) => { const onPing = vi.fn().mockReturnValue({}); client.setRequestHandler(PingRequestSchema, onPing); await delay(2000); expect(onPing).toHaveBeenCalledTimes(1); }, }); }); test("completes prompt arguments", async () => { await runWithTestServer({ server: async () => { const server = new FastMCP({ name: "Test", version: "1.0.0", }); server.addPrompt({ name: "countryPoem", description: "Writes a poem about a country", load: async ({ name }) => { return `Hello, ${name}!`; }, arguments: [ { name: "name", description: "Name of the country", required: true, complete: async (value) => { if (value === "Germ") { return { values: ["Germany"], }; } return { values: [], }; }, }, ], }); return server; }, run: async ({ client }) => { const response = await client.complete({ ref: { type: "ref/prompt", name: "countryPoem", }, argument: { name: "name", value: "Germ", }, }); expect(response).toEqual({ completion: { values: ["Germany"], }, }); }, }); }); test("adds automatic prompt argument completion when enum is provided", async () => { await runWithTestServer({ server: async () => { const server = new FastMCP({ name: "Test", version: "1.0.0", }); server.addPrompt({ name: "countryPoem", description: "Writes a poem about a country", load: async ({ name }) => { return `Hello, ${name}!`; }, arguments: [ { name: "name", description: "Name of the country", required: true, enum: ["Germany", "France", "Italy"], }, ], }); return server; }, run: async ({ client }) => { const response = await client.complete({ ref: { type: "ref/prompt", name: "countryPoem", }, argument: { name: "name", value: "Germ", }, }); expect(response).toEqual({ completion: { values: ["Germany"], total: 1, }, }); }, }); }); test("completes template resource arguments", async () => { await runWithTestServer({ server: async () => { const server = new FastMCP({ name: "Test", version: "1.0.0", }); server.addResourceTemplate({ uriTemplate: "issue:///{issueId}", name: "Issue", mimeType: "text/plain", arguments: [ { name: "issueId", description: "ID of the issue", complete: async (value) => { if (value === "123") { return { values: ["123456"], }; } return { values: [], }; }, }, ], load: async ({ issueId }) => { return { text: `Issue ${issueId}`, }; }, }); return server; }, run: async ({ client }) => { const response = await client.complete({ ref: { type: "ref/resource", uri: "issue:///{issueId}", }, argument: { name: "issueId", value: "123", }, }); expect(response).toEqual({ completion: { values: ["123456"], }, }); }, }); }); test("lists resource templates", async () => { await runWithTestServer({ server: async () => { const server = new FastMCP({ name: "Test", version: "1.0.0", }); server.addResourceTemplate({ uriTemplate: "file:///logs/{name}.log", name: "Application Logs", mimeType: "text/plain", arguments: [ { name: "name", description: "Name of the log", required: true, }, ], load: async ({ name }) => { return { text: `Example log content for ${name}`, }; }, }); return server; }, run: async ({ client }) => { expect(await client.listResourceTemplates()).toEqual({ resourceTemplates: [ { name: "Application Logs", uriTemplate: "file:///logs/{name}.log", }, ], }); }, }); }); test("clients reads a resource accessed via a resource template", async () => { const loadSpy = vi.fn((_args) => { return { text: "Example log content", }; }); await runWithTestServer({ server: async () => { const server = new FastMCP({ name: "Test", version: "1.0.0", }); server.addResourceTemplate({ uriTemplate: "file:///logs/{name}.log", name: "Application Logs", mimeType: "text/plain", arguments: [ { name: "name", description: "Name of the log", }, ], async load(args) { return loadSpy(args); }, }); return server; }, run: async ({ client }) => { expect( await client.readResource({ uri: "file:///logs/app.log", }), ).toEqual({ contents: [ { uri: "file:///logs/app.log", name: "Application Logs", text: "Example log content", mimeType: "text/plain", }, ], }); expect(loadSpy).toHaveBeenCalledWith({ name: "app", }); }, }); }); test("makes a sampling request", async () => { const onMessageRequest = vi.fn(() => { return { model: "gpt-3.5-turbo", role: "assistant", content: { type: "text", text: "The files are in the current directory.", }, }; }); await runWithTestServer({ client: async () => { const client = new Client( { name: "example-client", version: "1.0.0", }, { capabilities: { sampling: {}, }, }, ); return client; }, run: async ({ client, session }) => { client.setRequestHandler(CreateMessageRequestSchema, onMessageRequest); const response = await session.requestSampling({ messages: [ { role: "user", content: { type: "text", text: "What files are in the current directory?", }, }, ], systemPrompt: "You are a helpful file system assistant.", includeContext: "thisServer", maxTokens: 100, }); expect(response).toEqual({ model: "gpt-3.5-turbo", role: "assistant", content: { type: "text", text: "The files are in the current directory.", }, }); expect(onMessageRequest).toHaveBeenCalledTimes(1); }, }); }); test("throws ErrorCode.InvalidParams if tool parameters do not match zod schema", async () => { await runWithTestServer({ server: async () => { const server = new FastMCP({ name: "Test", version: "1.0.0", }); server.addTool({ name: "add", description: "Add two numbers", parameters: z.object({ a: z.number(), b: z.number(), }), execute: async (args) => { return String(args.a + args.b); }, }); return server; }, run: async ({ client }) => { try { await client.callTool({ name: "add", arguments: { a: 1, b: "invalid", }, }); } catch (error) { expect(error).toBeInstanceOf(McpError); // @ts-expect-error - we know that error is an McpError expect(error.code).toBe(ErrorCode.InvalidParams); // @ts-expect-error - we know that error is an McpError expect(error.message).toBe("MCP error -32602: MCP error -32602: Invalid add parameters"); } }, }); }); test("server remains usable after InvalidParams error", async () => { await runWithTestServer({ server: async () => { const server = new FastMCP({ name: "Test", version: "1.0.0", }); server.addTool({ name: "add", description: "Add two numbers", parameters: z.object({ a: z.number(), b: z.number(), }), execute: async (args) => { return String(args.a + args.b); }, }); return server; }, run: async ({ client }) => { try { await client.callTool({ name: "add", arguments: { a: 1, b: "invalid", }, }); } catch (error) { expect(error).toBeInstanceOf(McpError); // @ts-expect-error - we know that error is an McpError expect(error.code).toBe(ErrorCode.InvalidParams); // @ts-expect-error - we know that error is an McpError expect(error.message).toBe("MCP error -32602: MCP error -32602: Invalid add parameters"); } expect( await client.callTool({ name: "add", arguments: { a: 1, b: 2, }, }), ).toEqual({ content: [{ type: "text", text: "3" }], }); }, }); }); test("allows new clients to connect after a client disconnects", async () => { const port = await getRandomPort(); const server = new FastMCP({ name: "Test", version: "1.0.0", }); server.addTool({ name: "add", description: "Add two numbers", parameters: z.object({ a: z.number(), b: z.number(), }), execute: async (args) => { return String(args.a + args.b); }, }); await server.start({ transportType: "sse", sse: { endpoint: "/sse", port, }, }); const client1 = new Client( { name: "example-client", version: "1.0.0", }, { capabilities: {}, }, ); const transport1 = new SSEClientTransport( new URL(`http://localhost:${port}/sse`), ); await client1.connect(transport1); expect( await client1.callTool({ name: "add", arguments: { a: 1, b: 2, }, }), ).toEqual({ content: [{ type: "text", text: "3" }], }); await client1.close(); const client2 = new Client( { name: "example-client", version: "1.0.0", }, { capabilities: {}, }, ); const transport2 = new SSEClientTransport( new URL(`http://localhost:${port}/sse`), ); await client2.connect(transport2); expect( await client2.callTool({ name: "add", arguments: { a: 1, b: 2, }, }), ).toEqual({ content: [{ type: "text", text: "3" }], }); await client2.close(); await server.stop(); }); test("able to close server immediately after starting it", async () => { const port = await getRandomPort(); const server = new FastMCP({ name: "Test", version: "1.0.0", }); await server.start({ transportType: "sse", sse: { endpoint: "/sse", port, }, }); // We were previously not waiting for the server to start. // Therefore, this would have caused error 'Server is not running.'. await server.stop(); }); test("closing event source does not produce error", async () => { const port = await getRandomPort(); const server = new FastMCP({ name: "Test", version: "1.0.0", }); server.addTool({ name: "add", description: "Add two numbers", parameters: z.object({ a: z.number(), b: z.number(), }), execute: async (args) => { return String(args.a + args.b); }, }); await server.start({ transportType: "sse", sse: { endpoint: "/sse", port, }, }); const eventSource = await new Promise<EventSourceClient>((onMessage) => { const eventSource = createEventSource({ onConnect: () => { console.info('connected'); }, onDisconnect: () => { console.info('disconnected'); }, onMessage: () => { onMessage(eventSource); }, url: `http://127.0.0.1:${port}/sse`, }); }); expect(eventSource.readyState).toBe('open'); eventSource.close(); // We were getting unhandled error 'Not connected' // https://github.com/punkpeye/mcp-proxy/commit/62cf27d5e3dfcbc353e8d03c7714a62c37177b52 await delay(1000); await server.stop(); }); test("provides auth to tools", async () => { const port = await getRandomPort(); const authenticate = vi.fn(async () => { return { id: 1, }; }); const server = new FastMCP<{id: number}>({ name: "Test", version: "1.0.0", authenticate, }); const execute = vi.fn(async (args) => { return String(args.a + args.b); }); server.addTool({ name: "add", description: "Add two numbers", parameters: z.object({ a: z.number(), b: z.number(), }), execute, }); await server.start({ transportType: "sse", sse: { endpoint: "/sse", port, }, }); const client = new Client( { name: "example-client", version: "1.0.0", }, { capabilities: {}, }, ); const transport = new SSEClientTransport( new URL(`http://localhost:${port}/sse`), { eventSourceInit: { fetch: async (url, init) => { return fetch(url, { ...init, headers: { ...init?.headers, "x-api-key": "123", }, }); }, }, }, ); await client.connect(transport); expect(authenticate, "authenticate should have been called").toHaveBeenCalledTimes(1); expect( await client.callTool({ name: "add", arguments: { a: 1, b: 2, }, }), ).toEqual({ content: [{ type: "text", text: "3" }], }); expect(execute, "execute should have been called").toHaveBeenCalledTimes(1); expect(execute).toHaveBeenCalledWith({ a: 1, b: 2, }, { log: { debug: expect.any(Function), error: expect.any(Function), info: expect.any(Function), warn: expect.any(Function), }, reportProgress: expect.any(Function), session: { id: 1 }, }); }); test("blocks unauthorized requests", async () => { const port = await getRandomPort(); const server = new FastMCP<{id: number}>({ name: "Test", version: "1.0.0", authenticate: async () => { throw new Response(null, { status: 401, statusText: "Unauthorized", }); }, }); await server.start({ transportType: "sse", sse: { endpoint: "/sse", port, }, }); const client = new Client( { name: "example-client", version: "1.0.0", }, { capabilities: {}, }, ); const transport = new SSEClientTransport( new URL(`http://localhost:${port}/sse`), ); expect(async () => { await client.connect(transport); }).rejects.toThrow("SSE error: Non-200 status code (401)"); });

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/yamato-snow/2025_McpLab_FastMCP'

If you have feedback or need assistance with the MCP directory API, please join our Discord server