@smithery/sdk 1.2.3 → 1.3.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/README.md CHANGED
@@ -22,14 +22,14 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"
22
22
  function createMcpServer({ sessionId, config }) {
23
23
  // Create and return a server instance
24
24
  // https://github.com/modelcontextprotocol/typescript-sdk?tab=readme-ov-file#core-concepts
25
- const server = new McpServer({
25
+ const mcpServer = new McpServer({
26
26
  name: "My App",
27
27
  version: "1.0.0"
28
28
  })
29
29
 
30
30
  // ...
31
31
 
32
- return server
32
+ return mcpServer.server
33
33
  }
34
34
 
35
35
  // Create the stateless server using your MCP server function.
@@ -0,0 +1,15 @@
1
+ import type { Client } from "@modelcontextprotocol/sdk/client/index.js";
2
+ import { type Tool } from "ai";
3
+ type ToolClient = Pick<Client, "listTools" | "callTool" | "setNotificationHandler">;
4
+ /**
5
+ * Watches the MCP client for tool changes and updates the tools object accordingly.
6
+ * @param client The MCP client to watch
7
+ * @returns A record of tool names to their implementations
8
+ */
9
+ export declare function watchTools(client: ToolClient): Promise<Record<string, Tool>>;
10
+ /**
11
+ * Returns a set of wrapped AI SDK tools from the MCP server.
12
+ * @returns A record of tool names to their implementations
13
+ */
14
+ export declare function listTools(client: ToolClient): Promise<Record<string, Tool>>;
15
+ export {};
@@ -0,0 +1,39 @@
1
+ import { ToolListChangedNotificationSchema } from "@modelcontextprotocol/sdk/types.js";
2
+ import { jsonSchema, tool, } from "ai";
3
+ /**
4
+ * Watches the MCP client for tool changes and updates the tools object accordingly.
5
+ * @param client The MCP client to watch
6
+ * @returns A record of tool names to their implementations
7
+ */
8
+ export async function watchTools(client) {
9
+ const tools = {};
10
+ client.setNotificationHandler(ToolListChangedNotificationSchema, async () => {
11
+ Object.assign(tools, await listTools(client));
12
+ });
13
+ Object.assign(tools, await listTools(client));
14
+ return tools;
15
+ }
16
+ /**
17
+ * Returns a set of wrapped AI SDK tools from the MCP server.
18
+ * @returns A record of tool names to their implementations
19
+ */
20
+ export async function listTools(client) {
21
+ const tools = {};
22
+ const listToolsResult = await client.listTools();
23
+ for (const { name, description, inputSchema } of listToolsResult.tools) {
24
+ const parameters = jsonSchema(inputSchema);
25
+ tools[name] = tool({
26
+ description,
27
+ parameters,
28
+ execute: async (args, options) => {
29
+ options?.abortSignal?.throwIfAborted();
30
+ const result = await client.callTool({
31
+ name,
32
+ arguments: args,
33
+ });
34
+ return result;
35
+ },
36
+ });
37
+ }
38
+ return tools;
39
+ }
@@ -2,4 +2,4 @@ import type { Client } from "@modelcontextprotocol/sdk/client/index.js";
2
2
  /**
3
3
  * Wraps each tool call so any errors get sent back to the LLM instead of throwing
4
4
  */
5
- export declare function wrapErrorAdapter<C extends Pick<Client, "callTool">>(client: C): C;
5
+ export declare function wrapError<C extends Pick<Client, "callTool">>(client: C): C;
@@ -1,15 +1,14 @@
1
1
  import { CallToolResultSchema, } from "@modelcontextprotocol/sdk/types.js";
2
+ import { patch } from "src/shared/patch.js";
2
3
  /**
3
4
  * Wraps each tool call so any errors get sent back to the LLM instead of throwing
4
5
  */
5
- export function wrapErrorAdapter(client) {
6
- const callTool = client.callTool.bind(client);
7
- client.callTool = async (params, resultSchema = CallToolResultSchema, options) => {
6
+ export function wrapError(client) {
7
+ patch(client, "callTool", (callTool) => async (params, resultSchema = CallToolResultSchema, options) => {
8
8
  try {
9
9
  return await callTool(params, resultSchema, options);
10
10
  }
11
11
  catch (err) {
12
- console.error("Tool calling error:", err);
13
12
  return {
14
13
  content: [
15
14
  {
@@ -20,6 +19,6 @@ export function wrapErrorAdapter(client) {
20
19
  isError: true,
21
20
  };
22
21
  }
23
- };
22
+ });
24
23
  return client;
25
24
  }
@@ -1,9 +1,9 @@
1
1
  import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
2
+ import { type SmitheryUrlOptions } from "../config.js";
2
3
  /**
3
4
  * Creates a transport to connect to the Smithery server
4
5
  * @param baseUrl The URL of the Smithery server (without trailing slash or protocol)
5
- * @param config Config to pass to the server
6
- * @param apiKey Optional API key for authentication
6
+ * @param options Optional configuration object
7
7
  * @returns Transport
8
8
  */
9
- export declare function createTransport(baseUrl: string, config?: object, apiKey?: string): StreamableHTTPClientTransport;
9
+ export declare function createTransport(baseUrl: string, options?: SmitheryUrlOptions): StreamableHTTPClientTransport;
@@ -3,10 +3,9 @@ import { createSmitheryUrl } from "../config.js";
3
3
  /**
4
4
  * Creates a transport to connect to the Smithery server
5
5
  * @param baseUrl The URL of the Smithery server (without trailing slash or protocol)
6
- * @param config Config to pass to the server
7
- * @param apiKey Optional API key for authentication
6
+ * @param options Optional configuration object
8
7
  * @returns Transport
9
8
  */
10
- export function createTransport(baseUrl, config, apiKey) {
11
- return new StreamableHTTPClientTransport(createSmitheryUrl(`${baseUrl}`, config, apiKey));
9
+ export function createTransport(baseUrl, options) {
10
+ return new StreamableHTTPClientTransport(createSmitheryUrl(baseUrl, options));
12
11
  }
package/dist/config.d.ts CHANGED
@@ -1,12 +1,16 @@
1
1
  import type express from "express";
2
+ export interface SmitheryUrlOptions {
3
+ apiKey?: string;
4
+ profile?: string;
5
+ config?: object;
6
+ }
2
7
  /**
3
8
  * Creates a URL to connect to the Smithery MCP server.
4
9
  * @param baseUrl The base URL of the Smithery server
5
- * @param config Optional configuration object
6
- * @param apiKey API key for authentication. Required if using Smithery.
10
+ * @param options Optional configuration object
7
11
  * @returns A URL object with properly encoded parameters. Example: https://server.smithery.ai/{namespace}/mcp?config=BASE64_ENCODED_CONFIG&api_key=API_KEY
8
12
  */
9
- export declare function createSmitheryUrl(baseUrl: string, config?: object, apiKey?: string): URL;
13
+ export declare function createSmitheryUrl(baseUrl: string, options?: SmitheryUrlOptions): URL;
10
14
  /**
11
15
  * Parses the config from an express request by checking the query parameter "config".
12
16
  * @param req The express request
package/dist/config.js CHANGED
@@ -1,20 +1,22 @@
1
1
  /**
2
2
  * Creates a URL to connect to the Smithery MCP server.
3
3
  * @param baseUrl The base URL of the Smithery server
4
- * @param config Optional configuration object
5
- * @param apiKey API key for authentication. Required if using Smithery.
4
+ * @param options Optional configuration object
6
5
  * @returns A URL object with properly encoded parameters. Example: https://server.smithery.ai/{namespace}/mcp?config=BASE64_ENCODED_CONFIG&api_key=API_KEY
7
6
  */
8
- export function createSmitheryUrl(baseUrl, config, apiKey) {
7
+ export function createSmitheryUrl(baseUrl, options) {
9
8
  const url = new URL(`${baseUrl}/mcp`);
10
- if (config) {
9
+ if (options?.config) {
11
10
  const param = typeof window !== "undefined"
12
- ? btoa(JSON.stringify(config))
13
- : Buffer.from(JSON.stringify(config)).toString("base64");
11
+ ? btoa(JSON.stringify(options.config))
12
+ : Buffer.from(JSON.stringify(options.config)).toString("base64");
14
13
  url.searchParams.set("config", param);
15
14
  }
16
- if (apiKey) {
17
- url.searchParams.set("api_key", apiKey);
15
+ if (options?.apiKey) {
16
+ url.searchParams.set("api_key", options.apiKey);
17
+ }
18
+ if (options?.profile) {
19
+ url.searchParams.set("profile", options.profile);
18
20
  }
19
21
  return url;
20
22
  }
@@ -0,0 +1,17 @@
1
+ import type { Transport } from "@modelcontextprotocol/sdk/shared/transport.js";
2
+ export interface SessionStore<T extends Transport> {
3
+ /** return existing transport (or `undefined`) */
4
+ get(id: string): T | undefined;
5
+ /** insert / update */
6
+ set(id: string, t: T): void;
7
+ /** optional - explicit eviction */
8
+ delete?(id: string): void;
9
+ }
10
+ /**
11
+ * Minimal Map‑based LRU implementation that fulfils {@link SessionStore}.
12
+ * Keeps at most `max` transports; upon insert, the least‑recently‑used entry
13
+ * (oldest insertion order) is removed and the evicted transport is closed.
14
+ *
15
+ * @param max maximum number of sessions to retain (default = 1000)
16
+ */
17
+ export declare const createLRUStore: <T extends Transport>(max?: number) => SessionStore<T>;
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Minimal Map‑based LRU implementation that fulfils {@link SessionStore}.
3
+ * Keeps at most `max` transports; upon insert, the least‑recently‑used entry
4
+ * (oldest insertion order) is removed and the evicted transport is closed.
5
+ *
6
+ * @param max maximum number of sessions to retain (default = 1000)
7
+ */
8
+ export const createLRUStore = (max = 1000) => {
9
+ // ECMA‑262 §23.1.3.13 - the order of keys in a Map object is the order of insertion; operations that remove a key drop it from that order, and set appends when the key is new or has just been removed.
10
+ const cache = new Map();
11
+ return {
12
+ get: (id) => {
13
+ const t = cache.get(id);
14
+ if (!t)
15
+ return undefined;
16
+ // refresh position
17
+ cache.delete(id);
18
+ cache.set(id, t);
19
+ return t;
20
+ },
21
+ set: (id, transport) => {
22
+ if (cache.has(id)) {
23
+ // key already present - refresh position
24
+ cache.delete(id);
25
+ }
26
+ else if (cache.size >= max) {
27
+ // evict oldest entry (first in insertion order)
28
+ const [lruId, lruTransport] = cache.entries().next().value;
29
+ lruTransport.close?.();
30
+ cache.delete(lruId);
31
+ }
32
+ cache.set(id, transport);
33
+ },
34
+ delete: (id) => cache.delete(id),
35
+ };
36
+ };
@@ -1,4 +1,6 @@
1
+ import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
1
2
  import type { Server } from "@modelcontextprotocol/sdk/server/index.js";
3
+ import { type SessionStore } from "./session.js";
2
4
  /**
3
5
  * Arguments when we create a new instance of your server
4
6
  */
@@ -7,12 +9,21 @@ export interface CreateServerArg<T = Record<string, unknown>> {
7
9
  config: T;
8
10
  }
9
11
  export type CreateServerFn<T = Record<string, unknown>> = (arg: CreateServerArg<T>) => Server;
12
+ /**
13
+ * Configuration options for the stateful server
14
+ */
15
+ export interface StatefulServerOptions {
16
+ /**
17
+ * Session store to use for managing active sessions
18
+ */
19
+ sessionStore?: SessionStore<StreamableHTTPServerTransport>;
20
+ }
10
21
  /**
11
22
  * Creates a stateful server for handling MCP requests.
12
23
  * For every new session, we invoke createMcpServer to create a new instance of the server.
13
24
  * @param createMcpServer Function to create an MCP server
14
25
  * @returns Express app
15
26
  */
16
- export declare function createStatefulServer<T = Record<string, unknown>>(createMcpServer: CreateServerFn<T>): {
27
+ export declare function createStatefulServer<T = Record<string, unknown>>(createMcpServer: CreateServerFn<T>, options?: StatefulServerOptions): {
17
28
  app: import("express-serve-static-core").Express;
18
29
  };
@@ -1,27 +1,28 @@
1
+ import { randomUUID } from "node:crypto";
1
2
  import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
2
3
  import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
3
4
  import express from "express";
4
- import { randomUUID } from "node:crypto";
5
5
  import { parseExpressRequestConfig } from "../config.js";
6
+ import { createLRUStore } from "./session.js";
6
7
  /**
7
8
  * Creates a stateful server for handling MCP requests.
8
9
  * For every new session, we invoke createMcpServer to create a new instance of the server.
9
10
  * @param createMcpServer Function to create an MCP server
10
11
  * @returns Express app
11
12
  */
12
- export function createStatefulServer(createMcpServer) {
13
+ export function createStatefulServer(createMcpServer, options) {
13
14
  const app = express();
14
15
  app.use(express.json());
15
- // Map to store transports by session ID
16
- const transports = {};
16
+ const sessionStore = options?.sessionStore ?? createLRUStore();
17
17
  // Handle POST requests for client-to-server communication
18
18
  app.post("/mcp", async (req, res) => {
19
19
  // Check for existing session ID
20
20
  const sessionId = req.headers["mcp-session-id"];
21
21
  let transport;
22
- if (sessionId && transports[sessionId]) {
22
+ if (sessionId && sessionStore.get(sessionId)) {
23
23
  // Reuse existing transport
24
- transport = transports[sessionId];
24
+ // biome-ignore lint/style/noNonNullAssertion: Not possible
25
+ transport = sessionStore.get(sessionId);
25
26
  }
26
27
  else if (!sessionId && isInitializeRequest(req.body)) {
27
28
  // New initialization request
@@ -30,13 +31,13 @@ export function createStatefulServer(createMcpServer) {
30
31
  sessionIdGenerator: () => newSessionId,
31
32
  onsessioninitialized: (sessionId) => {
32
33
  // Store the transport by session ID
33
- transports[sessionId] = transport;
34
+ sessionStore.set(sessionId, transport);
34
35
  },
35
36
  });
36
37
  // Clean up transport when closed
37
38
  transport.onclose = () => {
38
39
  if (transport.sessionId) {
39
- delete transports[transport.sessionId];
40
+ sessionStore.delete?.(transport.sessionId);
40
41
  }
41
42
  };
42
43
  let config;
@@ -81,7 +82,7 @@ export function createStatefulServer(createMcpServer) {
81
82
  jsonrpc: "2.0",
82
83
  error: {
83
84
  code: -32000,
84
- message: "Bad Request: No valid session ID provided",
85
+ message: "Bad Request: No valid session ID provided. Session may have expired.",
85
86
  },
86
87
  id: null,
87
88
  });
@@ -93,11 +94,12 @@ export function createStatefulServer(createMcpServer) {
93
94
  // Reusable handler for GET and DELETE requests
94
95
  const handleSessionRequest = async (req, res) => {
95
96
  const sessionId = req.headers["mcp-session-id"];
96
- if (!sessionId || !transports[sessionId]) {
97
- res.status(400).send("Invalid or missing session ID");
97
+ if (!sessionId || !sessionStore.get(sessionId)) {
98
+ res.status(400).send("Invalid or expired session ID");
98
99
  return;
99
100
  }
100
- const transport = transports[sessionId];
101
+ // biome-ignore lint/style/noNonNullAssertion: Not possible
102
+ const transport = sessionStore.get(sessionId);
101
103
  await transport.handleRequest(req, res);
102
104
  };
103
105
  // Handle GET requests for server-to-client notifications via SSE
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Patches a function on an object
3
+ * @param obj
4
+ * @param key
5
+ * @param patcher
6
+ */
7
+ export declare function patch<T extends {
8
+ [P in K]: (...args: any[]) => any;
9
+ }, K extends keyof T & string>(obj: T, key: K, patcher: (fn: T[K]) => T[K]): void;
10
+ export declare function patch<T extends {
11
+ [P in K]?: (...args: any[]) => any;
12
+ }, K extends keyof T & string>(obj: T, key: K, patcher: (fn?: T[K]) => T[K]): void;
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Patches a function on an object
3
+ * @param obj
4
+ * @param key
5
+ * @param patcher
6
+ */
7
+ // Unified implementation (not type-checked by callers)
8
+ export function patch(obj, key, patcher) {
9
+ // If the property is actually a function, bind it; otherwise undefined
10
+ const original = typeof obj[key] === "function" ? obj[key].bind(obj) : undefined;
11
+ obj[key] = patcher(original);
12
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@smithery/sdk",
3
- "version": "1.2.3",
3
+ "version": "1.3.1",
4
4
  "description": "SDK to develop with Smithery",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -22,12 +22,15 @@
22
22
  "dependencies": {
23
23
  "@anthropic-ai/sdk": "^0.32.1",
24
24
  "@modelcontextprotocol/sdk": "^1.10.2",
25
+ "ai": "^4.3.15",
25
26
  "express": "^5.1.0",
27
+ "json-schema": "^0.4.0",
26
28
  "openai": "^4.0.0",
27
29
  "uuid": "^11.0.3"
28
30
  },
29
31
  "devDependencies": {
30
32
  "@types/express": "^5.0.1",
33
+ "@types/json-schema": "^7.0.15",
31
34
  "@types/node": "^20.0.0",
32
35
  "@types/uuid": "^9.0.7",
33
36
  "dotenv": "^16.4.7",