@smithery/sdk 1.3.3 → 1.4.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
@@ -19,7 +19,7 @@ import { createStatelessServer } from '@smithery/sdk/server/stateless.js'
19
19
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"
20
20
 
21
21
  // Create your MCP server function
22
- function createMcpServer({ sessionId, config }) {
22
+ function createMcpServer({ config }) {
23
23
  // Create and return a server instance
24
24
  // https://github.com/modelcontextprotocol/typescript-sdk?tab=readme-ov-file#core-concepts
25
25
  const mcpServer = new McpServer({
@@ -33,13 +33,9 @@ function createMcpServer({ sessionId, config }) {
33
33
  }
34
34
 
35
35
  // Create the stateless server using your MCP server function.
36
- const { app } = createStatelessServer(createMcpServer)
37
-
38
- // Start the server
39
- const PORT = process.env.PORT || 8081
40
- app.listen(PORT, () => {
41
- console.log(`MCP server running on port ${PORT}`)
42
- })
36
+ createStatelessServer(createMcpServer)
37
+ .app
38
+ .listen(process.env.PORT || 3000)
43
39
  ```
44
40
 
45
41
  This example:
@@ -1,4 +1,5 @@
1
1
  import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
2
+ import type { z } from "zod";
2
3
  import type { Server } from "@modelcontextprotocol/sdk/server/index.js";
3
4
  import { type SessionStore } from "./session.js";
4
5
  /**
@@ -12,18 +13,23 @@ export type CreateServerFn<T = Record<string, unknown>> = (arg: CreateServerArg<
12
13
  /**
13
14
  * Configuration options for the stateful server
14
15
  */
15
- export interface StatefulServerOptions {
16
+ export interface StatefulServerOptions<T = Record<string, unknown>> {
16
17
  /**
17
18
  * Session store to use for managing active sessions
18
19
  */
19
20
  sessionStore?: SessionStore<StreamableHTTPServerTransport>;
21
+ /**
22
+ * Zod schema for config validation
23
+ */
24
+ schema?: z.ZodSchema<T>;
20
25
  }
21
26
  /**
22
27
  * Creates a stateful server for handling MCP requests.
23
28
  * For every new session, we invoke createMcpServer to create a new instance of the server.
24
29
  * @param createMcpServer Function to create an MCP server
30
+ * @param options Configuration options including optional schema validation
25
31
  * @returns Express app
26
32
  */
27
- export declare function createStatefulServer<T = Record<string, unknown>>(createMcpServer: CreateServerFn<T>, options?: StatefulServerOptions): {
33
+ export declare function createStatefulServer<T = Record<string, unknown>>(createMcpServer: CreateServerFn<T>, options?: StatefulServerOptions<T>): {
28
34
  app: import("express-serve-static-core").Express;
29
35
  };
@@ -2,12 +2,13 @@ import { randomUUID } from "node:crypto";
2
2
  import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
3
3
  import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
4
4
  import express from "express";
5
- import { parseExpressRequestConfig } from "../shared/config.js";
5
+ import { parseAndValidateConfig } from "../shared/config.js";
6
6
  import { createLRUStore } from "./session.js";
7
7
  /**
8
8
  * Creates a stateful server for handling MCP requests.
9
9
  * For every new session, we invoke createMcpServer to create a new instance of the server.
10
10
  * @param createMcpServer Function to create an MCP server
11
+ * @param options Configuration options including optional schema validation
11
12
  * @returns Express app
12
13
  */
13
14
  export function createStatefulServer(createMcpServer, options) {
@@ -40,21 +41,14 @@ export function createStatefulServer(createMcpServer, options) {
40
41
  sessionStore.delete?.(transport.sessionId);
41
42
  }
42
43
  };
43
- let config;
44
- try {
45
- config = parseExpressRequestConfig(req);
46
- }
47
- catch (error) {
48
- res.status(400).json({
49
- jsonrpc: "2.0",
50
- error: {
51
- code: -32000,
52
- message: "Bad Request: Invalid configuration",
53
- },
54
- id: null,
55
- });
44
+ // New session - validate config
45
+ const configResult = parseAndValidateConfig(req, options?.schema);
46
+ if (!configResult.ok) {
47
+ const status = configResult.error.status || 400;
48
+ res.status(status).json(configResult.error);
56
49
  return;
57
50
  }
51
+ const config = configResult.value;
58
52
  try {
59
53
  const server = createMcpServer({
60
54
  sessionId: newSessionId,
@@ -1,3 +1,4 @@
1
+ import type { z } from "zod";
1
2
  import type { Server } from "@modelcontextprotocol/sdk/server/index.js";
2
3
  /**
3
4
  * Arguments when we create a new instance of your server
@@ -6,12 +7,16 @@ export interface CreateServerArg<T = Record<string, unknown>> {
6
7
  config: T;
7
8
  }
8
9
  export type CreateServerFn<T = Record<string, unknown>> = (arg: CreateServerArg<T>) => Server;
10
+ export interface CreateStatelessServerOptions<T> {
11
+ schema?: z.ZodSchema<T>;
12
+ }
9
13
  /**
10
14
  * Creates a stateless server for handling MCP requests
11
15
  * In stateless mode, each request creates a new server and transport instance
12
16
  * @param createMcpServer Function to create an MCP server
17
+ * @param options Optional configuration including Zod schema for validation
13
18
  * @returns Express app
14
19
  */
15
- export declare function createStatelessServer<T = Record<string, unknown>>(createMcpServer: CreateServerFn<T>): {
20
+ export declare function createStatelessServer<T = Record<string, unknown>>(createMcpServer: CreateServerFn<T>, options?: CreateStatelessServerOptions<T>): {
16
21
  app: import("express-serve-static-core").Express;
17
22
  };
@@ -1,13 +1,14 @@
1
- import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
2
1
  import express from "express";
3
- import { parseExpressRequestConfig } from "../shared/config.js";
2
+ import { parseAndValidateConfig } from "../shared/config.js";
3
+ import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
4
4
  /**
5
5
  * Creates a stateless server for handling MCP requests
6
6
  * In stateless mode, each request creates a new server and transport instance
7
7
  * @param createMcpServer Function to create an MCP server
8
+ * @param options Optional configuration including Zod schema for validation
8
9
  * @returns Express app
9
10
  */
10
- export function createStatelessServer(createMcpServer) {
11
+ export function createStatelessServer(createMcpServer, options) {
11
12
  const app = express();
12
13
  app.use(express.json());
13
14
  app.post("/mcp", async (req, res) => {
@@ -15,24 +16,14 @@ export function createStatelessServer(createMcpServer) {
15
16
  // to ensure complete isolation. A single instance would cause request ID collisions
16
17
  // when multiple clients connect concurrently.
17
18
  try {
18
- // Parse base64 encoded config from URL query parameter if present
19
- let config = {};
20
- if (req.query.config) {
21
- try {
22
- config = parseExpressRequestConfig(req);
23
- }
24
- catch (configError) {
25
- res.status(400).json({
26
- jsonrpc: "2.0",
27
- error: {
28
- code: -32000,
29
- message: "Bad Request: Invalid configuration",
30
- },
31
- id: null,
32
- });
33
- return;
34
- }
19
+ // Parse and validate config
20
+ const configResult = parseAndValidateConfig(req, options?.schema);
21
+ if (!configResult.ok) {
22
+ const status = configResult.error.status;
23
+ res.status(status).json(configResult.error);
24
+ return;
35
25
  }
26
+ const config = configResult.value;
36
27
  // Create a new server instance with config
37
28
  const server = createMcpServer({ config: config });
38
29
  // Create a new transport instance
@@ -1,4 +1,5 @@
1
- import type express from "express";
1
+ import type { Request as ExpressRequest } from "express";
2
+ import type { z } from "zod";
2
3
  export interface SmitheryUrlOptions {
3
4
  apiKey?: string;
4
5
  profile?: string;
@@ -16,4 +17,34 @@ export declare function createSmitheryUrl(baseUrl: string, options?: SmitheryUrl
16
17
  * @param req The express request
17
18
  * @returns The config
18
19
  */
19
- export declare function parseExpressRequestConfig(req: express.Request): Record<string, unknown>;
20
+ export declare function parseExpressRequestConfig(req: ExpressRequest): Record<string, unknown>;
21
+ /**
22
+ * Parses and validates config from an Express request with optional Zod schema validation
23
+ * Supports both base64-encoded config and dot-notation config parameters
24
+ * @param req The express request
25
+ * @param schema Optional Zod schema for validation
26
+ * @returns Result with either parsed data or error response
27
+ */
28
+ export declare function parseAndValidateConfig<T = Record<string, unknown>>(req: ExpressRequest, schema?: z.ZodSchema<T>): import("okay-error").Err<{
29
+ title: string;
30
+ status: number;
31
+ detail: string;
32
+ instance: string;
33
+ }> | import("okay-error").Err<{
34
+ readonly title: "Invalid configuration parameters";
35
+ readonly status: 422;
36
+ readonly detail: "One or more config parameters are invalid.";
37
+ readonly instance: string;
38
+ readonly configSchema: import("zod-to-json-schema").JsonSchema7Type & {
39
+ $schema?: string | undefined;
40
+ definitions?: {
41
+ [key: string]: import("zod-to-json-schema").JsonSchema7Type;
42
+ } | undefined;
43
+ };
44
+ readonly errors: {
45
+ param: string;
46
+ pointer: string;
47
+ reason: string;
48
+ received: unknown;
49
+ }[];
50
+ }> | import("okay-error").Ok<T>;
@@ -1,3 +1,6 @@
1
+ import _ from "lodash";
2
+ import { err, ok } from "okay-error";
3
+ import { zodToJsonSchema } from "zod-to-json-schema";
1
4
  /**
2
5
  * Creates a URL to connect to the Smithery MCP server.
3
6
  * @param baseUrl The base URL of the Smithery server
@@ -28,3 +31,89 @@ export function createSmitheryUrl(baseUrl, options) {
28
31
  export function parseExpressRequestConfig(req) {
29
32
  return JSON.parse(Buffer.from(req.query.config, "base64").toString());
30
33
  }
34
+ /**
35
+ * Parses and validates config from an Express request with optional Zod schema validation
36
+ * Supports both base64-encoded config and dot-notation config parameters
37
+ * @param req The express request
38
+ * @param schema Optional Zod schema for validation
39
+ * @returns Result with either parsed data or error response
40
+ */
41
+ export function parseAndValidateConfig(req, schema) {
42
+ // Parse config from request parameters
43
+ let config = {};
44
+ // 1. Process base64-encoded config parameter if present
45
+ if (req.query.config) {
46
+ try {
47
+ config = parseExpressRequestConfig(req);
48
+ }
49
+ catch (configError) {
50
+ return err({
51
+ title: "Invalid config parameter",
52
+ status: 400,
53
+ detail: "Failed to parse config parameter",
54
+ instance: req.originalUrl,
55
+ });
56
+ }
57
+ }
58
+ // 2. Process dot-notation config parameters (foo=bar, a.b=c)
59
+ // This allows URL params like ?server.host=localhost&server.port=8080&debug=true
60
+ for (const [key, value] of Object.entries(req.query)) {
61
+ // Skip reserved parameters
62
+ if (key === "config" || key === "api_key" || key === "profile")
63
+ continue;
64
+ const pathParts = key.split(".");
65
+ // Handle array values from Express query parsing
66
+ const rawValue = Array.isArray(value) ? value[0] : value;
67
+ if (typeof rawValue !== "string")
68
+ continue;
69
+ // Try to parse value as JSON (for booleans, numbers, objects)
70
+ let parsedValue = rawValue;
71
+ try {
72
+ parsedValue = JSON.parse(rawValue);
73
+ }
74
+ catch {
75
+ // If parsing fails, use the raw string value
76
+ }
77
+ // Use lodash's set method to handle nested paths
78
+ _.set(config, pathParts, parsedValue);
79
+ }
80
+ // Validate config against schema if provided
81
+ if (schema) {
82
+ const result = schema.safeParse(config);
83
+ if (!result.success) {
84
+ const jsonSchema = zodToJsonSchema(schema, {
85
+ name: "ConfigSchema",
86
+ $refStrategy: "none",
87
+ });
88
+ const errors = result.error.issues.map((issue) => {
89
+ // Safely traverse the config object to get the received value
90
+ let received = config;
91
+ for (const key of issue.path) {
92
+ if (received && typeof received === "object" && key in received) {
93
+ received = received[key];
94
+ }
95
+ else {
96
+ received = undefined;
97
+ break;
98
+ }
99
+ }
100
+ return {
101
+ param: issue.path.join(".") || "root",
102
+ pointer: `/${issue.path.join("/")}`,
103
+ reason: issue.message,
104
+ received,
105
+ };
106
+ });
107
+ return err({
108
+ title: "Invalid configuration parameters",
109
+ status: 422,
110
+ detail: "One or more config parameters are invalid.",
111
+ instance: req.originalUrl,
112
+ configSchema: jsonSchema,
113
+ errors,
114
+ });
115
+ }
116
+ return ok(result.data);
117
+ }
118
+ return ok(config);
119
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@smithery/sdk",
3
- "version": "1.3.3",
3
+ "version": "1.4.1",
4
4
  "description": "SDK to develop with Smithery",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -25,12 +25,17 @@
25
25
  "ai": "^4.3.15",
26
26
  "express": "^5.1.0",
27
27
  "json-schema": "^0.4.0",
28
+ "lodash": "^4.17.21",
29
+ "okay-error": "^1.0.2",
28
30
  "openai": "^4.0.0",
29
- "uuid": "^11.0.3"
31
+ "uuid": "^11.0.3",
32
+ "zod": "^3.23.8",
33
+ "zod-to-json-schema": "^3.24.1"
30
34
  },
31
35
  "devDependencies": {
32
36
  "@types/express": "^5.0.1",
33
37
  "@types/json-schema": "^7.0.15",
38
+ "@types/lodash": "^4.17.17",
34
39
  "@types/node": "^20.0.0",
35
40
  "@types/uuid": "^9.0.7",
36
41
  "dotenv": "^16.4.7",