@smithery/sdk 1.3.4 → 1.4.2

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.
@@ -1,4 +1,6 @@
1
1
  import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
2
+ import express from "express";
3
+ import type { z } from "zod";
2
4
  import type { Server } from "@modelcontextprotocol/sdk/server/index.js";
3
5
  import { type SessionStore } from "./session.js";
4
6
  /**
@@ -12,18 +14,27 @@ export type CreateServerFn<T = Record<string, unknown>> = (arg: CreateServerArg<
12
14
  /**
13
15
  * Configuration options for the stateful server
14
16
  */
15
- export interface StatefulServerOptions {
17
+ export interface StatefulServerOptions<T = Record<string, unknown>> {
16
18
  /**
17
19
  * Session store to use for managing active sessions
18
20
  */
19
21
  sessionStore?: SessionStore<StreamableHTTPServerTransport>;
22
+ /**
23
+ * Zod schema for config validation
24
+ */
25
+ schema?: z.ZodSchema<T>;
26
+ /**
27
+ * Express app instance to use (optional)
28
+ */
29
+ app?: express.Application;
20
30
  }
21
31
  /**
22
32
  * Creates a stateful server for handling MCP requests.
23
33
  * For every new session, we invoke createMcpServer to create a new instance of the server.
24
34
  * @param createMcpServer Function to create an MCP server
35
+ * @param options Configuration options including optional schema validation and Express app
25
36
  * @returns Express app
26
37
  */
27
- export declare function createStatefulServer<T = Record<string, unknown>>(createMcpServer: CreateServerFn<T>, options?: StatefulServerOptions): {
28
- app: import("express-serve-static-core").Express;
38
+ export declare function createStatefulServer<T = Record<string, unknown>>(createMcpServer: CreateServerFn<T>, options?: StatefulServerOptions<T>): {
39
+ app: express.Application;
29
40
  };
@@ -2,16 +2,17 @@ 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 and Express app
11
12
  * @returns Express app
12
13
  */
13
14
  export function createStatefulServer(createMcpServer, options) {
14
- const app = express();
15
+ const app = options?.app ?? express();
15
16
  app.use(express.json());
16
17
  const sessionStore = options?.sessionStore ?? createLRUStore();
17
18
  // Handle POST requests for client-to-server communication
@@ -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,5 @@
1
+ import express from "express";
2
+ import type { z } from "zod";
1
3
  import type { Server } from "@modelcontextprotocol/sdk/server/index.js";
2
4
  /**
3
5
  * Arguments when we create a new instance of your server
@@ -6,12 +8,17 @@ export interface CreateServerArg<T = Record<string, unknown>> {
6
8
  config: T;
7
9
  }
8
10
  export type CreateServerFn<T = Record<string, unknown>> = (arg: CreateServerArg<T>) => Server;
11
+ export interface CreateStatelessServerOptions<T> {
12
+ schema?: z.ZodSchema<T>;
13
+ app?: express.Application;
14
+ }
9
15
  /**
10
16
  * Creates a stateless server for handling MCP requests
11
17
  * In stateless mode, each request creates a new server and transport instance
12
18
  * @param createMcpServer Function to create an MCP server
19
+ * @param options Optional configuration including Zod schema for validation
13
20
  * @returns Express app
14
21
  */
15
- export declare function createStatelessServer<T = Record<string, unknown>>(createMcpServer: CreateServerFn<T>): {
16
- app: import("express-serve-static-core").Express;
22
+ export declare function createStatelessServer<T = Record<string, unknown>>(createMcpServer: CreateServerFn<T>, options?: CreateStatelessServerOptions<T>): {
23
+ app: express.Application;
17
24
  };
@@ -1,38 +1,29 @@
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
- const app = express();
11
+ export function createStatelessServer(createMcpServer, options) {
12
+ const app = options?.app ?? express();
12
13
  app.use(express.json());
13
14
  app.post("/mcp", async (req, res) => {
14
15
  // In stateless mode, create a new instance of transport and server for each request
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.4",
3
+ "version": "1.4.2",
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",