@smithery/sdk 1.6.6 → 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.
@@ -32,7 +32,7 @@ export function createIdentityTokenRouter(options) {
32
32
  return;
33
33
  }
34
34
  const host = req.get("host") ?? "localhost";
35
- const audience = `${req.protocol}://${host}${tokenPath}`;
35
+ const audience = `https://${host}${tokenPath}`;
36
36
  const { payload } = await jwtVerify(assertion, JWKS, {
37
37
  issuer,
38
38
  audience,
@@ -74,7 +74,7 @@ export function mountOAuth(app, opts) {
74
74
  // Identity-only: explicitly mount protected resource metadata endpoint
75
75
  app.use("/.well-known/oauth-protected-resource", (req, res, next) => {
76
76
  const host = req.get("host") ?? "localhost";
77
- const issuerUrl = new URL(`${req.protocol}://${host}`);
77
+ const issuerUrl = new URL(`https://${host}`);
78
78
  const protectedResourceMetadata = {
79
79
  resource: new URL("/mcp", issuerUrl).href,
80
80
  authorization_servers: [issuerUrl.href],
@@ -84,7 +84,7 @@ export function mountOAuth(app, opts) {
84
84
  // Identity-only: also advertise minimal AS metadata for discovery per RFC 8414
85
85
  app.use("/.well-known/oauth-authorization-server", (req, res, next) => {
86
86
  const host = req.get("host") ?? "localhost";
87
- const issuerUrl = new URL(`${req.protocol}://${host}`);
87
+ const issuerUrl = new URL(`https://${host}`);
88
88
  const oauthMetadata = {
89
89
  issuer: issuerUrl.href,
90
90
  token_endpoint: new URL(`${basePath}token`, issuerUrl).href,
@@ -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.
@@ -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
- console.error("Error initializing server:", error);
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.
@@ -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
- console.error("Error handling MCP request:", error);
66
+ logger.error({ error }, "Error handling MCP request");
52
67
  if (!res.headersSent) {
53
68
  res.status(500).json({
54
69
  jsonrpc: "2.0",
@@ -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&param2=value2";
40
41
  }> | import("okay-error").Ok<T>;
41
42
  export declare function parseConfigFromQuery(query: Iterable<[string, unknown]>): Record<string, unknown>;
@@ -98,6 +98,7 @@ export function parseAndValidateConfig(req, schema) {
98
98
  instance: req.originalUrl,
99
99
  configSchema: jsonSchema,
100
100
  errors,
101
+ help: "Pass config as URL query params. Example: /mcp?param1=value1&param2=value2",
101
102
  });
102
103
  }
103
104
  return ok(result.data);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@smithery/sdk",
3
- "version": "1.6.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.0",
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",