From e1e13a1242d59839e8dda97772c69a3ddbd13998 Mon Sep 17 00:00:00 2001 From: Wizard Date: Mon, 5 May 2025 04:20:39 +0000 Subject: [PATCH 1/5] add multiple tenant support --- package-lock.json | 4 ++-- src/server/rest.ts | 55 +++++++++++++++++++++++++++++++++++++++------- 2 files changed, 49 insertions(+), 10 deletions(-) diff --git a/package-lock.json b/package-lock.json index 8e2cd60..9dff5cf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@chatmcp/sdk", - "version": "1.0.2", + "version": "1.0.6", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@chatmcp/sdk", - "version": "1.0.2", + "version": "1.0.6", "license": "MIT", "dependencies": { "@modelcontextprotocol/sdk": "^1.8.0", diff --git a/src/server/rest.ts b/src/server/rest.ts index f5fecbb..5e4b195 100644 --- a/src/server/rest.ts +++ b/src/server/rest.ts @@ -17,6 +17,7 @@ const MAXIMUM_MESSAGE_SIZE = "4mb"; export interface RestServerTransportOptions { endpoint?: string; port?: string | number; + supportTenantId?: boolean; } /** @@ -36,6 +37,7 @@ export class RestServerTransport implements Transport { private _started: boolean = false; private _endpoint: string; private _port: number; + private _supportTenantId: boolean; private _server: ReturnType | null = null; private _httpServer: ReturnType | null = null; @@ -55,6 +57,7 @@ export class RestServerTransport implements Transport { constructor(options: RestServerTransportOptions = {}) { this._endpoint = options.endpoint || "/rest"; this._port = Number(options.port) || 9593; + this._supportTenantId = options.supportTenantId || false; } /** @@ -66,9 +69,19 @@ export class RestServerTransport implements Transport { } this._server = express(); - this._server.post(this._endpoint, (req, res) => { - this.handleRequest(req, res, req.body); - }); + + if (this._supportTenantId) { + // 添加带租户ID的路由 + this._server.post(`${this._endpoint}/:tenantId`, (req, res) => { + const tenantId = req.params.tenantId; + this.handleRequest(req, res, req.body, tenantId); + }); + } else { + // 保持原有路由 + this._server.post(this._endpoint, (req, res) => { + this.handleRequest(req, res, req.body); + }); + } return new Promise((resolve, reject) => { try { @@ -125,10 +138,11 @@ export class RestServerTransport implements Transport { async handleRequest( req: IncomingMessage, res: ServerResponse, - parsedBody?: unknown + parsedBody?: unknown, + tenantId?: string ): Promise { if (req.method === "POST") { - await this.handlePostRequest(req, res, parsedBody); + await this.handlePostRequest(req, res, parsedBody, tenantId); } else { res.writeHead(405).end( JSON.stringify({ @@ -149,7 +163,8 @@ export class RestServerTransport implements Transport { private async handlePostRequest( req: IncomingMessage, res: ServerResponse, - parsedBody?: unknown + parsedBody?: unknown, + tenantId?: string ): Promise { try { // validate the Accept header @@ -223,7 +238,19 @@ export class RestServerTransport implements Transport { // handle each message for (const message of messages) { - this.onmessage?.(message); + if (tenantId && "method" in message) { + // 为每个消息添加租户ID + const messageWithTenant = { + ...message, + params: { + ...message.params, + _tenantId: tenantId // 添加租户ID作为隐含参数 + } + }; + this.onmessage?.(messageWithTenant); + } else { + this.onmessage?.(message); + } } } else if (hasRequests) { // Create a unique identifier for this request batch @@ -245,7 +272,19 @@ export class RestServerTransport implements Transport { // Process all messages for (const message of messages) { - this.onmessage?.(message); + if (tenantId && "method" in message) { + // 为每个消息添加租户ID + const messageWithTenant = { + ...message, + params: { + ...message.params, + _tenantId: tenantId // 添加租户ID作为隐含参数 + } + }; + this.onmessage?.(messageWithTenant); + } else { + this.onmessage?.(message); + } } // Wait for responses and send them From c97a3fc54fdbd292b909ed42e17bb6672a839f6a Mon Sep 17 00:00:00 2001 From: Wizard Date: Mon, 5 May 2025 04:25:13 +0000 Subject: [PATCH 2/5] add simple api key authorization --- src/server/rest.ts | 51 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 50 insertions(+), 1 deletion(-) diff --git a/src/server/rest.ts b/src/server/rest.ts index 5e4b195..5a5c764 100644 --- a/src/server/rest.ts +++ b/src/server/rest.ts @@ -18,6 +18,8 @@ export interface RestServerTransportOptions { endpoint?: string; port?: string | number; supportTenantId?: boolean; + apiKey?: string; + apiKeyHeaderName?: string; } /** @@ -27,10 +29,25 @@ export interface RestServerTransportOptions { * Usage example: * * ```typescript - * // Create a basic synchronous transport + * // 创建基本同步传输 * const transport = new RestServerTransport({ endpoint: '/rest', port: '9593' }); * await server.connect(transport); * await transport.startServer(); + * + * // 带租户ID支持的传输 + * const multitenantTransport = new RestServerTransport({ + * endpoint: '/api', + * port: 9593, + * supportTenantId: true + * }); + * + * // 带API Key认证的传输 + * const secureTransport = new RestServerTransport({ + * endpoint: '/secure', + * port: 9593, + * apiKey: 'your-secret-api-key', + * apiKeyHeaderName: 'X-API-Key' // 可选,默认为 'X-API-Key' + * }); * ``` */ export class RestServerTransport implements Transport { @@ -38,6 +55,8 @@ export class RestServerTransport implements Transport { private _endpoint: string; private _port: number; private _supportTenantId: boolean; + private _apiKey?: string; + private _apiKeyHeaderName: string; private _server: ReturnType | null = null; private _httpServer: ReturnType | null = null; @@ -58,6 +77,8 @@ export class RestServerTransport implements Transport { this._endpoint = options.endpoint || "/rest"; this._port = Number(options.port) || 9593; this._supportTenantId = options.supportTenantId || false; + this._apiKey = options.apiKey; + this._apiKeyHeaderName = options.apiKeyHeaderName || "X-API-Key"; } /** @@ -157,6 +178,19 @@ export class RestServerTransport implements Transport { } } + /** + * Validates API Key from the request header + */ + private validateApiKey(req: IncomingMessage): boolean { + // 如果没有设置API Key,则不需要验证 + if (!this._apiKey) { + return true; + } + + const providedApiKey = req.headers[this._apiKeyHeaderName.toLowerCase()]; + return providedApiKey === this._apiKey; + } + /** * Handles POST requests containing JSON-RPC messages */ @@ -167,6 +201,21 @@ export class RestServerTransport implements Transport { tenantId?: string ): Promise { try { + // 验证API Key + if (!this.validateApiKey(req)) { + res.writeHead(401).end( + JSON.stringify({ + jsonrpc: "2.0", + error: { + code: -32001, + message: "Unauthorized: Invalid API Key", + }, + id: null, + }) + ); + return; + } + // validate the Accept header const acceptHeader = req.headers.accept; if ( From 20b56b0710320ddaf95108020fde30267e10a536 Mon Sep 17 00:00:00 2001 From: Wizard Date: Mon, 5 May 2025 04:28:16 +0000 Subject: [PATCH 3/5] update doc for multi tenant and api authorization --- README.md | 121 ++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 117 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 7051f1b..96ccc71 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,16 @@ # MCP Typescript SDK by ChatMCP -## How to use +## 如何使用 -1. install sdk +1. 安装SDK ```shell npm i @chatmcp/sdk ``` -2. update MCP Server +2. 配置MCP服务器 + +### 基本配置 ```typescript import { RestServerTransport } from "@chatmcp/sdk/server/rest.js"; @@ -24,7 +26,56 @@ async function main() { } ``` -3. request api +### 多租户支持 + +```typescript +import { RestServerTransport } from "@chatmcp/sdk/server/rest.js"; + +async function main() { + const port = 9593; + const endpoint = "/api"; + + // 启用多租户支持 + const transport = new RestServerTransport({ + port, + endpoint, + supportTenantId: true // 启用多租户支持 + }); + + await server.connect(transport); + await transport.startServer(); + + // 现在可以通过 /api/{tenantId} 访问,如 /api/tenant1, /api/tenant2 +} +``` + +### API认证支持 + +```typescript +import { RestServerTransport } from "@chatmcp/sdk/server/rest.js"; + +async function main() { + const port = 9593; + const endpoint = "/secure"; + + // 启用API Key认证 + const transport = new RestServerTransport({ + port, + endpoint, + apiKey: "your-secret-api-key", // 设置API密钥 + apiKeyHeaderName: "X-API-Key" // 可选,默认为"X-API-Key" + // 也可以使用标准的Authorization头 + // apiKeyHeaderName: "Authorization" + }); + + await server.connect(transport); + await transport.startServer(); +} +``` + +3. 请求API + +### 基本请求 ```curl curl -X POST http://127.0.0.1:9593/rest \ @@ -43,3 +94,65 @@ curl -X POST http://127.0.0.1:9593/rest \ } }' ``` + +### 多租户请求 + +```curl +curl -X POST http://127.0.0.1:9593/api/tenant1 \ +-H "Content-Type: application/json" \ +-d '{ + "jsonrpc": "2.0", + "id": "1", + "method": "initialize", + "params": { + "protocolVersion": "1.0", + "capabilities": {}, + "clientInfo": { + "name": "your_client_name", + "version": "your_version" + } + } +}' +``` + +### 带API认证的请求 + +```curl +curl -X POST http://127.0.0.1:9593/secure \ +-H "Content-Type: application/json" \ +-H "X-API-Key: your-secret-api-key" \ +-d '{ + "jsonrpc": "2.0", + "id": "1", + "method": "initialize", + "params": { + "protocolVersion": "1.0", + "capabilities": {}, + "clientInfo": { + "name": "your_client_name", + "version": "your_version" + } + } +}' +``` + +### 使用Authorization头的API认证请求 + +```curl +curl -X POST http://127.0.0.1:9593/secure \ +-H "Content-Type: application/json" \ +-H "Authorization: Bearer your-secret-api-key" \ +-d '{ + "jsonrpc": "2.0", + "id": "1", + "method": "initialize", + "params": { + "protocolVersion": "1.0", + "capabilities": {}, + "clientInfo": { + "name": "your_client_name", + "version": "your_version" + } + } +}' +``` From 6bd7827a6a611546776568214faeb5e9f1687038 Mon Sep 17 00:00:00 2001 From: Wizard Date: Mon, 5 May 2025 05:06:17 +0000 Subject: [PATCH 4/5] add doc for caller --- README.md | 66 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/README.md b/README.md index 96ccc71..d41f652 100644 --- a/README.md +++ b/README.md @@ -156,3 +156,69 @@ curl -X POST http://127.0.0.1:9593/secure \ } }' ``` + +### 在请求处理程序中获取租户ID + +当您启用多租户支持(`supportTenantId: true`)时,租户ID会作为特殊参数`_tenantId`添加到每个请求的`params`对象中。下面是一个示例,展示如何在请求处理程序中获取租户ID: + +```typescript +import { RestServerTransport } from "@chatmcp/sdk/server/rest.js"; + +async function main() { + // 创建支持多租户的传输 + const transport = new RestServerTransport({ + port: 9593, + endpoint: "/api", + supportTenantId: true + }); + + await server.connect(transport); + + // 设置请求处理程序 + server.setRequestHandler(ListToolsRequestSchema, async (request) => { + // 获取租户ID + const tenantId = request.params._tenantId; + console.log(`处理来自租户 ${tenantId} 的请求`); + + // 可以根据租户ID返回不同的工具列表 + return { + tools: tenantId === "admin" ? ADMIN_TOOLS : REGULAR_TOOLS + }; + }); + + server.setRequestHandler(CallToolRequestSchema, async (request) => { + // 获取租户ID + const tenantId = request.params._tenantId; + + // 使用租户ID进行权限检查或租户隔离 + if (!hasPermission(tenantId, request.params.name)) { + throw new Error(`租户 ${tenantId} 无权访问工具 ${request.params.name}`); + } + + // 将租户ID传递给工具执行函数,以支持租户隔离 + return await executeToolAndHandleErrors( + request.params.name, + { + ...request.params.arguments || {}, + _tenantId: tenantId // 传递租户ID到工具执行上下文 + }, + taskManager + ); + }); + + await transport.startServer(); +} + +// 示例权限检查函数 +function hasPermission(tenantId: string, toolName: string): boolean { + // 实现您的权限检查逻辑 + return true; +} +``` + +通过这种方式,您可以在请求处理程序中获取租户ID,并用它来实现: + +1. 租户隔离 - 确保每个租户只能访问其自己的数据 +2. 租户特定的配置 - 为不同租户提供不同的工具或功能 +3. 多租户认证和授权 - 结合API密钥实现更细粒度的访问控制 +4. 审计日志 - 记录每个租户的访问和操作 From 5f625a0f7f390744d9d8627eec1b788447f13b3d Mon Sep 17 00:00:00 2001 From: Wizard Date: Mon, 5 May 2025 05:11:53 +0000 Subject: [PATCH 5/5] translate chs to eng --- README.md | 74 +++++++++++++++++++++++----------------------- src/server/rest.ts | 24 +++++++-------- 2 files changed, 49 insertions(+), 49 deletions(-) diff --git a/README.md b/README.md index d41f652..3b2f452 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,16 @@ # MCP Typescript SDK by ChatMCP -## 如何使用 +## How to Use -1. 安装SDK +1. Install SDK ```shell npm i @chatmcp/sdk ``` -2. 配置MCP服务器 +2. Configure MCP Server -### 基本配置 +### Basic Configuration ```typescript import { RestServerTransport } from "@chatmcp/sdk/server/rest.js"; @@ -26,7 +26,7 @@ async function main() { } ``` -### 多租户支持 +### Multi-tenant Support ```typescript import { RestServerTransport } from "@chatmcp/sdk/server/rest.js"; @@ -35,21 +35,21 @@ async function main() { const port = 9593; const endpoint = "/api"; - // 启用多租户支持 + // Enable multi-tenant support const transport = new RestServerTransport({ port, endpoint, - supportTenantId: true // 启用多租户支持 + supportTenantId: true // Enable multi-tenant support }); await server.connect(transport); await transport.startServer(); - // 现在可以通过 /api/{tenantId} 访问,如 /api/tenant1, /api/tenant2 + // Now accessible via /api/{tenantId}, such as /api/tenant1, /api/tenant2 } ``` -### API认证支持 +### API Authentication Support ```typescript import { RestServerTransport } from "@chatmcp/sdk/server/rest.js"; @@ -58,13 +58,13 @@ async function main() { const port = 9593; const endpoint = "/secure"; - // 启用API Key认证 + // Enable API Key authentication const transport = new RestServerTransport({ port, endpoint, - apiKey: "your-secret-api-key", // 设置API密钥 - apiKeyHeaderName: "X-API-Key" // 可选,默认为"X-API-Key" - // 也可以使用标准的Authorization头 + apiKey: "your-secret-api-key", // Set API key + apiKeyHeaderName: "X-API-Key" // Optional, defaults to "X-API-Key" + // You can also use the standard Authorization header // apiKeyHeaderName: "Authorization" }); @@ -73,9 +73,9 @@ async function main() { } ``` -3. 请求API +3. API Requests -### 基本请求 +### Basic Request ```curl curl -X POST http://127.0.0.1:9593/rest \ @@ -95,7 +95,7 @@ curl -X POST http://127.0.0.1:9593/rest \ }' ``` -### 多租户请求 +### Multi-tenant Request ```curl curl -X POST http://127.0.0.1:9593/api/tenant1 \ @@ -115,7 +115,7 @@ curl -X POST http://127.0.0.1:9593/api/tenant1 \ }' ``` -### 带API认证的请求 +### Request with API Authentication ```curl curl -X POST http://127.0.0.1:9593/secure \ @@ -136,7 +136,7 @@ curl -X POST http://127.0.0.1:9593/secure \ }' ``` -### 使用Authorization头的API认证请求 +### Request with API Authentication Using Authorization Header ```curl curl -X POST http://127.0.0.1:9593/secure \ @@ -157,15 +157,15 @@ curl -X POST http://127.0.0.1:9593/secure \ }' ``` -### 在请求处理程序中获取租户ID +### Accessing Tenant ID in Request Handlers -当您启用多租户支持(`supportTenantId: true`)时,租户ID会作为特殊参数`_tenantId`添加到每个请求的`params`对象中。下面是一个示例,展示如何在请求处理程序中获取租户ID: +When you enable multi-tenant support (`supportTenantId: true`), the tenant ID is added to each request's `params` object as a special parameter `_tenantId`. Here's an example showing how to access the tenant ID in request handlers: ```typescript import { RestServerTransport } from "@chatmcp/sdk/server/rest.js"; async function main() { - // 创建支持多租户的传输 + // Create transport with multi-tenant support const transport = new RestServerTransport({ port: 9593, endpoint: "/api", @@ -174,33 +174,33 @@ async function main() { await server.connect(transport); - // 设置请求处理程序 + // Set up request handlers server.setRequestHandler(ListToolsRequestSchema, async (request) => { - // 获取租户ID + // Get tenant ID const tenantId = request.params._tenantId; - console.log(`处理来自租户 ${tenantId} 的请求`); + console.log(`Processing request from tenant ${tenantId}`); - // 可以根据租户ID返回不同的工具列表 + // Return different tool lists based on tenant ID return { tools: tenantId === "admin" ? ADMIN_TOOLS : REGULAR_TOOLS }; }); server.setRequestHandler(CallToolRequestSchema, async (request) => { - // 获取租户ID + // Get tenant ID const tenantId = request.params._tenantId; - // 使用租户ID进行权限检查或租户隔离 + // Use tenant ID for permission checks or tenant isolation if (!hasPermission(tenantId, request.params.name)) { - throw new Error(`租户 ${tenantId} 无权访问工具 ${request.params.name}`); + throw new Error(`Tenant ${tenantId} does not have permission to access tool ${request.params.name}`); } - // 将租户ID传递给工具执行函数,以支持租户隔离 + // Pass tenant ID to tool execution function for tenant isolation return await executeToolAndHandleErrors( request.params.name, { ...request.params.arguments || {}, - _tenantId: tenantId // 传递租户ID到工具执行上下文 + _tenantId: tenantId // Pass tenant ID to tool execution context }, taskManager ); @@ -209,16 +209,16 @@ async function main() { await transport.startServer(); } -// 示例权限检查函数 +// Example permission check function function hasPermission(tenantId: string, toolName: string): boolean { - // 实现您的权限检查逻辑 + // Implement your permission check logic return true; } ``` -通过这种方式,您可以在请求处理程序中获取租户ID,并用它来实现: +Using this approach, you can access the tenant ID in request handlers to implement: -1. 租户隔离 - 确保每个租户只能访问其自己的数据 -2. 租户特定的配置 - 为不同租户提供不同的工具或功能 -3. 多租户认证和授权 - 结合API密钥实现更细粒度的访问控制 -4. 审计日志 - 记录每个租户的访问和操作 +1. Tenant Isolation - Ensure each tenant can only access their own data +2. Tenant-specific Configuration - Provide different tools or features for different tenants +3. Multi-tenant Authentication and Authorization - Combine with API keys for more granular access control +4. Audit Logging - Record access and operations for each tenant diff --git a/src/server/rest.ts b/src/server/rest.ts index 5a5c764..b02cbbf 100644 --- a/src/server/rest.ts +++ b/src/server/rest.ts @@ -29,24 +29,24 @@ export interface RestServerTransportOptions { * Usage example: * * ```typescript - * // 创建基本同步传输 + * // Create basic synchronous transport * const transport = new RestServerTransport({ endpoint: '/rest', port: '9593' }); * await server.connect(transport); * await transport.startServer(); * - * // 带租户ID支持的传输 + * // Transport with tenant ID support * const multitenantTransport = new RestServerTransport({ * endpoint: '/api', * port: 9593, * supportTenantId: true * }); * - * // 带API Key认证的传输 + * // Transport with API Key authentication * const secureTransport = new RestServerTransport({ * endpoint: '/secure', * port: 9593, * apiKey: 'your-secret-api-key', - * apiKeyHeaderName: 'X-API-Key' // 可选,默认为 'X-API-Key' + * apiKeyHeaderName: 'X-API-Key' // Optional, defaults to 'X-API-Key' * }); * ``` */ @@ -92,13 +92,13 @@ export class RestServerTransport implements Transport { this._server = express(); if (this._supportTenantId) { - // 添加带租户ID的路由 + // Add route with tenant ID this._server.post(`${this._endpoint}/:tenantId`, (req, res) => { const tenantId = req.params.tenantId; this.handleRequest(req, res, req.body, tenantId); }); } else { - // 保持原有路由 + // Keep original route this._server.post(this._endpoint, (req, res) => { this.handleRequest(req, res, req.body); }); @@ -182,7 +182,7 @@ export class RestServerTransport implements Transport { * Validates API Key from the request header */ private validateApiKey(req: IncomingMessage): boolean { - // 如果没有设置API Key,则不需要验证 + // If no API Key is set, no validation needed if (!this._apiKey) { return true; } @@ -201,7 +201,7 @@ export class RestServerTransport implements Transport { tenantId?: string ): Promise { try { - // 验证API Key + // Validate API Key if (!this.validateApiKey(req)) { res.writeHead(401).end( JSON.stringify({ @@ -288,12 +288,12 @@ export class RestServerTransport implements Transport { // handle each message for (const message of messages) { if (tenantId && "method" in message) { - // 为每个消息添加租户ID + // Add tenant ID to each message const messageWithTenant = { ...message, params: { ...message.params, - _tenantId: tenantId // 添加租户ID作为隐含参数 + _tenantId: tenantId // Add tenant ID as implicit parameter } }; this.onmessage?.(messageWithTenant); @@ -322,12 +322,12 @@ export class RestServerTransport implements Transport { // Process all messages for (const message of messages) { if (tenantId && "method" in message) { - // 为每个消息添加租户ID + // Add tenant ID to each message const messageWithTenant = { ...message, params: { ...message.params, - _tenantId: tenantId // 添加租户ID作为隐含参数 + _tenantId: tenantId // Add tenant ID as implicit parameter } }; this.onmessage?.(messageWithTenant);