Bladeren bron

fix: Admin MCP Server 修复多请求连接错误

修复问题:
- 每个 HTTP 请求创建独立的 McpServer 实例
- 避免 "Already connected to a transport" 错误
- 重构代码架构,使用 createMcpServer() 和 registerAllTools()

技术细节:
- 将单例 server 模式改为每请求一个实例
- 正确使用 StreamableHTTPServerTransport 的无状态特性
- 删除重复的工具注册代码

Generated with [Claude Code](https://claude.ai/code)
via [Happy](https://happy.engineering)

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
yourname 3 weken geleden
bovenliggende
commit
cb853eefdc
1 gewijzigde bestanden met toevoegingen van 236 en 331 verwijderingen
  1. 236 331
      packages/admin-mcp-server/src/index.ts

+ 236 - 331
packages/admin-mcp-server/src/index.ts

@@ -32,74 +32,35 @@ const SERVER_CONFIG = {
 };
 
 // ============================================================================
-// Create MCP Server Instance
+// Helper: Create MCP Server Instance
 // ============================================================================
 
-const server = new McpServer({
-  name: SERVER_CONFIG.name,
-  version: SERVER_CONFIG.version
-});
-
-// ============================================================================
-// Helper: Register Tool with Annotations
-// ============================================================================
-
-interface ToolRegistration {
-  name: string;
-  schema?: z.ZodSchema;
-  // eslint-disable-next-line @typescript-eslint/no-explicit-any
-  handler: (...args: any[]) => any;
-}
-
-interface ToolAnnotations {
-  readOnlyHint?: boolean;
-  destructiveHint?: boolean;
-  idempotentHint?: boolean;
-  openWorldHint?: boolean;
-}
-
-function registerToolWithAnnotations(
-  registration: ToolRegistration,
-  annotations: ToolAnnotations,
-  title: string,
-  description: string
-): void {
-  const { name, schema, handler } = registration;
-
-  // Build tool options
-  // eslint-disable-next-line @typescript-eslint/no-explicit-any
-  const toolOptions: any = {
-    title,
-    description
-  };
-
-  // Add input schema if provided
-  if (schema) {
-    toolOptions.inputSchema = schema;
-  }
+/**
+ * Create a new McpServer instance with all tools registered
+ * This is called for each request to ensure clean state
+ */
+function createMcpServer(): McpServer {
+  const newServer = new McpServer({
+    name: SERVER_CONFIG.name,
+    version: SERVER_CONFIG.version
+  });
 
-  // Add annotations
-  toolOptions.annotations = annotations;
+  // Register all tools on the new server instance
+  registerAllTools(newServer);
 
-  // Register the tool
-  server.registerTool(name, toolOptions, handler);
+  return newServer;
 }
 
-// ============================================================================
-// Register Authentication Tools
-// ============================================================================
-
-// Login tool
-registerToolWithAnnotations(
-  authTools.login,
-  {
-    readOnlyHint: false,
-    destructiveHint: false,
-    idempotentHint: false,
-    openWorldHint: false
-  },
-  'Admin Login',
-  `Authenticate with the admin backend using username and password.
+/**
+ * Register all tools on a given server instance
+ */
+function registerAllTools(targetServer: McpServer): void {
+  // Authentication tools
+  registerToolWithAnnotations(
+    authTools.login,
+    { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false },
+    'Admin Login',
+    `Authenticate with the admin backend using username and password.
 
 This tool establishes an authenticated session by logging in to the admin system.
 The authentication token is automatically stored and used for subsequent tool calls.
@@ -117,20 +78,15 @@ Examples:
 
 Error Handling:
   - Returns "Error: Authentication failed" if credentials are invalid
-  - Returns "Error: Cannot connect to API server" if backend is unreachable`
-);
+  - Returns "Error: Cannot connect to API server" if backend is unreachable`,
+    targetServer
+  );
 
-// Logout tool
-registerToolWithAnnotations(
-  authTools.logout,
-  {
-    readOnlyHint: false,
-    destructiveHint: false,
-    idempotentHint: true,
-    openWorldHint: false
-  },
-  'Admin Logout',
-  `Logout from the admin backend and clear the authentication token.
+  registerToolWithAnnotations(
+    authTools.logout,
+    { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false },
+    'Admin Logout',
+    `Logout from the admin backend and clear the authentication token.
 
 This tool ends the current admin session by clearing the stored authentication token.
 Subsequent API calls will require re-authentication.
@@ -145,20 +101,15 @@ Examples:
   - Use when: "Logout from admin system" or "End admin session"
 
 Error Handling:
-  - Rarely fails, mainly for client-side issues`
-);
+  - Rarely fails, mainly for client-side issues`,
+    targetServer
+  );
 
-// Get current user tool
-registerToolWithAnnotations(
-  authTools.getCurrentUser,
-  {
-    readOnlyHint: true,
-    destructiveHint: false,
-    idempotentHint: true,
-    openWorldHint: false
-  },
-  'Get Current Admin User',
-  `Retrieve information about the currently authenticated admin user.
+  registerToolWithAnnotations(
+    authTools.getCurrentUser,
+    { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
+    'Get Current Admin User',
+    `Retrieve information about the currently authenticated admin user.
 
 This tool returns details about the user that is currently logged in,
 including their roles, permissions, and profile information.
@@ -174,24 +125,16 @@ Examples:
   - Use when: "Check my admin permissions"
 
 Error Handling:
-  - Returns "Error: Authentication failed" if not logged in`
-);
+  - Returns "Error: Authentication failed" if not logged in`,
+    targetServer
+  );
 
-// ============================================================================
-// Register User Management Tools
-// ============================================================================
-
-// List users
-registerToolWithAnnotations(
-  userTools.userList,
-  {
-    readOnlyHint: true,
-    destructiveHint: false,
-    idempotentHint: true,
-    openWorldHint: true
-  },
-  'List Users',
-  `List and search users in the admin system with pagination and filtering.
+  // User management tools
+  registerToolWithAnnotations(
+    userTools.userList,
+    { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
+    'List Users',
+    `List and search users in the admin system with pagination and filtering.
 
 This tool retrieves a paginated list of users from the admin system.
 Supports keyword search, sorting, and advanced filtering.
@@ -224,20 +167,15 @@ Examples:
 
 Error Handling:
   - Returns "Error: Permission denied" if not authorized
-  - Returns "Error: Cannot connect to API server" if backend is unreachable`
-);
+  - Returns "Error: Cannot connect to API server" if backend is unreachable`,
+    targetServer
+  );
 
-// Get user
-registerToolWithAnnotations(
-  userTools.userGet,
-  {
-    readOnlyHint: true,
-    destructiveHint: false,
-    idempotentHint: true,
-    openWorldHint: false
-  },
-  'Get User by ID',
-  `Retrieve detailed information about a specific user by their ID.
+  registerToolWithAnnotations(
+    userTools.userGet,
+    { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
+    'Get User by ID',
+    `Retrieve detailed information about a specific user by their ID.
 
 This tool fetches complete user information including profile, roles, and company.
 
@@ -253,20 +191,15 @@ Examples:
 
 Error Handling:
   - Returns "Error: Resource not found" if user ID doesn't exist
-  - Returns "Error: Permission denied" if not authorized`
-);
+  - Returns "Error: Permission denied" if not authorized`,
+    targetServer
+  );
 
-// Create user
-registerToolWithAnnotations(
-  userTools.userCreate,
-  {
-    readOnlyHint: false,
-    destructiveHint: false,
-    idempotentHint: false,
-    openWorldHint: false
-  },
-  'Create User',
-  `Create a new user in the admin system.
+  registerToolWithAnnotations(
+    userTools.userCreate,
+    { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false },
+    'Create User',
+    `Create a new user in the admin system.
 
 This tool creates a new user with the provided information.
 A password will be generated if not provided.
@@ -292,20 +225,15 @@ Examples:
 
 Error Handling:
   - Returns "Error: Data already exists" if username/phone/email is duplicated
-  - Returns "Error: Permission denied" if not authorized`
-);
+  - Returns "Error: Permission denied" if not authorized`,
+    targetServer
+  );
 
-// Update user
-registerToolWithAnnotations(
-  userTools.userUpdate,
-  {
-    readOnlyHint: false,
-    destructiveHint: false,
-    idempotentHint: true,
-    openWorldHint: false
-  },
-  'Update User',
-  `Update an existing user's information.
+  registerToolWithAnnotations(
+    userTools.userUpdate,
+    { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false },
+    'Update User',
+    `Update an existing user's information.
 
 This tool modifies user information for the specified user ID.
 Only the fields provided will be updated.
@@ -332,20 +260,15 @@ Examples:
 
 Error Handling:
   - Returns "Error: Resource not found" if user ID doesn't exist
-  - Returns "Error: Permission denied" if not authorized`
-);
+  - Returns "Error: Permission denied" if not authorized`,
+    targetServer
+  );
 
-// Delete user
-registerToolWithAnnotations(
-  userTools.userDelete,
-  {
-    readOnlyHint: false,
-    destructiveHint: true,
-    idempotentHint: false,
-    openWorldHint: false
-  },
-  'Delete User',
-  `Delete a user from the admin system.
+  registerToolWithAnnotations(
+    userTools.userDelete,
+    { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: false },
+    'Delete User',
+    `Delete a user from the admin system.
 
 This tool permanently deletes the specified user.
 This action cannot be undone.
@@ -362,24 +285,16 @@ Examples:
 Error Handling:
   - Returns "Error: Resource not found" if user ID doesn't exist
   - Returns "Error: Permission denied" if not authorized
-  - May fail if user has dependent records`
-);
-
-// ============================================================================
-// Register Role Management Tools
-// ============================================================================
+  - May fail if user has dependent records`,
+    targetServer
+  );
 
-// List roles
-registerToolWithAnnotations(
-  roleTools.roleList,
-  {
-    readOnlyHint: true,
-    destructiveHint: false,
-    idempotentHint: true,
-    openWorldHint: true
-  },
-  'List Roles',
-  `List and search roles in the admin system with pagination.
+  // Role management tools
+  registerToolWithAnnotations(
+    roleTools.roleList,
+    { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
+    'List Roles',
+    `List and search roles in the admin system with pagination.
 
 This tool retrieves a paginated list of roles from the admin system.
 Supports keyword search and sorting.
@@ -401,20 +316,15 @@ Examples:
   - Use when: "Search for admin role" -> params with keyword="admin"
 
 Error Handling:
-  - Returns "Error: Permission denied" if not authorized`
-);
+  - Returns "Error: Permission denied" if not authorized`,
+    targetServer
+  );
 
-// Get role
-registerToolWithAnnotations(
-  roleTools.roleGet,
-  {
-    readOnlyHint: true,
-    destructiveHint: false,
-    idempotentHint: true,
-    openWorldHint: false
-  },
-  'Get Role by ID',
-  `Retrieve detailed information about a specific role by ID.
+  registerToolWithAnnotations(
+    roleTools.roleGet,
+    { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
+    'Get Role by ID',
+    `Retrieve detailed information about a specific role by ID.
 
 Args:
   - id (number): Role ID (required)
@@ -426,20 +336,15 @@ Examples:
   - Use when: "Get role with ID 5" -> params with id=5
 
 Error Handling:
-  - Returns "Error: Resource not found" if role ID doesn't exist`
-);
+  - Returns "Error: Resource not found" if role ID doesn't exist`,
+    targetServer
+  );
 
-// Create role
-registerToolWithAnnotations(
-  roleTools.roleCreate,
-  {
-    readOnlyHint: false,
-    destructiveHint: false,
-    idempotentHint: false,
-    openWorldHint: false
-  },
-  'Create Role',
-  `Create a new role in the admin system.
+  registerToolWithAnnotations(
+    roleTools.roleCreate,
+    { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false },
+    'Create Role',
+    `Create a new role in the admin system.
 
 Args:
   - name (string): Role name (required)
@@ -454,20 +359,15 @@ Examples:
   - Use when: "Create role Content Manager" -> params with name="Content Manager"
 
 Error Handling:
-  - Returns "Error: Data already exists" if role name/code is duplicated`
-);
+  - Returns "Error: Data already exists" if role name/code is duplicated`,
+    targetServer
+  );
 
-// Update role
-registerToolWithAnnotations(
-  roleTools.roleUpdate,
-  {
-    readOnlyHint: false,
-    destructiveHint: false,
-    idempotentHint: true,
-    openWorldHint: false
-  },
-  'Update Role',
-  `Update an existing role's information.
+  registerToolWithAnnotations(
+    roleTools.roleUpdate,
+    { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false },
+    'Update Role',
+    `Update an existing role's information.
 
 Args:
   - id (number): Role ID to update (required)
@@ -483,20 +383,15 @@ Examples:
   - Use when: "Update role 3 description" -> params with id=3, description="New description"
 
 Error Handling:
-  - Returns "Error: Resource not found" if role ID doesn't exist`
-);
+  - Returns "Error: Resource not found" if role ID doesn't exist`,
+    targetServer
+  );
 
-// Delete role
-registerToolWithAnnotations(
-  roleTools.roleDelete,
-  {
-    readOnlyHint: false,
-    destructiveHint: true,
-    idempotentHint: false,
-    openWorldHint: false
-  },
-  'Delete Role',
-  `Delete a role from the admin system.
+  registerToolWithAnnotations(
+    roleTools.roleDelete,
+    { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: false },
+    'Delete Role',
+    `Delete a role from the admin system.
 
 Args:
   - id (number): Role ID to delete (required)
@@ -509,24 +404,16 @@ Examples:
 
 Error Handling:
   - Returns "Error: Resource not found" if role ID doesn't exist
-  - May fail if role is assigned to users`
-);
-
-// ============================================================================
-// Register System Config Tools
-// ============================================================================
+  - May fail if role is assigned to users`,
+    targetServer
+  );
 
-// List system configs
-registerToolWithAnnotations(
-  systemConfigTools.systemConfigList,
-  {
-    readOnlyHint: true,
-    destructiveHint: false,
-    idempotentHint: true,
-    openWorldHint: true
-  },
-  'List System Configurations',
-  `List and search system configurations with pagination.
+  // System config tools
+  registerToolWithAnnotations(
+    systemConfigTools.systemConfigList,
+    { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
+    'List System Configurations',
+    `List and search system configurations with pagination.
 
 This tool retrieves a paginated list of system configuration items.
 
@@ -547,20 +434,15 @@ Examples:
   - Use when: "Get configs in category 'payment'" -> params with filters='{"category": "payment"}'
 
 Error Handling:
-  - Returns "Error: Permission denied" if not authorized`
-);
+  - Returns "Error: Permission denied" if not authorized`,
+    targetServer
+  );
 
-// Get system config
-registerToolWithAnnotations(
-  systemConfigTools.systemConfigGet,
-  {
-    readOnlyHint: true,
-    destructiveHint: false,
-    idempotentHint: true,
-    openWorldHint: false
-  },
-  'Get System Configuration by ID',
-  `Retrieve a specific system configuration by ID.
+  registerToolWithAnnotations(
+    systemConfigTools.systemConfigGet,
+    { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
+    'Get System Configuration by ID',
+    `Retrieve a specific system configuration by ID.
 
 Args:
   - id (number): Configuration ID (required)
@@ -572,20 +454,15 @@ Examples:
   - Use when: "Get config with ID 1" -> params with id=1
 
 Error Handling:
-  - Returns "Error: Resource not found" if config ID doesn't exist`
-);
+  - Returns "Error: Resource not found" if config ID doesn't exist`,
+    targetServer
+  );
 
