动态MCP SSE服务器

一个基于Spring Boot的动态MCP SSE服务器,通过单一端点提供多种工具集,支持运行时工具发布和会话隔离。

  • by luckyHuangShuang
  • Java
  • 本地部署
  • 1 星标
  • 更新于
在 GitHub 查看 luckyHuangShuang/dynamic-mcp-server

使用说明

Dynamic MCP SSE Server

One Endpoint, Infinite Toolkits
A dynamic MCP (Model Context Protocol) SSE Server built with Spring Boot.
Different clients get different tool sets from the same endpoint — just change the URL parameter.

Quick Start • Architecture • API Reference • Production Deployment • Comparison

English | 中文


Client A → GET /sse?id=weather    → tools: [weather_query, weather_forecast]
Client B → GET /sse?id=translate  → tools: [text_translate, language_detect]
Client C → GET /sse?id=my_toolkit → tools: [your_tool_1, your_tool_2, ...]

第一个开源 Java/Spring引导 实现每会话动态MCP工具路由。 受到百度动态MCP方法的启发。


问题

今天的MCP生态系统是 默认情况下为静态每个AI客户端(Claude Desktop、Cursor、Windsurf……)都要求您在配置文件中硬编码每个MCP服务器:

{
  "mcpServers": {
    "weather": { "command": "npx", "args": ["weather-mcp-server"] },
    "translate": { "command": "npx", "args": ["translate-mcp-server"] },
    "search": { "command": "npx", "args": ["search-mcp-server"] }
  }
}

这造成了真正的问题:

  • 每个工具包=一个单独的服务器进程(资源浪费)
  • 添加新工具包意味着编辑配置+重新启动客户端
  • 无运行时工具发布——为每个新工具部署一个新服务器
  • 没有每个用户/每个租户的工具隔离
  • 没有集中的工具管理或治理

解决方案

动态MCP SSE服务器 将N台静态服务器替换为 一个动态服务器:

核心功能

特性 描述
基于URL的工具包路由 GET /sse?id=weather 归还气象工具; ?id=translate 返回翻译工具。相同的端点,不同的工具。
运行时工具发布 通过REST API添加/删除工具-无需重新启动,无需重新部署。
实时更改通知 已连接的客户端接收 notifications/tools/list_changed 工具更新时立即生效。
工具包隔离 每个SSE会话都绑定到一个工具包。A. translate 会话无法调用 weather_query --服务器强制执行此操作。
可插拔存储 ToolkitRegistry 是一个接口。默认值:内存中。在PostgreSQL、MySQL、Redis或任何后端进行交换。
零外部依赖 基本实现只需要Spring Boot。没有数据库,没有消息队列,没有Redis。
符合MCP协议 全面实施MCP SSE传输: initialize, tools/list, tools/call, ping,更改通知。

快速开始

先决条件

  • JDK 17+
  • Maven 3.6+

1.启动服务器

cd examples/dynamic-mcp-server
mvn spring-boot:run

服务器启动于 http://localhost:8080 有两个内置的示例工具包:

工具包ID 工具
weather weather_query --查询一个城市的当前天气
weather_forecast --获取多日天气预报
translate text_translate --在语言之间翻译文本
language_detect --检测文本语言

2.从AI客户端连接

克劳德桌面 (claude_desktop_config.json):

{
  "mcpServers": {
    "my-weather-tools": {
      "url": "http://localhost:8080/sse?id=weather"
    }
  }
}

光标 (.cursor/mcp.json):

{
  "mcpServers": {
    "my-translate-tools": {
      "url": "http://localhost:8080/sse?id=translate"
    }
  }
}

同一台服务器,不同 id --每个客户端看到的工具完全不同。

3.使用curl进行验证

# Step 1: Check available toolkits
curl http://localhost:8080/api/toolkits
# → ["weather","translate"]

# Step 2: Open SSE connection (keep it running in a terminal)
curl -N "http://localhost:8080/sse?id=weather"
# → event:endpoint
# → data:/mcp/message?sessionId=a1b2c3d4-xxxx-xxxx-xxxx-xxxxxxxxxxxx

