@smithery/sdk 1.7.2 → 1.7.3
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.d.ts +11 -0
- package/dist/index.js +19 -0
- package/dist/openai/index.d.ts +2 -0
- package/dist/openai/index.js +1 -0
- package/dist/openai/widget.d.ts +67 -0
- package/dist/openai/widget.js +126 -0
- package/dist/react/ErrorBoundary.d.ts +18 -0
- package/dist/react/ErrorBoundary.js +28 -0
- package/dist/react/hooks.d.ts +32 -0
- package/dist/react/hooks.js +135 -0
- package/dist/react/index.d.ts +3 -0
- package/dist/react/index.js +2 -0
- package/dist/react/types.d.ts +66 -0
- package/dist/react/types.js +1 -0
- package/dist/server/auth/identity.d.ts +18 -0
- package/dist/server/auth/identity.js +55 -0
- package/dist/server/auth/oauth.d.ts +21 -0
- package/dist/server/auth/oauth.js +155 -0
- package/dist/server/index.d.ts +7 -0
- package/dist/server/index.js +6 -0
- package/dist/server/logger.d.ts +19 -0
- package/dist/server/logger.js +76 -0
- package/dist/server/session.d.ts +17 -0
- package/dist/server/session.js +36 -0
- package/dist/server/stateful.d.ts +48 -0
- package/dist/server/stateful.js +176 -0
- package/dist/server/stateless.d.ts +46 -0
- package/dist/server/stateless.js +123 -0
- package/dist/server/widget.d.ts +6 -0
- package/dist/server/widget.js +7 -0
- package/dist/shared/config.d.ts +42 -0
- package/dist/shared/config.js +133 -0
- package/dist/shared/patch.d.ts +12 -0
- package/dist/shared/patch.js +12 -0
- package/package.json +3 -2
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
2
|
+
import express from "express";
|
|
3
|
+
import { parseAndValidateConfig } from "../shared/config.js";
|
|
4
|
+
import { zodToJsonSchema } from "zod-to-json-schema";
|
|
5
|
+
import { createLogger } from "./logger.js";
|
|
6
|
+
/**
|
|
7
|
+
* Creates a stateless server for handling MCP requests.
|
|
8
|
+
* Each request creates a new server instance - no session state is maintained.
|
|
9
|
+
* This is ideal for stateless API integrations and serverless environments.
|
|
10
|
+
*
|
|
11
|
+
* @param createMcpServer Function to create an MCP server
|
|
12
|
+
* @param options Configuration options including optional schema validation and Express app
|
|
13
|
+
* @returns Express app
|
|
14
|
+
*/
|
|
15
|
+
export function createStatelessServer(createMcpServer, options) {
|
|
16
|
+
const app = options?.app ?? express();
|
|
17
|
+
const logger = createLogger(options?.logLevel ?? "info");
|
|
18
|
+
app.use("/mcp", express.json());
|
|
19
|
+
// Handle POST requests for client-to-server communication
|
|
20
|
+
app.post("/mcp", async (req, res) => {
|
|
21
|
+
// In stateless mode, create a new instance of transport and server for each request
|
|
22
|
+
// to ensure complete isolation. A single instance would cause request ID collisions
|
|
23
|
+
// when multiple clients connect concurrently.
|
|
24
|
+
try {
|
|
25
|
+
// Log incoming MCP request
|
|
26
|
+
logger.debug({
|
|
27
|
+
method: req.body.method,
|
|
28
|
+
id: req.body.id,
|
|
29
|
+
params: req.body.params,
|
|
30
|
+
}, "MCP Request");
|
|
31
|
+
// Validate config for all requests in stateless mode
|
|
32
|
+
const configResult = parseAndValidateConfig(req, options?.schema);
|
|
33
|
+
if (!configResult.ok) {
|
|
34
|
+
const status = configResult.error.status || 400;
|
|
35
|
+
logger.error({ error: configResult.error }, "Config validation failed");
|
|
36
|
+
res.status(status).json(configResult.error);
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
const config = configResult.value;
|
|
40
|
+
// Create a fresh server instance for each request
|
|
41
|
+
const server = createMcpServer({
|
|
42
|
+
config,
|
|
43
|
+
auth: req.auth,
|
|
44
|
+
logger,
|
|
45
|
+
});
|
|
46
|
+
// Create a new transport for this request (no session management)
|
|
47
|
+
const transport = new StreamableHTTPServerTransport({
|
|
48
|
+
sessionIdGenerator: undefined,
|
|
49
|
+
});
|
|
50
|
+
// Clean up resources when request closes
|
|
51
|
+
res.on("close", () => {
|
|
52
|
+
transport.close();
|
|
53
|
+
server.close();
|
|
54
|
+
});
|
|
55
|
+
// Connect to the MCP server
|
|
56
|
+
await server.connect(transport);
|
|
57
|
+
// Handle the request directly
|
|
58
|
+
await transport.handleRequest(req, res, req.body);
|
|
59
|
+
// Log successful response
|
|
60
|
+
logger.debug({
|
|
61
|
+
method: req.body.method,
|
|
62
|
+
id: req.body.id,
|
|
63
|
+
}, "MCP Response sent");
|
|
64
|
+
}
|
|
65
|
+
catch (error) {
|
|
66
|
+
logger.error({ error }, "Error handling MCP request");
|
|
67
|
+
if (!res.headersSent) {
|
|
68
|
+
res.status(500).json({
|
|
69
|
+
jsonrpc: "2.0",
|
|
70
|
+
error: {
|
|
71
|
+
code: -32603,
|
|
72
|
+
message: "Internal server error",
|
|
73
|
+
},
|
|
74
|
+
id: null,
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
// SSE notifications not supported in stateless mode
|
|
80
|
+
app.get("/mcp", async (_req, res) => {
|
|
81
|
+
res.status(405).json({
|
|
82
|
+
jsonrpc: "2.0",
|
|
83
|
+
error: {
|
|
84
|
+
code: -32000,
|
|
85
|
+
message: "Method not allowed.",
|
|
86
|
+
},
|
|
87
|
+
id: null,
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
// Session termination not needed in stateless mode
|
|
91
|
+
app.delete("/mcp", async (_req, res) => {
|
|
92
|
+
res.status(405).json({
|
|
93
|
+
jsonrpc: "2.0",
|
|
94
|
+
error: {
|
|
95
|
+
code: -32000,
|
|
96
|
+
message: "Method not allowed.",
|
|
97
|
+
},
|
|
98
|
+
id: null,
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
// Add .well-known/mcp-config endpoint for configuration discovery
|
|
102
|
+
app.get("/.well-known/mcp-config", (req, res) => {
|
|
103
|
+
// Set proper content type for JSON Schema
|
|
104
|
+
res.set("Content-Type", "application/schema+json; charset=utf-8");
|
|
105
|
+
const baseSchema = options?.schema
|
|
106
|
+
? zodToJsonSchema(options.schema)
|
|
107
|
+
: {
|
|
108
|
+
type: "object",
|
|
109
|
+
properties: {},
|
|
110
|
+
required: [],
|
|
111
|
+
};
|
|
112
|
+
const configSchema = {
|
|
113
|
+
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
114
|
+
$id: `${req.protocol}://${req.get("host")}/.well-known/mcp-config`,
|
|
115
|
+
title: "MCP Session Configuration",
|
|
116
|
+
description: "Schema for the /mcp endpoint configuration",
|
|
117
|
+
"x-query-style": "dot+bracket",
|
|
118
|
+
...baseSchema,
|
|
119
|
+
};
|
|
120
|
+
res.json(configSchema);
|
|
121
|
+
});
|
|
122
|
+
return { app };
|
|
123
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { Request as ExpressRequest } from "express";
|
|
2
|
+
import type { z } from "zod";
|
|
3
|
+
export interface SmitheryUrlOptions {
|
|
4
|
+
apiKey?: string;
|
|
5
|
+
profile?: string;
|
|
6
|
+
config?: object;
|
|
7
|
+
}
|
|
8
|
+
export declare function appendConfigAsDotParams(url: URL, config: unknown): void;
|
|
9
|
+
/**
|
|
10
|
+
* Creates a URL to connect to the Smithery MCP server.
|
|
11
|
+
* @param baseUrl The base URL of the Smithery server
|
|
12
|
+
* @param options Optional configuration object
|
|
13
|
+
* @returns A URL with config encoded using dot-notation query params (e.g. model.name=gpt-4&debug=true)
|
|
14
|
+
*/
|
|
15
|
+
export declare function createSmitheryUrl(baseUrl: string, options?: SmitheryUrlOptions): URL;
|
|
16
|
+
/**
|
|
17
|
+
* Parses and validates config from an Express request with optional Zod schema validation
|
|
18
|
+
* Supports dot-notation config parameters (e.g., foo=bar, a.b=c)
|
|
19
|
+
* @param req The express request
|
|
20
|
+
* @param schema Optional Zod schema for validation
|
|
21
|
+
* @returns Result with either parsed data or error response
|
|
22
|
+
*/
|
|
23
|
+
export declare function parseAndValidateConfig<T = Record<string, unknown>>(req: ExpressRequest, schema?: z.ZodSchema<T>): import("okay-error").Err<{
|
|
24
|
+
readonly title: "Invalid configuration parameters";
|
|
25
|
+
readonly status: 422;
|
|
26
|
+
readonly detail: "One or more config parameters are invalid.";
|
|
27
|
+
readonly instance: string;
|
|
28
|
+
readonly configSchema: import("zod-to-json-schema").JsonSchema7Type & {
|
|
29
|
+
$schema?: string | undefined;
|
|
30
|
+
definitions?: {
|
|
31
|
+
[key: string]: import("zod-to-json-schema").JsonSchema7Type;
|
|
32
|
+
} | undefined;
|
|
33
|
+
};
|
|
34
|
+
readonly errors: {
|
|
35
|
+
param: string;
|
|
36
|
+
pointer: string;
|
|
37
|
+
reason: string;
|
|
38
|
+
received: unknown;
|
|
39
|
+
}[];
|
|
40
|
+
readonly help: "Pass config as URL query params. Example: /mcp?param1=value1¶m2=value2";
|
|
41
|
+
}> | import("okay-error").Ok<T>;
|
|
42
|
+
export declare function parseConfigFromQuery(query: Iterable<[string, unknown]>): Record<string, unknown>;
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import _ from "lodash";
|
|
2
|
+
import { err, ok } from "okay-error";
|
|
3
|
+
import { zodToJsonSchema } from "zod-to-json-schema";
|
|
4
|
+
function isPlainObject(value) {
|
|
5
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
6
|
+
}
|
|
7
|
+
export function appendConfigAsDotParams(url, config) {
|
|
8
|
+
function add(pathParts, value) {
|
|
9
|
+
if (Array.isArray(value)) {
|
|
10
|
+
for (let index = 0; index < value.length; index++) {
|
|
11
|
+
add([...pathParts, String(index)], value[index]);
|
|
12
|
+
}
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
if (isPlainObject(value)) {
|
|
16
|
+
for (const [key, nested] of Object.entries(value)) {
|
|
17
|
+
add([...pathParts, key], nested);
|
|
18
|
+
}
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
const key = pathParts.join(".");
|
|
22
|
+
let stringValue;
|
|
23
|
+
switch (typeof value) {
|
|
24
|
+
case "string":
|
|
25
|
+
stringValue = value;
|
|
26
|
+
break;
|
|
27
|
+
case "number":
|
|
28
|
+
case "boolean":
|
|
29
|
+
stringValue = String(value);
|
|
30
|
+
break;
|
|
31
|
+
default:
|
|
32
|
+
stringValue = JSON.stringify(value);
|
|
33
|
+
}
|
|
34
|
+
url.searchParams.set(key, stringValue);
|
|
35
|
+
}
|
|
36
|
+
if (isPlainObject(config)) {
|
|
37
|
+
for (const [key, value] of Object.entries(config)) {
|
|
38
|
+
add([key], value);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Creates a URL to connect to the Smithery MCP server.
|
|
44
|
+
* @param baseUrl The base URL of the Smithery server
|
|
45
|
+
* @param options Optional configuration object
|
|
46
|
+
* @returns A URL with config encoded using dot-notation query params (e.g. model.name=gpt-4&debug=true)
|
|
47
|
+
*/
|
|
48
|
+
export function createSmitheryUrl(baseUrl, options) {
|
|
49
|
+
const url = new URL(`${baseUrl}/mcp`);
|
|
50
|
+
if (options?.config) {
|
|
51
|
+
appendConfigAsDotParams(url, options.config);
|
|
52
|
+
}
|
|
53
|
+
if (options?.apiKey) {
|
|
54
|
+
url.searchParams.set("api_key", options.apiKey);
|
|
55
|
+
}
|
|
56
|
+
if (options?.profile) {
|
|
57
|
+
url.searchParams.set("profile", options.profile);
|
|
58
|
+
}
|
|
59
|
+
return url;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Parses and validates config from an Express request with optional Zod schema validation
|
|
63
|
+
* Supports dot-notation config parameters (e.g., foo=bar, a.b=c)
|
|
64
|
+
* @param req The express request
|
|
65
|
+
* @param schema Optional Zod schema for validation
|
|
66
|
+
* @returns Result with either parsed data or error response
|
|
67
|
+
*/
|
|
68
|
+
export function parseAndValidateConfig(req, schema) {
|
|
69
|
+
const config = parseConfigFromQuery(Object.entries(req.query));
|
|
70
|
+
// Validate config against schema if provided
|
|
71
|
+
if (schema) {
|
|
72
|
+
const result = schema.safeParse(config);
|
|
73
|
+
if (!result.success) {
|
|
74
|
+
const jsonSchema = zodToJsonSchema(schema);
|
|
75
|
+
const errors = result.error.issues.map(issue => {
|
|
76
|
+
// Safely traverse the config object to get the received value
|
|
77
|
+
let received = config;
|
|
78
|
+
for (const key of issue.path) {
|
|
79
|
+
if (received && typeof received === "object" && key in received) {
|
|
80
|
+
received = received[key];
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
received = undefined;
|
|
84
|
+
break;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return {
|
|
88
|
+
param: issue.path.join(".") || "root",
|
|
89
|
+
pointer: `/${issue.path.join("/")}`,
|
|
90
|
+
reason: issue.message,
|
|
91
|
+
received,
|
|
92
|
+
};
|
|
93
|
+
});
|
|
94
|
+
return err({
|
|
95
|
+
title: "Invalid configuration parameters",
|
|
96
|
+
status: 422,
|
|
97
|
+
detail: "One or more config parameters are invalid.",
|
|
98
|
+
instance: req.originalUrl,
|
|
99
|
+
configSchema: jsonSchema,
|
|
100
|
+
errors,
|
|
101
|
+
help: "Pass config as URL query params. Example: /mcp?param1=value1¶m2=value2",
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
return ok(result.data);
|
|
105
|
+
}
|
|
106
|
+
return ok(config);
|
|
107
|
+
}
|
|
108
|
+
// Process dot-notation config parameters from query parameters (foo=bar, a.b=c)
|
|
109
|
+
// This allows URL params like ?server.host=localhost&server.port=8080&debug=true
|
|
110
|
+
export function parseConfigFromQuery(query) {
|
|
111
|
+
const config = {};
|
|
112
|
+
for (const [key, value] of query) {
|
|
113
|
+
// Skip reserved parameters
|
|
114
|
+
if (key === "api_key" || key === "profile")
|
|
115
|
+
continue;
|
|
116
|
+
const pathParts = key.split(".");
|
|
117
|
+
// Handle array values from Express query parsing
|
|
118
|
+
const rawValue = Array.isArray(value) ? value[0] : value;
|
|
119
|
+
if (typeof rawValue !== "string")
|
|
120
|
+
continue;
|
|
121
|
+
// Try to parse value as JSON (for booleans, numbers, objects)
|
|
122
|
+
let parsedValue = rawValue;
|
|
123
|
+
try {
|
|
124
|
+
parsedValue = JSON.parse(rawValue);
|
|
125
|
+
}
|
|
126
|
+
catch {
|
|
127
|
+
// If parsing fails, use the raw string value
|
|
128
|
+
}
|
|
129
|
+
// Use lodash's set method to handle nested paths
|
|
130
|
+
_.set(config, pathParts, parsedValue);
|
|
131
|
+
}
|
|
132
|
+
return config;
|
|
133
|
+
}
|
|
@@ -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.7.
|
|
3
|
+
"version": "1.7.3",
|
|
4
4
|
"description": "SDK to develop with Smithery",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": {
|
|
@@ -39,7 +39,8 @@
|
|
|
39
39
|
"build": "tsc",
|
|
40
40
|
"build:all": "npm run build -ws --include-workspace-root",
|
|
41
41
|
"watch": "tsc --watch",
|
|
42
|
-
"check": "npx @biomejs/biome check --write --unsafe"
|
|
42
|
+
"check": "npx @biomejs/biome check --write --unsafe",
|
|
43
|
+
"prepare": "npm run build"
|
|
43
44
|
},
|
|
44
45
|
"packageManager": "npm@11.4.1",
|
|
45
46
|
"license": "MIT",
|