-// Create system config
-registerToolWithAnnotations(
-  systemConfigTools.systemConfigCreate,
-  {
-    readOnlyHint: false,
-    destructiveHint: false,
-    idempotentHint: false,
-    openWorldHint: false
-  },
-  'Create System Configuration',
-  `Create a new system configuration item.
+  registerToolWithAnnotations(
+    systemConfigTools.systemConfigCreate,
+    { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false },
+    'Create System Configuration',
+    `Create a new system configuration item.
 
 Args:
   - configKey (string): Configuration key (required)
@@ -600,20 +477,15 @@ Examples:
   - Use when: "Create config for max upload size" -> params with configKey="maxUploadSize", configValue="10485760"
 
 Error Handling:
-  - Returns "Error: Data already exists" if config key is duplicated`
-);
+  - Returns "Error: Data already exists" if config key is duplicated`,
+    targetServer
+  );
 
-// Update system config
-registerToolWithAnnotations(
-  systemConfigTools.systemConfigUpdate,
-  {
-    readOnlyHint: false,
-    destructiveHint: false,
-    idempotentHint: true,
-    openWorldHint: false
-  },
-  'Update System Configuration',
-  `Update an existing system configuration.
+  registerToolWithAnnotations(
+    systemConfigTools.systemConfigUpdate,
+    { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false },
+    'Update System Configuration',
+    `Update an existing system configuration.
 
 Args:
   - id (number): Configuration ID to update (required)
@@ -629,20 +501,15 @@ Examples:
   - Use when: "Update config 5 value to 100" -> params with id=5, configValue="100"
 
 Error Handling:
-  - Returns "Error: Resource not found" if config ID doesn't exist`
-);
+  - Returns "Error: Resource not found" if config ID doesn't exist`,
+    targetServer
+  );
 
