@smithery/sdk 1.6.7 → 1.6.8
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/server/logger.d.ts +19 -0
- package/dist/server/logger.js +76 -0
- package/dist/server/stateful.d.ts +6 -0
- package/dist/server/stateful.js +22 -1
- package/dist/server/stateless.d.ts +7 -0
- package/dist/server/stateless.js +16 -1
- package/dist/shared/config.d.ts +1 -0
- package/dist/shared/config.js +1 -0
- package/package.json +3 -2
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Logger interface for structured logging
|
|
3
|
+
*/
|
|
4
|
+
export interface Logger {
|
|
5
|
+
info(msg: string, ...args: unknown[]): void;
|
|
6
|
+
info(obj: Record<string, unknown>, msg?: string, ...args: unknown[]): void;
|
|
7
|
+
error(msg: string, ...args: unknown[]): void;
|
|
8
|
+
error(obj: Record<string, unknown>, msg?: string, ...args: unknown[]): void;
|
|
9
|
+
warn(msg: string, ...args: unknown[]): void;
|
|
10
|
+
warn(obj: Record<string, unknown>, msg?: string, ...args: unknown[]): void;
|
|
11
|
+
debug(msg: string, ...args: unknown[]): void;
|
|
12
|
+
debug(obj: Record<string, unknown>, msg?: string, ...args: unknown[]): void;
|
|
13
|
+
}
|
|
14
|
+
type LogLevel = "debug" | "info" | "warn" | "error";
|
|
15
|
+
/**
|
|
16
|
+
* Creates a simple console-based logger with pretty formatting
|
|
17
|
+
*/
|
|
18
|
+
export declare function createLogger(logLevel?: LogLevel): Logger;
|
|
19
|
+
export {};
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
/**
|
|
3
|
+
* Lightweight stringify with depth limiting
|
|
4
|
+
*/
|
|
5
|
+
function stringifyWithDepth(obj, maxDepth = 3) {
|
|
6
|
+
let depth = 0;
|
|
7
|
+
const seen = new WeakSet();
|
|
8
|
+
try {
|
|
9
|
+
return JSON.stringify(obj, (key, value) => {
|
|
10
|
+
// Track depth
|
|
11
|
+
if (key === "")
|
|
12
|
+
depth = 0;
|
|
13
|
+
else if (typeof value === "object" && value !== null)
|
|
14
|
+
depth++;
|
|
15
|
+
// Depth limit
|
|
16
|
+
if (depth > maxDepth) {
|
|
17
|
+
return "[Object]";
|
|
18
|
+
}
|
|
19
|
+
// Circular reference check
|
|
20
|
+
if (typeof value === "object" && value !== null) {
|
|
21
|
+
if (seen.has(value))
|
|
22
|
+
return "[Circular]";
|
|
23
|
+
seen.add(value);
|
|
24
|
+
}
|
|
25
|
+
// Handle special types
|
|
26
|
+
if (typeof value === "function")
|
|
27
|
+
return "[Function]";
|
|
28
|
+
if (typeof value === "bigint")
|
|
29
|
+
return `${value}n`;
|
|
30
|
+
if (value instanceof Error)
|
|
31
|
+
return { name: value.name, message: value.message };
|
|
32
|
+
if (value instanceof Date)
|
|
33
|
+
return value.toISOString();
|
|
34
|
+
return value;
|
|
35
|
+
}, 2);
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
return String(obj);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Creates a simple console-based logger with pretty formatting
|
|
43
|
+
*/
|
|
44
|
+
export function createLogger(logLevel = "info") {
|
|
45
|
+
const levels = { debug: 0, info: 1, warn: 2, error: 3 };
|
|
46
|
+
const currentLevel = levels[logLevel];
|
|
47
|
+
const formatLog = (level, color, msgOrObj, msg) => {
|
|
48
|
+
const time = new Date().toISOString().split("T")[1].split(".")[0];
|
|
49
|
+
const timestamp = chalk.dim(time);
|
|
50
|
+
const levelStr = color(level);
|
|
51
|
+
if (typeof msgOrObj === "string") {
|
|
52
|
+
return `${timestamp} ${levelStr} ${msgOrObj}`;
|
|
53
|
+
}
|
|
54
|
+
const message = msg || "";
|
|
55
|
+
const data = stringifyWithDepth(msgOrObj, 3);
|
|
56
|
+
return `${timestamp} ${levelStr} ${message}\n${chalk.dim(data)}`;
|
|
57
|
+
};
|
|
58
|
+
return {
|
|
59
|
+
debug: (msgOrObj, msg) => {
|
|
60
|
+
if (currentLevel <= 0)
|
|
61
|
+
console.error(formatLog("DEBUG", chalk.cyan, msgOrObj, msg));
|
|
62
|
+
},
|
|
63
|
+
info: (msgOrObj, msg) => {
|
|
64
|
+
if (currentLevel <= 1)
|
|
65
|
+
console.error(formatLog("INFO", chalk.blue, msgOrObj, msg));
|
|
66
|
+
},
|
|
67
|
+
warn: (msgOrObj, msg) => {
|
|
68
|
+
if (currentLevel <= 2)
|
|
69
|
+
console.error(formatLog("WARN", chalk.yellow, msgOrObj, msg));
|
|
70
|
+
},
|
|
71
|
+
error: (msgOrObj, msg) => {
|
|
72
|
+
if (currentLevel <= 3)
|
|
73
|
+
console.error(formatLog("ERROR", chalk.red, msgOrObj, msg));
|
|
74
|
+
},
|
|
75
|
+
};
|
|
76
|
+
}
|
|
@@ -4,6 +4,7 @@ import express from "express";
|
|
|
4
4
|
import type { z } from "zod";
|
|
5
5
|
import type { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
6
6
|
import { type SessionStore } from "./session.js";
|
|
7
|
+
import type { Logger } from "./logger.js";
|
|
7
8
|
/**
|
|
8
9
|
* Arguments when we create a new instance of your server
|
|
9
10
|
*/
|
|
@@ -11,6 +12,7 @@ export interface CreateServerArg<T = Record<string, unknown>> {
|
|
|
11
12
|
sessionId: string;
|
|
12
13
|
config: T;
|
|
13
14
|
auth?: AuthInfo;
|
|
15
|
+
logger: Logger;
|
|
14
16
|
}
|
|
15
17
|
export type CreateServerFn<T = Record<string, unknown>> = (arg: CreateServerArg<T>) => Server;
|
|
16
18
|
/**
|
|
@@ -29,6 +31,10 @@ export interface StatefulServerOptions<T = Record<string, unknown>> {
|
|
|
29
31
|
* Express app instance to use (optional)
|
|
30
32
|
*/
|
|
31
33
|
app?: express.Application;
|
|
34
|
+
/**
|
|
35
|
+
* Log level for the server (default: 'info')
|
|
36
|
+
*/
|
|
37
|
+
logLevel?: "debug" | "info" | "warn" | "error";
|
|
32
38
|
}
|
|
33
39
|
/**
|
|
34
40
|
* Creates a stateful server for handling MCP requests.
|
package/dist/server/stateful.js
CHANGED
|
@@ -5,6 +5,7 @@ import { randomUUID } from "node:crypto";
|
|
|
5
5
|
import { parseAndValidateConfig } from "../shared/config.js";
|
|
6
6
|
import { zodToJsonSchema } from "zod-to-json-schema";
|
|
7
7
|
import { createLRUStore } from "./session.js";
|
|
8
|
+
import { createLogger } from "./logger.js";
|
|
8
9
|
/**
|
|
9
10
|
* Creates a stateful server for handling MCP requests.
|
|
10
11
|
* For every new session, we invoke createMcpServer to create a new instance of the server.
|
|
@@ -16,8 +17,15 @@ export function createStatefulServer(createMcpServer, options) {
|
|
|
16
17
|
const app = options?.app ?? express();
|
|
17
18
|
app.use("/mcp", express.json());
|
|
18
19
|
const sessionStore = options?.sessionStore ?? createLRUStore();
|
|
20
|
+
const logger = createLogger(options?.logLevel ?? "info");
|
|
19
21
|
// Handle POST requests for client-to-server communication
|
|
20
22
|
app.post("/mcp", async (req, res) => {
|
|
23
|
+
// Log incoming MCP request
|
|
24
|
+
logger.debug({
|
|
25
|
+
method: req.body.method,
|
|
26
|
+
id: req.body.id,
|
|
27
|
+
sessionId: req.headers["mcp-session-id"],
|
|
28
|
+
}, "MCP Request");
|
|
21
29
|
// Check for existing session ID
|
|
22
30
|
const sessionId = req.headers["mcp-session-id"];
|
|
23
31
|
let transport;
|
|
@@ -46,21 +54,24 @@ export function createStatefulServer(createMcpServer, options) {
|
|
|
46
54
|
const configResult = parseAndValidateConfig(req, options?.schema);
|
|
47
55
|
if (!configResult.ok) {
|
|
48
56
|
const status = configResult.error.status || 400;
|
|
57
|
+
logger.error({ error: configResult.error, sessionId: newSessionId }, "Config validation failed");
|
|
49
58
|
res.status(status).json(configResult.error);
|
|
50
59
|
return;
|
|
51
60
|
}
|
|
52
61
|
const config = configResult.value;
|
|
53
62
|
try {
|
|
63
|
+
logger.info({ sessionId: newSessionId }, "Creating new session");
|
|
54
64
|
const server = createMcpServer({
|
|
55
65
|
sessionId: newSessionId,
|
|
56
66
|
config: config,
|
|
57
67
|
auth: req.auth,
|
|
68
|
+
logger,
|
|
58
69
|
});
|
|
59
70
|
// Connect to the MCP server
|
|
60
71
|
await server.connect(transport);
|
|
61
72
|
}
|
|
62
73
|
catch (error) {
|
|
63
|
-
|
|
74
|
+
logger.error({ error, sessionId: newSessionId }, "Error initializing server");
|
|
64
75
|
res.status(500).json({
|
|
65
76
|
jsonrpc: "2.0",
|
|
66
77
|
error: {
|
|
@@ -74,6 +85,7 @@ export function createStatefulServer(createMcpServer, options) {
|
|
|
74
85
|
}
|
|
75
86
|
else {
|
|
76
87
|
// Invalid request
|
|
88
|
+
logger.warn({ sessionId }, "Session not found or expired");
|
|
77
89
|
res.status(400).json({
|
|
78
90
|
jsonrpc: "2.0",
|
|
79
91
|
error: {
|
|
@@ -86,6 +98,12 @@ export function createStatefulServer(createMcpServer, options) {
|
|
|
86
98
|
}
|
|
87
99
|
// Handle the request
|
|
88
100
|
await transport.handleRequest(req, res, req.body);
|
|
101
|
+
// Log successful response
|
|
102
|
+
logger.debug({
|
|
103
|
+
method: req.body.method,
|
|
104
|
+
id: req.body.id,
|
|
105
|
+
sessionId: req.headers["mcp-session-id"],
|
|
106
|
+
}, "MCP Response sent");
|
|
89
107
|
});
|
|
90
108
|
// Add .well-known/mcp-config endpoint for configuration discovery
|
|
91
109
|
app.get("/.well-known/mcp-config", (req, res) => {
|
|
@@ -124,6 +142,7 @@ export function createStatefulServer(createMcpServer, options) {
|
|
|
124
142
|
app.delete("/mcp", async (req, res) => {
|
|
125
143
|
const sessionId = req.headers["mcp-session-id"];
|
|
126
144
|
if (!sessionId) {
|
|
145
|
+
logger.warn("Session termination request missing session ID");
|
|
127
146
|
res.status(400).json({
|
|
128
147
|
jsonrpc: "2.0",
|
|
129
148
|
error: {
|
|
@@ -136,6 +155,7 @@ export function createStatefulServer(createMcpServer, options) {
|
|
|
136
155
|
}
|
|
137
156
|
const transport = sessionStore.get(sessionId);
|
|
138
157
|
if (!transport) {
|
|
158
|
+
logger.warn({ sessionId }, "Session termination failed - not found");
|
|
139
159
|
res.status(404).json({
|
|
140
160
|
jsonrpc: "2.0",
|
|
141
161
|
error: {
|
|
@@ -148,6 +168,7 @@ export function createStatefulServer(createMcpServer, options) {
|
|
|
148
168
|
}
|
|
149
169
|
// Close the transport
|
|
150
170
|
transport.close?.();
|
|
171
|
+
logger.info({ sessionId }, "Session terminated");
|
|
151
172
|
// Acknowledge session termination with 204 No Content
|
|
152
173
|
res.status(204).end();
|
|
153
174
|
});
|
|
@@ -3,12 +3,15 @@ import type { z } from "zod";
|
|
|
3
3
|
import type { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
4
4
|
import type { AuthInfo } from "@modelcontextprotocol/sdk/server/auth/types.js";
|
|
5
5
|
import type { OAuthMountOptions } from "./auth/oauth.js";
|
|
6
|
+
import { type Logger } from "./logger.js";
|
|
7
|
+
export type { Logger } from "./logger.js";
|
|
6
8
|
/**
|
|
7
9
|
* Arguments when we create a stateless server instance
|
|
8
10
|
*/
|
|
9
11
|
export interface CreateStatelessServerArg<T = Record<string, unknown>> {
|
|
10
12
|
config: T;
|
|
11
13
|
auth?: AuthInfo;
|
|
14
|
+
logger: Logger;
|
|
12
15
|
}
|
|
13
16
|
export type CreateStatelessServerFn<T = Record<string, unknown>> = (arg: CreateStatelessServerArg<T>) => Server;
|
|
14
17
|
/**
|
|
@@ -24,6 +27,10 @@ export interface StatelessServerOptions<T = Record<string, unknown>> {
|
|
|
24
27
|
*/
|
|
25
28
|
app?: express.Application;
|
|
26
29
|
oauth?: OAuthMountOptions;
|
|
30
|
+
/**
|
|
31
|
+
* Log level for the server (default: 'info')
|
|
32
|
+
*/
|
|
33
|
+
logLevel?: "debug" | "info" | "warn" | "error";
|
|
27
34
|
}
|
|
28
35
|
/**
|
|
29
36
|
* Creates a stateless server for handling MCP requests.
|
package/dist/server/stateless.js
CHANGED
|
@@ -2,6 +2,7 @@ import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/
|
|
|
2
2
|
import express from "express";
|
|
3
3
|
import { parseAndValidateConfig } from "../shared/config.js";
|
|
4
4
|
import { zodToJsonSchema } from "zod-to-json-schema";
|
|
5
|
+
import { createLogger } from "./logger.js";
|
|
5
6
|
/**
|
|
6
7
|
* Creates a stateless server for handling MCP requests.
|
|
7
8
|
* Each request creates a new server instance - no session state is maintained.
|
|
@@ -13,6 +14,7 @@ import { zodToJsonSchema } from "zod-to-json-schema";
|
|
|
13
14
|
*/
|
|
14
15
|
export function createStatelessServer(createMcpServer, options) {
|
|
15
16
|
const app = options?.app ?? express();
|
|
17
|
+
const logger = createLogger(options?.logLevel ?? "info");
|
|
16
18
|
app.use("/mcp", express.json());
|
|
17
19
|
// Handle POST requests for client-to-server communication
|
|
18
20
|
app.post("/mcp", async (req, res) => {
|
|
@@ -20,10 +22,17 @@ export function createStatelessServer(createMcpServer, options) {
|
|
|
20
22
|
// to ensure complete isolation. A single instance would cause request ID collisions
|
|
21
23
|
// when multiple clients connect concurrently.
|
|
22
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");
|
|
23
31
|
// Validate config for all requests in stateless mode
|
|
24
32
|
const configResult = parseAndValidateConfig(req, options?.schema);
|
|
25
33
|
if (!configResult.ok) {
|
|
26
34
|
const status = configResult.error.status || 400;
|
|
35
|
+
logger.error({ error: configResult.error }, "Config validation failed");
|
|
27
36
|
res.status(status).json(configResult.error);
|
|
28
37
|
return;
|
|
29
38
|
}
|
|
@@ -32,6 +41,7 @@ export function createStatelessServer(createMcpServer, options) {
|
|
|
32
41
|
const server = createMcpServer({
|
|
33
42
|
config,
|
|
34
43
|
auth: req.auth,
|
|
44
|
+
logger,
|
|
35
45
|
});
|
|
36
46
|
// Create a new transport for this request (no session management)
|
|
37
47
|
const transport = new StreamableHTTPServerTransport({
|
|
@@ -46,9 +56,14 @@ export function createStatelessServer(createMcpServer, options) {
|
|
|
46
56
|
await server.connect(transport);
|
|
47
57
|
// Handle the request directly
|
|
48
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");
|
|
49
64
|
}
|
|
50
65
|
catch (error) {
|
|
51
|
-
|
|
66
|
+
logger.error({ error }, "Error handling MCP request");
|
|
52
67
|
if (!res.headersSent) {
|
|
53
68
|
res.status(500).json({
|
|
54
69
|
jsonrpc: "2.0",
|
package/dist/shared/config.d.ts
CHANGED
|
@@ -37,5 +37,6 @@ export declare function parseAndValidateConfig<T = Record<string, unknown>>(req:
|
|
|
37
37
|
reason: string;
|
|
38
38
|
received: unknown;
|
|
39
39
|
}[];
|
|
40
|
+
readonly help: "Pass config as URL query params. Example: /mcp?param1=value1¶m2=value2";
|
|
40
41
|
}> | import("okay-error").Ok<T>;
|
|
41
42
|
export declare function parseConfigFromQuery(query: Iterable<[string, unknown]>): Record<string, unknown>;
|
package/dist/shared/config.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@smithery/sdk",
|
|
3
|
-
"version": "1.6.
|
|
3
|
+
"version": "1.6.8",
|
|
4
4
|
"description": "SDK to develop with Smithery",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -21,7 +21,8 @@
|
|
|
21
21
|
"packageManager": "npm@11.4.1",
|
|
22
22
|
"license": "MIT",
|
|
23
23
|
"dependencies": {
|
|
24
|
-
"@modelcontextprotocol/sdk": "^1.18.
|
|
24
|
+
"@modelcontextprotocol/sdk": "^1.18.1",
|
|
25
|
+
"chalk": "^5.6.2",
|
|
25
26
|
"express": "^5.1.0",
|
|
26
27
|
"jose": "^6.1.0",
|
|
27
28
|
"json-schema": "^0.4.0",
|