MCP

AI Gateway 设计:用 MCP 构建企业级 AI 服务网关

探讨如何基于 Model Context Protocol 设计企业级 AI Gateway,实现统一的工具管理、权限控制和流量调度。

当企业内部有数十个 MCP Server、数百个工具时,如何统一管理它们的访问、权限和监控?AI Gateway 是解决这个问题的关键基础设施。

为什么需要 AI Gateway

在小规模场景下,MCP Client 直接连接 Server 就够了。但在企业环境中,面临以下挑战:

服务发现——Server 可能分布在不同的机器和网络中,Client 需要知道去哪里连接。

权限控制——不同的用户和 Agent 有不同的工具访问权限。

流量管理——需要限流、熔断、负载均衡。

审计合规——所有工具调用需要记录和审计。

版本管理——Server 可能有多个版本同时运行。

AI Gateway 作为所有 MCP 通信的统一入口,解决这些问题。

架构设计

┌──────────────────────────────────────────────┐
│                 AI Gateway                    │
│                                              │
│  ┌────────┐ ┌────────┐ ┌────────┐ ┌───────┐ │
│  │认证鉴权│ │限流熔断│ │路由分发│ │审计日志│ │
│  └────────┘ └────────┘ └────────┘ └───────┘ │
│                                              │
│  ┌─────────────────────────────────────────┐ │
│  │          工具注册中心                    │ │
│  └─────────────────────────────────────────┘ │
└──────────────────────────────────────────────┘
         │              │              │
    ┌────┴────┐    ┌────┴────┐    ┌────┴────┐
    │ Server A│    │ Server B│    │ Server C│
    │ v1.0    │    │ v2.0    │    │ v1.5    │
    └─────────┘    └─────────┘    └─────────┘

核心组件实现

工具注册中心

interface ToolRegistration {
  name: string;
  description: string;
  inputSchema: any;
  serverName: string;
  serverUrl: string;
  version: string;
  permissions: string[];
  rateLimit: number;
}

class ToolRegistry {
  private tools: Map<string, ToolRegistration> = new Map();

  register(tool: ToolRegistration) {
    this.tools.set(tool.name, tool);
    console.error(`[Registry] 注册工具: ${tool.name} (${tool.serverName})`);
  }

  unregister(toolName: string) {
    this.tools.delete(toolName);
  }

  get(toolName: string): ToolRegistration | undefined {
    return this.tools.get(toolName);
  }

  list(filter?: { serverName?: string; permission?: string }): ToolRegistration[] {
    let tools = Array.from(this.tools.values());

    if (filter?.serverName) {
      tools = tools.filter(t => t.serverName === filter.serverName);
    }

    if (filter?.permission) {
      tools = tools.filter(t => t.permissions.includes(filter.permission));
    }

    return tools;
  }
}

认证鉴权中间件

interface AuthContext {
  userId: string;
  roles: string[];
  permissions: string[];
}

class AuthMiddleware {
  private apiKeys: Map<string, AuthContext> = new Map();

  registerApiKey(key: string, context: AuthContext) {
    this.apiKeys.set(key, context);
  }

  authenticate(apiKey: string): AuthContext | null {
    return this.apiKeys.get(apiKey) || null;
  }

  authorize(context: AuthContext, tool: ToolRegistration): boolean {
    // 检查用户是否有权限调用该工具
    return tool.permissions.some(p => context.permissions.includes(p));
  }
}

限流器

class RateLimiter {
  private windows: Map<string, number[]> = new Map();

  check(key: string, limit: number, windowMs: number = 60000): boolean {
    const now = Date.now();
    const timestamps = this.windows.get(key) || [];
    const recent = timestamps.filter(t => now - t < windowMs);

    if (recent.length >= limit) {
      return false;
    }

    recent.push(now);
    this.windows.set(key, recent);
    return true;
  }

  getRemaining(key: string, limit: number, windowMs: number = 60000): number {
    const now = Date.now();
    const timestamps = this.windows.get(key) || [];
    const recent = timestamps.filter(t => now - t < windowMs);
    return Math.max(0, limit - recent.length);
  }
}