-// Delete system config
-registerToolWithAnnotations(
-  systemConfigTools.systemConfigDelete,
-  {
-    readOnlyHint: false,
-    destructiveHint: true,
-    idempotentHint: false,
-    openWorldHint: false
-  },
-  'Delete System Configuration',
-  `Delete a system configuration item.
+  registerToolWithAnnotations(
+    systemConfigTools.systemConfigDelete,
+    { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: false },
+    'Delete System Configuration',
+    `Delete a system configuration item.
 
 Args:
   - id (number): Configuration ID to delete (required)
@@ -654,24 +521,16 @@ Examples:
   - Use when: "Delete config 10" -> params with id=10
 
 Error Handling:
-  - Returns "Error: Resource not found" if config ID doesn't exist`
-);
-
-// ============================================================================
-// Register Order Management Tools
-// ============================================================================
+  - Returns "Error: Resource not found" if config ID doesn't exist`,
+    targetServer
+  );
 
-// List orders
-registerToolWithAnnotations(
-  orderTools.orderList,
-  {
-    readOnlyHint: true,
-    destructiveHint: false,
-    idempotentHint: true,
-    openWorldHint: true
-  },
-  'List Orders',
-  `List and search orders in the admin system with pagination and filtering.
+  // Order management tools
+  registerToolWithAnnotations(
+    orderTools.orderList,
+    { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
+    'List Orders',
+    `List and search orders in the admin system with pagination and filtering.
 
 This tool retrieves a paginated list of orders from the system.
 
@@ -692,20 +551,15 @@ Examples:
   - Use when: "Get pending orders" -> params with filters='{"status": "pending"}'
 
 Error Handling:
-  - Returns "Error: Permission denied" if not authorized`
-);
+  - Returns "Error: Permission denied" if not authorized`,
+    targetServer
+  );
 
