1. 项目缘起:为什么我们需要一个“万能格式转换器”?

在当今的软件开发与集成领域,我们正处在一个“协议爆炸”的时代。作为一名常年在一线折腾各种API、工具链和自动化流程的开发者,我几乎每天都要面对这样的场景:一个核心的业务逻辑,需要同时暴露给内部的管理后台(通过REST API)、集成到某个AI智能体(作为MCP技能)、打包成一个命令行工具(CLI)给运维同事使用,甚至可能还要为移动端提供一个轻量级的GraphQL端点。这不仅仅是写几份代码那么简单,更是一场关于数据格式、通信协议、认证方式和文档规范的“拉锯战”。

最让人疲惫的,不是实现功能本身,而是为每一个“访问格式”重复进行几乎相同的劳动:为REST API设计OpenAPI/Swagger规范,为MCP技能编写严格的Manifest和Schema,为CLI工具处理参数解析、帮助文本和子命令,还要确保它们背后的业务逻辑一致,数据模型同步。任何一个逻辑的改动,都意味着要在多个地方进行修改和测试,沟通成本和出错几率呈指数级上升。这种重复、琐碎且极易出错的“胶水代码”工作,严重拖慢了从想法到可交付物的速度。

于是,一个想法自然浮现:能不能只写一次核心逻辑,就让它自动适配成各种需要的形态?就像有一个“万能格式转换器”,输入是纯粹的业务函数,输出则是立即可用的API、MCP技能或CLI。这不仅仅是偷懒,更是对开发体验和交付效率的根本性提升。我想要的不是一个庞大的、沉重的企业级中间件,而是一个轻量、直接、开箱即用的“OneKey Gateway”——按下一个键,就能把想法“发射”到所有需要的平台。

这个项目的核心目标,就是构建这样一个“Omni Converter”(全能转换器)。它不是一个代理,也不是一个复杂的网关,它的定位更偏向于一个“编译器”或“生成器”,专注于将开发者定义的核心函数(Function),根据目标格式的规范,编译成相应的“外壳”。最终实现“编写一次,处处运行”的API、技能和工具交付,理想状态下,将这类集成工作的效率提升十倍。

2. 核心设计:OneKey Gateway的架构与工作原理

2.1 设计哲学:约定优于配置,函数即接口

这个项目的设计首要原则是“约定优于配置”(Convention Over Configuration)。我们不希望开发者为了生成一个CLI而去学习一套复杂的注解或配置语法。相反,我们希望通过最直观的方式——即函数签名(Function Signature)本身——来推断出一切所需信息。

一个普通的Python函数(或JavaScript/Go等语言中的类似结构)已经包含了大量信息:

  • 函数名 :天然可以作为CLI的命令名、API的端点路径的一部分或MCP技能的名称。
  • 参数 :每个参数的名称、类型(如 str , int , bool , List[str] )以及默认值,直接对应着CLI的参数选项、API的查询/路径/请求体参数,以及MCP技能的输入Schema。
  • 返回值类型 :定义了API的响应结构、CLI的输出格式(如JSON)以及MCP技能的结果Schema。
  • 文档字符串(Docstring) :这是宝藏。格式良好的Docstring可以自动被提取为API的接口描述、CLI的 --help 文本内容,以及MCP技能的描述信息。

因此,OneKey Gateway的核心输入,就是一个或多个遵循特定约定的函数。它的核心工作,就是解析这些函数,理解其意图,然后应用不同的“生成器插件”,产出对应的交付物。

2.2 系统架构拆解