# Step 3: In another terminal, send MCP initialize
curl -X POST "http://localhost:8080/mcp/message?sessionId=<YOUR_SESSION_ID>" \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "initialize",
    "params": {
      "protocolVersion": "2024-11-05",
      "capabilities": {},
      "clientInfo": {"name": "curl-test", "version": "1.0"}
    }
  }'

# Step 4: Request tools list
curl -X POST "http://localhost:8080/mcp/message?sessionId=<YOUR_SESSION_ID>" \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}'
# → SSE stream receives: weather_query + weather_forecast (NOT translate tools)

# Step 5: Call a tool
curl -X POST "http://localhost:8080/mcp/message?sessionId=<YOUR_SESSION_ID>" \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "id": 3,
    "method": "tools/call",
    "params": {"name": "weather_query", "arguments": {"city": "Beijing"}}
  }'
# → "Weather in Beijing: Sunny, 25°C, humidity 40%"

4.运行时发布工具

# Create a new toolkit
curl -X POST http://localhost:8080/api/toolkits \
  -H "Content-Type: application/json" \
  -d '{"id":"search","name":"Search Toolkit","description":"Web search tools"}'

# Add a tool
curl -X POST http://localhost:8080/api/toolkits/search/tools \
  -H "Content-Type: application/json" \
  -d '{
    "name": "web_search",
    "description": "Search the web for information",
    "inputSchema": "{\"type\":\"object\",\"properties\":{\"query\":{\"type\":\"string\",\"description\":\"Search query\"}},\"required\":[\"query\"]}"
  }'

# Instantly available: GET /sse?id=search now returns web_search
# Already-connected clients receive tools/list_changed notification

建筑

请求流

动态路由的工作原理

魔法发生在 3条逻辑线:

1. GET /sse?id=weather      → Session created, bound to toolkitId="weather"
2. POST tools/list          → Lookup session → get toolkitId → return ONLY weather tools
3. POST tools/call          → Lookup session → verify tool belongs to toolkit → execute or reject

没有复杂的路由规则。没有中间件链。只是: session会记住它的工具包,每个请求都会通过它进行过滤。

工具包隔离

实时更改通知

项目结构

src/main/java/com/alibaba/cloud/ai/examples/dynamicmcp/
│
├── DynamicMcpServerApplication.java          # Spring Boot entry point
│
├── config/
│   └── SampleToolkitInitializer.java         # Registers demo toolkits on startup
│                                              # (weather + translate)
│
├── registry/                                  # === Storage Layer ===
│   ├── ToolkitDefinition.java                # Data model: Toolkit → List<ToolInfo>
│   │                                          #   ToolInfo = name + description + schema + handler
│   ├── ToolkitRegistry.java                  # Interface: register/get/add/remove + change listener
│   │                                          #   (implement this for DB-backed storage)
│   └── InMemoryToolkitRegistry.java          # Default: ConcurrentHashMap-based implementation
│
├── session/                                   # === Session Layer ===
│   └── McpSessionManager.java                # Maps sessionId → (toolkitId, SseEmitter)
│                                              #   Creates, lookups, and cleans up sessions
│
├── protocol/                                  # === Protocol Layer ===
│   └── McpProtocolHandler.java               # JSON-RPC dispatcher:
│                                              #   initialize → server capabilities
│                                              #   tools/list → filtered by session's toolkit
│                                              #   tools/call → validated + executed
│                                              #   ping → pong
│
└── controller/                                # === Transport Layer ===
    ├── McpSseController.java                 # MCP SSE transport endpoints:
    │                                          #   GET  /sse?id={toolkitId} → SSE stream
    │                                          #   POST /mcp/message?sessionId={id} → JSON-RPC
    │
    └── ToolkitAdminController.java           # Management REST API:
                                               #   CRUD toolkits and tools at runtime

API 参考

MCP传输端点

GET /sse?id={toolkitId}

建立绑定到特定工具包的SSE(服务器发送事件)连接。

参数:

名称 类型 必填 描述
id string 是 工具包标识符(例如。, weather, translate)

答复: SSE事件流。第一个事件:

event: endpoint
data: /mcp/message?sessionId=a1b2c3d4-xxxx-xxxx-xxxx-xxxxxxxxxxxx