-// Get order
-registerToolWithAnnotations(
-  orderTools.orderGet,
-  {
-    readOnlyHint: true,
-    destructiveHint: false,
-    idempotentHint: true,
-    openWorldHint: false
-  },
-  'Get Order by ID',
-  `Retrieve detailed information about a specific order by ID.
+  registerToolWithAnnotations(
+    orderTools.orderGet,
+    { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
+    'Get Order by ID',
+    `Retrieve detailed information about a specific order by ID.
 
 Args:
   - id (number): Order ID (required)
@@ -717,8 +571,56 @@ Examples:
   - Use when: "Get order with ID 12345" -> params with id=12345
 
 Error Handling:
-  - Returns "Error: Resource not found" if order ID doesn't exist`
-);
+  - Returns "Error: Resource not found" if order ID doesn't exist`,
+    targetServer
+  );
+}
+
+// ============================================================================
+// Helper: Register Tool with Annotations
+// ============================================================================
+
+interface ToolRegistration {
+  name: string;
+  schema?: z.ZodSchema;
+  // eslint-disable-next-line @typescript-eslint/no-explicit-any
+  handler: (...args: any[]) => any;
+}
+
+interface ToolAnnotations {
+  readOnlyHint?: boolean;
+  destructiveHint?: boolean;
+  idempotentHint?: boolean;
+  openWorldHint?: boolean;
+}
+
+function registerToolWithAnnotations(
+  registration: ToolRegistration,
+  annotations: ToolAnnotations,
+  title: string,
+  description: string,
+  targetServer: McpServer
+): void {
+  const { name, schema, handler } = registration;
+
+  // Build tool options
+  // eslint-disable-next-line @typescript-eslint/no-explicit-any
+  const toolOptions: any = {
+    title,
+    description
+  };
+
+  // Add input schema if provided
+  if (schema) {
+    toolOptions.inputSchema = schema;
+  }
+
+  // Add annotations
+  toolOptions.annotations = annotations;
+
+  // Register the tool
+  targetServer.registerTool(name, toolOptions, handler);
+}
 
 // ============================================================================
 // Start HTTP Server with Streamable Transport
@@ -748,7 +650,10 @@ async function runHTTP() {
       getApiClient().setToken(token);
     }
 
-    // Create a new transport for each request (stateless, prevents request ID collisions)
+    // Create a new server instance for each request to avoid "Already connected" error
+    const requestServer = createMcpServer();
+
+    // Create a new transport for this request
     const transport = new StreamableHTTPServerTransport({
       sessionIdGenerator: undefined,
       enableJsonResponse: true
@@ -760,11 +665,11 @@ async function runHTTP() {
         transport.close();
       });
 
-      // Connect server to transport and handle request
-      await server.connect(transport);
+      // Connect the server instance to transport and handle request
+      await requestServer.connect(transport);
       await transport.handleRequest(req, res, req.body);
     } finally {
-      // Always close transport to allow reconnection on next request
+      // Close transport to clean up resources
       transport.close();
     }
   });