整个系统可以划分为三个清晰的核心层次:

  1. 核心抽象层(Core Abstraction Layer)

    • 函数解析器(Function Parser) :负责加载用户提供的函数模块,利用抽象语法树(AST)解析或反射(Inspection)机制,提取出函数名、参数列表(包括名称、类型注解、默认值)、返回值类型注解以及文档字符串。这是所有转换的基石。
    • 统一函数模型(Uniform Function Model) :将解析出的信息,标准化为一个内部的中介数据结构。这个模型独立于任何目标格式,包含了函数的所有元数据,例如 Function(name=‘get_user‘, params=[Param(name=‘user_id‘, type=int, required=True)], return_type=User, description=‘...‘)
  2. 插件化生成层(Plugin-Based Generator Layer)

    • 这是系统的“肌肉”。每个生成器插件负责将 统一函数模型 转换到一种特定的目标格式。
    • REST API 生成器 :基于模型生成FastAPI/Flask的路由函数。例如,将 get_user(user_id: int) 映射为 GET /user/{user_id} 。它会自动处理路径参数、查询参数、请求体(Pydantic模型),并生成对应的OpenAPI文档。
    • MCP 技能生成器 :基于模型生成符合Model Context Protocol规范的技能Manifest( mcp.json )和工具定义(Tools)。它会将函数参数转换为JSON Schema,将函数描述填入,并生成一个适配器,使得MCP服务器可以调用原始函数。
    • CLI 生成器 :基于模型生成一个Typer或Click的CLI应用。函数名成为主命令或子命令,参数变成命令行选项或参数,类型注解用于类型验证和转换, bool 类型自动生成 --flag 风格选项。
    • GraphQL 生成器(可选) :将函数模型转换为GraphQL的Query或Mutation类型及解析器。
  3. 编排与输出层(Orchestration & Output Layer)

    • 构建管道(Build Pipeline) :用户通过一个简单的配置文件(如 omniconfig.yaml )或命令行指令,指定输入函数的位置和需要生成的输出格式列表(如 [‘rest‘, ‘mcp‘, ‘cli‘] )。
    • 网关核心(Gateway Core) :按顺序调用解析器和指定的生成器插件,协调整个转换流程。
    • 产物输出 :将各生成器产生的代码、配置文件、Dockerfile等,输出到指定的目录结构中,例如 ./build/rest/ , ./build/mcp/ , ./build/cli/

2.3 关键技术选型与考量

  • 语言选择:Python :因其在AI、数据科学和自动化领域的绝对主导地位,拥有极其丰富的生态(FastAPI, Pydantic, Typer, Click, AST/Inspect库),并且是MCP服务器的主流实现语言之一。原型验证和开发者体验最佳。
  • 类型注解的依赖 :强烈依赖现代Python的类型注解(Type Hints)。这是实现“约定优于配置”和自动Schema生成的关键。对于没有类型注解的旧代码,系统可以提供回退机制(如视为 Any 类型),但会强烈建议补充。
  • 插件系统 :采用动态加载( importlib )或简单的注册表模式,允许社区轻松贡献新的生成器(如gRPC、AsyncAPI、Slack Bot等)。
  • 配置极简化 :目标是通过函数签名和Docstring实现90%的配置。额外的配置(如认证方式、服务器端口、CLI工具名)通过一个极简的YAML文件或装饰器来提供,作为对“约定”的补充,而非替代。

3. 实战演练:从零构建一个用户管理模块的Omni交付

让我们通过一个完整的例子,看看如何将一个简单的用户管理模块,一键转换为REST API、MCP技能和CLI工具。

3.1 第一步:编写核心业务函数

我们创建一个 user_logic.py 文件,这里只包含最纯粹的业务逻辑。

# user_logic.py
from typing import List, Optional
from pydantic import BaseModel

# 数据模型
class User(BaseModel):
    id: int
    username: str
    email: str
    is_active: bool = True

# 模拟一个“数据库”
fake_db: dict[int, User] = {
    1: User(id=1, username="alice", email="alice@example.com"),
    2: User(id=2, username="bob", email="bob@example.com", is_active=False),
}

def get_user(user_id: int) -> Optional[User]:
    """
    根据用户ID获取用户信息。

    Args:
        user_id: 用户的唯一标识符。

    Returns:
        对应的用户对象,如果未找到则返回None。
    """
    return fake_db.get(user_id)