错误: 如果工具包返回SSE错误 id 不存在。


POST /mcp/message?sessionId={sessionId}

向MCP服务器发送JSON-RPC消息。响应通过SSE流传递。

参数:

名称 类型 必填 描述
sessionId string 是 会话ID来自 endpoint 事件

请求正文: JSON-RPC 2.0消息

支持的方法:

方法 描述 响应
initialize MCP握手 服务器信息+功能
notifications/initialized 客户端就绪信号 (无响应--通知)
tools/list 列出可用工具 仅此会话工具包中的工具
tools/call 执行工具 工具结果或错误
ping 健康检查 结果为空 {}

tools/list 响应示例 (为 ?id=weather 会话):

{
  "jsonrpc": "2.0",
  "id": 2,
  "result": {
    "tools": [
      {
        "name": "weather_query",
        "description": "Query current weather for a given city",
        "inputSchema": {
          "type": "object",
          "properties": {
            "city": { "type": "string", "description": "City name, e.g. Beijing" }
          },
          "required": ["city"]
        }
      },
      {
        "name": "weather_forecast",
        "description": "Get 3-day weather forecast for a given city",
        "inputSchema": { "..." }
      }
    ]
  }
}

tools/call 错误示例 (从错误的工具包调用工具):

{
  "jsonrpc": "2.0",
  "id": 3,
  "error": {
    "code": -32602,
    "message": "Tool 'weather_query' not found in toolkit 'translate'"
  }
}

工具包管理API

GET /api/toolkits

列出所有已注册的工具包ID。

curl http://localhost:8080/api/toolkits
# → ["weather", "translate"]

GET /api/toolkits/{id}

获取包括所有工具在内的工具包详细信息。

curl http://localhost:8080/api/toolkits/weather
{
  "id": "weather",
  "name": "Weather Toolkit",
  "description": "Weather query and forecast tools",
  "tools": [
    {
      "name": "weather_query",
      "description": "Query current weather for a given city",
      "inputSchema": "{ ... }"
    },
    {
      "name": "weather_forecast",
      "description": "Get 3-day weather forecast for a given city",
      "inputSchema": "{ ... }"
    }
  ]
}

POST /api/toolkits

创建一个新的工具包。

curl -X POST http://localhost:8080/api/toolkits \
  -H "Content-Type: application/json" \
  -d '{
    "id": "search",
    "name": "Search Toolkit",
    "description": "Web search and indexing tools"
  }'
# → {"id": "search", "status": "created"}

POST /api/toolkits/{id}/tools

将工具添加到现有工具包中。 触发器 tools/list_changed 通知 此工具包的所有连接会话。

curl -X POST http://localhost:8080/api/toolkits/search/tools \
  -H "Content-Type: application/json" \
  -d '{
    "name": "web_search",
    "description": "Search the web for information",
    "inputSchema": "{\"type\":\"object\",\"properties\":{\"query\":{\"type\":\"string\"}},\"required\":[\"query\"]}"
  }'
# → {"toolkit": "search", "tool": "web_search", "status": "added"}

DELETE /api/toolkits/{id}/tools/{toolName}

从工具箱中删除工具。触发更改通知。

curl -X DELETE http://localhost:8080/api/toolkits/search/tools/web_search
# → 204 No Content

DELETE /api/toolkits/{id}

删除整个工具包。

curl -X DELETE http://localhost:8080/api/toolkits/search
# → 204 No Content

生产部署

默认值 InMemoryToolkitRegistry 非常适合演示和开发。对于生产,您需要持久存储、身份验证和横向扩展。这是完整的设计。

数据库模式(PostgreSQL)

-- ============================================================
-- Table: mcp_toolkit
-- Stores toolkit definitions (each toolkit = a group of tools)
-- ============================================================
CREATE TABLE mcp_toolkit (
    id              VARCHAR(64)     PRIMARY KEY,
    name            VARCHAR(128)    NOT NULL,
    description     TEXT,
    status          SMALLINT        DEFAULT 1,        -- 0=disabled, 1=enabled
    created_by      VARCHAR(128),                      -- creator identifier
    created_at      TIMESTAMP       DEFAULT NOW(),
    updated_at      TIMESTAMP       DEFAULT NOW()
);