审计日志

interface AuditEntry {
  timestamp: string;
  userId: string;
  toolName: string;
  serverName: string;
  arguments: any;
  result: 'success' | 'error';
  duration: number;
  error?: string;
}

class AuditLogger {
  private logFile: fs.WriteStream;

  constructor(logPath: string) {
    this.logFile = fs.createWriteStream(logPath, { flags: 'a' });
  }

  log(entry: AuditEntry) {
    this.logFile.write(JSON.stringify(entry) + '\n');
  }
}

Gateway 主体

import express from 'express';

class AIGateway {
  private app = express();
  private registry: ToolRegistry;
  private auth: AuthMiddleware;
  private rateLimiter: RateLimiter;
  private audit: AuditLogger;

  constructor() {
    this.registry = new ToolRegistry();
    this.auth = new AuthMiddleware();
    this.rateLimiter = new RateLimiter();
    this.audit = new AuditLogger('/var/log/ai-gateway/audit.log');

    this.setupRoutes();
  }

  private setupRoutes() {
    this.app.use(express.json());

    // 工具列表
    this.app.get('/tools', (req, res) => {
      const apiKey = req.headers['x-api-key'] as string;
      const context = this.auth.authenticate(apiKey);
      if (!context) {
        return res.status(401).json({ error: 'Unauthorized' });
      }

      const tools = this.registry.list({ permission: context.permissions[0] });
      res.json({ tools });
    });

    // 工具调用
    this.app.post('/tools/:name/call', async (req, res) => {
      const startTime = Date.now();
      const apiKey = req.headers['x-api-key'] as string;
      const toolName = req.params.name;

      // 认证
      const context = this.auth.authenticate(apiKey);
      if (!context) {
        return res.status(401).json({ error: 'Unauthorized' });
      }

      // 查找工具
      const tool = this.registry.get(toolName);
      if (!tool) {
        return res.status(404).json({ error: 'Tool not found' });
      }

      // 鉴权
      if (!this.auth.authorize(context, tool)) {
        return res.status(403).json({ error: 'Forbidden' });
      }

      // 限流
      if (!this.rateLimiter.check(context.userId, tool.rateLimit)) {
        return res.status(429).json({ error: 'Rate limit exceeded' });
      }

      // 调用
      try {
        const result = await this.callServer(tool, req.body.arguments);

        this.audit.log({
          timestamp: new Date().toISOString(),
          userId: context.userId,
          toolName,
          serverName: tool.serverName,
          arguments: req.body.arguments,
          result: 'success',
          duration: Date.now() - startTime,
        });

        res.json(result);
      } catch (error) {
        this.audit.log({
          timestamp: new Date().toISOString(),
          userId: context.userId,
          toolName,
          serverName: tool.serverName,
          arguments: req.body.arguments,
          result: 'error',
          duration: Date.now() - startTime,
          error: error.message,
        });

        res.status(500).json({ error: error.message });
      }
    });
  }

  private async callServer(tool: ToolRegistration, args: any): Promise<any> {
    // 通过 HTTP 调用 MCP Server
    const response = await fetch(`${tool.serverUrl}/tools/${tool.name}/call`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ arguments: args }),
    });

    return response.json();
  }

  start(port: number) {
    this.app.listen(port, () => {
      console.error(`[Gateway] 监听端口 ${port}`);
    });
  }
}

常见问题(FAQ)

Gateway 会增加多少延迟?

通常增加 5-10ms,主要是认证和路由的开销。在大多数场景下可以接受。

Gateway 如何处理 Server 的高可用?

Gateway 可以对同一个工具配置多个 Server 实例,实现负载均衡和故障转移。

如何扩展 Gateway?

Gateway 是无状态的,可以通过增加实例进行水平扩展。工具注册中心可以使用 Redis 等外部存储实现共享。

总结

AI Gateway 是企业级 MCP 部署的关键基础设施。它将安全、路由、监控等横切关注点从各个 Server 中抽离出来,形成统一的管理层。在构建大规模 Agent 系统时,Gateway 是不可或缺的组件。