def list_users(active_only: bool = False) -> List[User]:
    """
    列出所有用户,可选择只列出活跃用户。

    Args:
        active_only: 如果为True,则只返回 is_active=True 的用户。

    Returns:
        用户列表。
    """
    users = fake_db.values()
    if active_only:
        return [u for u in users if u.is_active]
    return list(users)

def create_user(username: str, email: str) -> User:
    """
    创建一个新用户。

    Args:
        username: 用户名,必须唯一。
        email: 用户邮箱。

    Returns:
        新创建的用户对象。
    """
    new_id = max(fake_db.keys(), default=0) + 1
    user = User(id=new_id, username=username, email=email)
    fake_db[new_id] = user
    return user

要点解析

  1. 我们使用了Pydantic的 BaseModel 来定义数据结构,这不仅是良好的实践,其本身也包含了丰富的类型和验证信息,能被生成器完美利用。
  2. 每个函数都有完整的类型注解和格式规范的文档字符串(Google风格或类似)。这是自动化生成的“燃料”。
  3. 函数只关心业务逻辑(增删改查),不包含任何HTTP路由、命令行解析或MCP协议相关的代码。

3.2 第二步:配置OneKey Gateway并生成

假设我们的工具叫 omnic ,已经安装。我们创建一个简单的配置文件 omni.yaml

# omni.yaml
version: "1"
input:
  module: "./user_logic.py" # 指向我们的核心逻辑文件
  functions: ["get_user", "list_users", "create_user"] # 指定要转换的函数,留空则转换所有
output:
  formats:
    - type: "rest"
      config:
        framework: "fastapi" # 指定生成FastAPI应用
        port: 8000
        title: "User Management API"
    - type: "mcp"
      config:
        server_name: "user-tools"
        transport: "stdio" # MCP通信方式
    - type: "cli"
      config:
        cli_name: "userctl" # CLI工具的名称
        use_typer: true
  directory: "./build" # 所有产物输出到此目录

然后,运行命令:

omnic build --config omni.yaml

3.3 第三步:检视与使用生成产物

执行后, ./build 目录下会生成如下结构:

./build/
├── rest/
│   ├── main.py              # 完整的FastAPI应用入口
│   ├── requirements.txt     # 依赖文件
│   └── Dockerfile          # 可选的Docker镜像文件
├── mcp/
│   ├── mcp.json            # MCP技能清单文件
│   ├── server.py           # MCP服务器实现
│   └── requirements.txt
└── cli/
    ├── userctl             # 可执行的CLI脚本(或main.py)
    └── requirements.txt