COMMENT ON TABLE  mcp_toolkit IS 'MCP toolkit definitions - each toolkit groups related tools';
COMMENT ON COLUMN mcp_toolkit.status IS '0=disabled (hidden from SSE), 1=enabled';

-- ============================================================
-- Table: mcp_tool
-- Individual tools within a toolkit
-- ============================================================
CREATE TABLE mcp_tool (
    id              BIGSERIAL       PRIMARY KEY,
    toolkit_id      VARCHAR(64)     NOT NULL REFERENCES mcp_toolkit(id) ON DELETE CASCADE,
    name            VARCHAR(128)    NOT NULL,
    description     TEXT            NOT NULL,
    input_schema    JSONB           NOT NULL,          -- JSON Schema for tool parameters
    endpoint_url    VARCHAR(512),                       -- Backend service URL (proxy mode)
    endpoint_type   VARCHAR(32)     DEFAULT 'internal', -- internal / http / grpc / mcp-bridge
    config          JSONB,                              -- Extra config: headers, timeout, retry, etc.
    status          SMALLINT        DEFAULT 1,          -- 0=disabled, 1=enabled
    sort_order      INT             DEFAULT 0,          -- Display order within toolkit
    created_at      TIMESTAMP       DEFAULT NOW(),
    updated_at      TIMESTAMP       DEFAULT NOW(),
    UNIQUE (toolkit_id, name)
);

CREATE INDEX idx_mcp_tool_toolkit ON mcp_tool(toolkit_id, status);

COMMENT ON COLUMN mcp_tool.endpoint_type IS
  'internal = handler in JVM; http = proxy to REST API; grpc = proxy to gRPC; mcp-bridge = proxy to another MCP server';
COMMENT ON COLUMN mcp_tool.config IS
  'JSON: {"headers": {...}, "timeout_ms": 5000, "retry": 2, "auth": "bearer:xxx"}';

-- ============================================================
-- Table: mcp_access
-- API key based access control and toolkit authorization
-- ============================================================
CREATE TABLE mcp_access (
    id              BIGSERIAL       PRIMARY KEY,
    api_key         VARCHAR(128)    UNIQUE NOT NULL,
    name            VARCHAR(128)    NOT NULL,           -- Human-readable name (e.g., "Team Alpha Key")
    toolkit_ids     TEXT[]          NOT NULL,            -- Allowed toolkit IDs
    rate_limit      INT             DEFAULT 100,         -- Max requests per minute
    expires_at      TIMESTAMP,                           -- NULL = never expires
    status          SMALLINT        DEFAULT 1,           -- 0=revoked, 1=active
    created_at      TIMESTAMP       DEFAULT NOW()
);

CREATE INDEX idx_mcp_access_key ON mcp_access(api_key) WHERE status = 1;

COMMENT ON TABLE mcp_access IS
  'API key authentication: each key grants access to specific toolkits';

-- ============================================================
-- Table: mcp_tool_call_log (Optional — Observability)
-- Audit log for tool invocations
-- ============================================================
CREATE TABLE mcp_tool_call_log (
    id              BIGSERIAL       PRIMARY KEY,
    session_id      VARCHAR(64)     NOT NULL,
    toolkit_id      VARCHAR(64)     NOT NULL,
    tool_name       VARCHAR(128)    NOT NULL,
    arguments       JSONB,
    result_text     TEXT,
    is_error        BOOLEAN         DEFAULT FALSE,
    duration_ms     INT,
    called_at       TIMESTAMP       DEFAULT NOW()
);

CREATE INDEX idx_tool_call_log_time ON mcp_tool_call_log(called_at DESC);
CREATE INDEX idx_tool_call_log_toolkit ON mcp_tool_call_log(toolkit_id, called_at DESC);

COMMENT ON TABLE mcp_tool_call_log IS
  'Audit trail: who called what tool, when, with what arguments, and what happened';
MySQL version (click to expand)
CREATE TABLE mcp_toolkit (
    id              VARCHAR(64)     PRIMARY KEY,
    name            VARCHAR(128)    NOT NULL,
    description     TEXT,
    status          TINYINT         DEFAULT 1,
    created_by      VARCHAR(128),
    created_at      DATETIME        DEFAULT CURRENT_TIMESTAMP,
    updated_at      DATETIME        DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE mcp_tool (
    id              BIGINT          AUTO_INCREMENT PRIMARY KEY,
    toolkit_id      VARCHAR(64)     NOT NULL,
    name            VARCHAR(128)    NOT NULL,
    description     TEXT            NOT NULL,
    input_schema    JSON            NOT NULL,
    endpoint_url    VARCHAR(512),
    endpoint_type   VARCHAR(32)     DEFAULT 'internal',
    config          JSON,
    status          TINYINT         DEFAULT 1,
    sort_order      INT             DEFAULT 0,
    created_at      DATETIME        DEFAULT CURRENT_TIMESTAMP,
    updated_at      DATETIME        DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    UNIQUE KEY uk_toolkit_tool (toolkit_id, name),
    KEY idx_toolkit_status (toolkit_id, status),
    FOREIGN KEY (toolkit_id) REFERENCES mcp_toolkit(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE mcp_access (
    id              BIGINT          AUTO_INCREMENT PRIMARY KEY,
    api_key         VARCHAR(128)    UNIQUE NOT NULL,
    name            VARCHAR(128)    NOT NULL,
    toolkit_ids     JSON            NOT NULL,  -- MySQL doesn't support TEXT[], use JSON array
    rate_limit      INT             DEFAULT 100,
    expires_at      DATETIME,
    status          TINYINT         DEFAULT 1,
    created_at      DATETIME        DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE mcp_tool_call_log (
    id              BIGINT          AUTO_INCREMENT PRIMARY KEY,
    session_id      VARCHAR(64)     NOT NULL,
    toolkit_id      VARCHAR(64)     NOT NULL,
    tool_name       VARCHAR(128)    NOT NULL,
    arguments       JSON,
    result_text     TEXT,
    is_error        TINYINT(1)      DEFAULT 0,
    duration_ms     INT,
    called_at       DATETIME        DEFAULT CURRENT_TIMESTAMP,
    KEY idx_call_log_time (called_at DESC),
    KEY idx_call_log_toolkit (toolkit_id, called_at DESC)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

实现结构

关键实施点

1.JdbcToolkitRegistry

替换 InMemoryToolkitRegistry 使用数据库支持的实现:

@Component
@Profile("production")
public class JdbcToolkitRegistry implements ToolkitRegistry {

    // tools/list → SELECT * FROM mcp_tool WHERE toolkit_id = ? AND status = 1
    // addTool    → INSERT INTO mcp_tool (...) VALUES (...)
    //              then → NOTIFY tool_changed, 'weather'  (PostgreSQL)
    //              or   → redisTemplate.convertAndSend("tool_changed", "weather")
    // ...
}

2.工具执行路由(代理模式)

这 endpoint_type 领域 mcp_tool 决定工具的执行方式:

endpoint_type = "internal"
  → Execute Java handler in JVM (same as demo mode)

endpoint_type = "http"
  → POST {endpoint_url}
    Headers: from mcp_tool.config.headers
    Body:    tool arguments (JSON)
    Response text → tool result

endpoint_type = "grpc"
  → gRPC call to {endpoint_url}

endpoint_type = "mcp-bridge"
  → Forward to another MCP server as tools/call
    Enables chaining: Dynamic MCP → upstream MCP server

这意味着您可以注册实际上由以下支持的工具 任何现有的HTTP API --无需更改代码,只需在数据库中添加一行即可。

3.变更检测与通知

方法 最佳选择 方法
PostgreSQL LISTEN/NOTIFY 单实例或PG原生 NOTIFY tool_changed, 'weather' 插入/更新后
Redis发布/订阅 多实例横向扩展 发布到 tool_changed 频道
轮询 最简单,不需要基础设施 检查 updated_at > last_checked 每N秒

4.API密钥认证

添加a HandlerInterceptor 上 /sse:

GET /sse?id=weather&apiKey=sk-xxxx
     │                    │
     │                    ▼
     │         SELECT toolkit_ids FROM mcp_access
     │         WHERE api_key = 'sk-xxxx' AND status = 1
     │                    │
     │                    ▼
     │         toolkit_ids contains 'weather'?
     │            YES → proceed
     │            NO  → 403 Forbidden
     ▼
 Create session

5.缓存策略

Request flow:  Session → Redis Cache → PostgreSQL

tools/list:    Redis GET toolkit:{id}:tools
                 ├─ HIT  → return cached
                 └─ MISS → SELECT FROM mcp_tool → cache with 60s TTL

tool change:   Write to DB → invalidate Redis key → notify sessions

多实例水平缩放

上面的生产架构图显示了多实例部署模式:具有粘性会话的负载均衡器将客户端分布在实例A和实例B之间,Redis Pub/Sub在实例之间广播工具更改事件。

要点:

  • 需要粘性会话 --SSE端点返回 sessionId,后续POST必须到达同一实例。使用负载平衡器cookie关联或会话感知路由。
  • 刀具更换流程 --实例A写入DB+发布到Redis→ 实例B收到事件→ 这两个实例都会通知其本地会话。

比较

|特点| 动态MCP SSE服务器 |scitara cto/
动态mcp服务器|元工具ai/
微软/
mcp网关| |---------|:---:|:---:|:---:|:---:| | 语言 |Java/Spring Boot | TypeScript | TypeScript | C#/。NET| | 路由机制 |URL参数 ?id=xxx |URL参数 ?apiKey=xxx |命名空间|工具名称路由| | 运行时工具发布 |REST API | REST API |管理员界面| K8s CRD| | 实时更改通知 |SSE推| SSE推|-|-| | 会话级隔离 |每个工具包|每个用户|每个命名空间|每个服务器| | 基础依赖关系 |无| MongoDB | PostgreSQL | Kubernetes| | 数据库备份存储 |可插拔接口|仅限MongoDB | PostgreSQL |-| | 代理模式 |设计(endpoint_type)|-|内置|内置| | 复杂性 |轻量级(约9个文件)|中等|重型|重型| | 生产就绪认证 |设计(API密钥)|内置|内置| K8s RBAC|

何时使用何种方法

  • 这个项目 --您处于Java/Spring生态系统中,想要完全控制的轻量级内容,并且需要每个工具包的动态路由
  • metamcp --您需要一个具有UI的全功能MCP聚合器,不介意TypeScript,并希望将许多现有的MCP服务器合并到一个
  • microsoft/mcp网关 --您使用的是Kubernetes,需要基于CRD管理的基础设施级MCP路由
  • scitara动态mcp服务器 --你身处TypeScript生态系统,希望与MongoDB实现按用户工具隔离

技术栈

组件 技术
语言 Java 17
框架 Spring Boot 3.5
苏格兰和南方能源公司运输 春季 SseEmitter
MCP类型 MCP SDK 0.14.0(McpSchema 仅)
协议 SSE上的JSON-RPC 2.0(自定义实现)
存储 可插拔(ToolkitRegistry 接口)
默认存储 ConcurrentHashMap (记忆中)

MCP SDK使用说明: 此项目不使用SDK的内置 McpServer 或 HttpServletSseServerTransportProvider --他们的 tools/list 是全局的,不能按会话进行筛选。相反,我们直接使用Spring实现MCP SSE传输协议 SseEmitter,这使我们能够完全控制每个会话的工具路由。


贡献

欢迎投稿!一些特别有价值的领域:

  • [ ] JdbcToolkitRegistry --PostgreSQL与MySQL的实现 ToolkitRegistry
  • []API密钥身份验证拦截器
  • []HTTP代理工具执行器(用于 endpoint_type = "http")
  • []Redis缓存层用于工具定义
  • []用于多实例变更通知的Redis发布/订阅
  • []用于工具包管理的管理web UI
  • []MCP网桥执行器(转发到上游MCP服务器)
  • []度量和可观察性(千分尺/开放遥测)
  • []每个API键的速率限制
  • []可流式HTTP传输(除SSE之外)

许可证

MIT许可证