3.3.1 使用生成的REST API 进入 ./build/rest ,安装依赖后运行 uvicorn main:app --reload 。你将立即获得一个完整的Swagger UI(位于 http://localhost:8000/docs ),其中包含了自动生成的三个端点:

  • GET /user/{user_id}
  • GET /users?active_only=false
  • POST /users (请求体为 {“username“: “...“, “email“: “...“} )

3.3.2 使用生成的MCP技能 进入 ./build/mcp ,安装依赖。这个MCP服务器可以通过标准输入输出与兼容MCP的客户端(如某些AI IDE)通信。生成的 mcp.json 会声明三个工具(Tools): get_user list_users create_user ,并附带从Docstring和类型生成的描述与JSON Schema。AI助手可以直接调用这些工具。

3.3.3 使用生成的CLI工具 进入 ./build/cli ,安装依赖(或使用 pipx install . )。你就可以在终端使用 userctl 命令了:

userctl get-user --user-id 1
userctl list-users --active-only
userctl create-user --username charlie --email charlie@example.com

帮助信息 userctl --help userctl create-user --help 也全部由函数Docstring自动生成。

4. 深入细节:生成器如何工作及关键配置

4.1 REST API生成器的内部机制

以FastAPI生成器为例,它的工作流程如下:

  1. 解析模型 :接收 get_user(user_id: int) -> User 统一函数模型
  2. 路由映射
    • 函数名 get_user 被转换为RESTful风格路径 /user/{user_id} (规则可配置,如支持复数 /users/{user_id} )。
    • GET POST PUT DELETE 等HTTP方法根据函数名语义推断(如 get_ 前缀用 GET create_ 前缀用 POST ),或通过装饰器/配置指定。
  3. 参数绑定
    • 路径参数 :所有没有默认值的、简单的标量参数(如 int , str )默认被视为路径参数 {user_id}
    • 查询参数 :所有有默认值的参数,或通过配置指定的参数,被转换为查询参数(如 active_only: bool = False 变成 ?active_only=true )。
    • 请求体 :如果参数是一个Pydantic BaseModel 类型,它将被自动包装为请求体参数 item: Item 。对于 create_user(username: str, email: str) ,生成器会智能地为其创建一个临时的Pydantic模型作为请求体。
  4. 响应模型 :根据返回值类型 User ,自动设置FastAPI的 response_model ,确保输出序列化和文档正确。
  5. 文档生成 :将函数Docstring的 Args Returns 部分,填充到OpenAPI的 description response_description 中。

注意 :对于复杂的参数场景(如混合了路径、查询和请求体),可以通过在原始函数的参数上添加特定的装饰器或元数据来提供提示,例如 @param.in_query , 而无需修改业务逻辑本身。

4.2 MCP技能生成器的适配逻辑

MCP(Model Context Protocol)要求技能以“工具(Tools)”的形式暴露。生成器需要:

  1. 创建Manifest :编写 mcp.json ,声明服务器名称、版本和提供的工具列表。
  2. 定义工具Schema :为每个函数生成一个JSON Schema对象,描述其输入。例如, get_user 工具的输入Schema会定义 user_id 为必需的整数类型。这直接从函数参数的类型注解转换而来。
  3. 实现工具调用适配器 :编写一个 server.py ,其中包含一个标准的MCP服务器框架。对于每个函数,生成一个对应的异步处理函数,该函数从MCP客户端接收符合Schema的参数,调用原始的业务函数,再将结果封装成MCP要求的格式返回。
  4. 处理流式响应(可选) :如果原始函数是一个生成器( yield ),生成器可以将其适配为MCP的 PartialResult 流式响应,这对于需要长时间运行或分块返回结果的技能非常有用。

4.3 CLI生成器的用户体验优化

CLI的体验至关重要。生成器基于Typer(推荐,因其易用性和类型集成度)或Click实现:

  1. 命令结构 :单个函数可以映射为一个顶级命令(如 userctl get-user ),或者一组相关函数可以组织在一个子命令组下(如 userctl user get )。
  2. 参数转换
    • 参数名从蛇形( snake_case )转换为更CLI风格的串形( kebab-case ),如 user_id 变成 --user-id
    • bool 类型参数自动生成两个选项: --active-only --no-active-only
    • 类型注解用于自动验证和转换输入字符串。例如, int 类型会确保输入被转换为整数。
  3. 帮助文本 :函数Docstring的整个描述成为命令的 help 文本, Args 部分被解析为每个参数的 help 文本。
  4. 输出格式化 :默认将返回值以JSON格式漂亮地打印到标准输出。可以通过配置支持表格、YAML等其他格式。

5. 进阶话题:扩展性、测试与部署考量

5.1 如何扩展支持新的格式?

OneKey Gateway的插件系统设计使其易于扩展。要添加一个新的生成器(例如,生成gRPC服务定义),你需要:

  1. 实现生成器接口 :创建一个类,实现一个标准的方法,例如 generate(function_models: List[FunctionModel], config: dict) -> OutputArtifacts
  2. 注册插件 :通过入口点( entry_points )或简单的配置文件注册你的生成器。
  3. 处理模型转换 :在你的生成器内部,将通用的 FunctionModel 转换为你目标格式所需的特定代码或配置(如 .proto 文件和gRPC服务实现桩代码)。

5.2 测试策略:确保生成代码的质量

生成的代码必须是可靠且可维护的。测试策略应分层:

  1. 单元测试生成器本身 :测试每个生成器插件,给定固定的 FunctionModel 输入,是否产生预期的、语法正确的代码片段。
  2. 集成测试生成产物 :对生成的REST API、CLI等运行自动化测试。
    • REST API :使用 pytest httpx ,针对生成的API端点发起请求,验证响应状态码、数据结构和业务逻辑是否正确。
    • CLI :使用 clix.testing click.testing ,模拟运行生成的CLI命令,捕获其输出并断言。
    • MCP技能 :可以启动生成的MCP服务器,使用一个测试客户端发送标准的MCP请求来验证。
  3. 快照测试(Snapshot Testing) :这是一个非常有效的方法。将生成的代表性代码(如 main.py 的关键部分、 mcp.json )保存为“快照”。每次生成后与快照对比,可以快速发现非预期的变动。

5.3 部署与持续集成

将OneKey Gateway集成到CI/CD流水线中,可以实现“基础设施即代码”风格的API/技能管理:

  1. 源码即真相 :团队仅维护核心的业务逻辑函数文件(如 user_logic.py )和极简的生成配置( omni.yaml )。
  2. CI中的生成步骤 :在CI流水线(如GitHub Actions)中,添加一个步骤运行 omnic build
  3. 自动发布
    • 生成的REST API代码可以被打包成Docker镜像,推送到容器仓库并部署到Kubernetes。
    • 生成的CLI工具可以通过 pip 打包并发布到内部或公共的PyPI仓库。
    • 生成的MCP技能可以打包并发布到内部的技能仓库,供AI助手订阅。
  4. 版本同步 :所有产物的版本号应与核心业务逻辑模块的版本号保持一致,确保可追溯性。

6. 常见陷阱与最佳实践

在开发和使用的过程中,我总结出以下几点“避坑指南”和心得:

  1. Docstring是黄金,请务必写好它 :生成器的文档自动填充能力严重依赖格式良好的Docstring。使用明确的 Args: Returns: 部分。这不仅是为了生成,也是为了你代码的可读性。
  2. 类型注解不是可选项,是必选项 :如果你想获得最好的生成效果(准确的Schema、自动验证),请务必为所有函数参数和返回值添加详细的类型注解。使用 List[int] Optional[str] Dict[str, Any] 等,而不仅仅是简单的 list str
  3. 保持函数“纯洁” :核心业务函数应尽可能保持“纯函数”特性,避免副作用或依赖全局状态。如果必须依赖(如数据库会话),考虑通过参数注入(依赖注入),这样生成器能更好地处理。例如,将 db: Session 作为函数的一个参数,生成器可以将其识别为需要从请求上下文或CLI配置中获取的“依赖项”。
  4. 处理复杂参数和返回类型
    • 复杂对象 :优先使用Pydantic模型。它们能被所有生成器完美理解。
    • 文件上传 :对于REST API,可能需要特殊处理。可以通过在参数上添加类似 @param.file 的提示,告诉生成器将其生成为 UploadFile 类型。
    • 自定义返回状态码 :可以通过在函数上添加装饰器如 @returns(status_code=201) 来为 create_* 函数指定201 Created状态码。
  5. 命名约定很重要 :函数名和参数名会直接影响到生成的API路径、CLI选项名。采用清晰、一致的命名规范(如蛇形命名法),生成器转换后的结果会更直观。
  6. 不要试图用一把锤子敲所有钉子 :OneKey Gateway适用于CRUD类、工具类、查询类等标准化程度高的功能。对于极其复杂、非标准或性能要求极高的接口,手动编写可能仍然是更优选择。这个工具的目标是覆盖80%的常见场景,解放生产力。

构建和使用这样一个“万能格式转换器”的过程,本质上是对软件抽象和开发者体验的一次深度思考。它迫使你更清晰地定义接口边界,更规范地编写代码,最终收获的不仅仅是十倍的交付速度提升,更是一套更干净、更可维护、更易于集成的代码基底。当你看到仅仅几十行核心逻辑,瞬间变成一套立即可用的、文档齐全的生态系统时,那种成就感,足以抵消最初构建它所花费的所有精力。

Logo

欢迎加入 MCP 技术社区!与志同道合者携手前行,一同解锁 MCP 技术的无限可能!

更多推荐