Tiny Agents:用50行代码打造基于MCP的AI代理

引言

过去几周,我深入研究了MCP(Model Context Protocol,模型上下文协议),试图理解它为何备受关注。我的总结是:MCP简单却强大,它是一个标准API,用于暴露可以连接到大语言模型(LLM)的工具集。

通过扩展推理客户端(Hugging Face提供了两个官方SDK:JavaScript的@huggingface/inference和Python的huggingface_hub),可以轻松将其变为MCP客户端,将MCP服务器中的工具接入LLM推理。

在实现过程中,我有了第二个发现:一旦拥有MCP客户端,AI代理本质上只是一个简单的循环。本文将通过TypeScript(JavaScript)示例,展示如何实现这一过程,介绍如何采用MCP,并探讨它如何让Agentic AI开发变得更简单。


如何运行完整示例

如果你有NodeJS(支持pnpm或npm),只需在终端运行以下命令:

bash

bash 复制代码
npx @huggingface/mcp-client

或使用pnpm:

bash

bash 复制代码
pnpx @huggingface/mcp-client

这会将我的软件包安装到一个临时文件夹并执行其命令。你将看到一个简单的代理连接到两个本地运行的MCP服务器,加载它们的工具,然后提示你进行对话。

默认MCP服务器

默认情况下,示例代理会连接以下两个MCP服务器:

  • 文件系统服务器:访问你的桌面文件。
  • Playwright MCP服务器:使用沙盒化的Chromium浏览器进行网页操作。

注意:目前所有MCP服务器都是本地进程,但远程服务器支持即将来临。

示例输入

我们测试了以下两个输入:

  1. 文件操作:
    "写一首关于Hugging Face社区的俳句,并将其写入桌面上的hf.txt文件。"
  2. 网页浏览:
    "在Brave Search上搜索HF推理提供商,并打开前三个结果。"

默认模型和提供商

示例代理默认使用:

  • 模型:Qwen/Qwen2.5-72B-Instruct
  • 提供商:Nebius

这些参数可通过环境变量配置:

javascript

arduino 复制代码
const agent = new Agent({
    provider: process.env.PROVIDER ?? "nebius",
    model: process.env.MODEL_ID ?? "Qwen/Qwen2.5-72B-Instruct",
    apiKey: process.env.HF_TOKEN,
    servers: SERVERS,
});

代码存储位置

Tiny Agent的代码位于huggingface.js mono-repo的mcp-client子包中,这是Hugging Face所有JavaScript库的GitHub仓库。

代码地址:
github.com/huggingface...

代码使用了现代JavaScript特性(尤其是异步生成器),这让异步事件(如LLM响应)的实现更加简单。如果你不熟悉这些特性,可以咨询LLM以获取更多信息。

基础:LLM的原生工具调用支持

本文的核心在于,当前主流LLM(无论是闭源还是开源)都已训练支持函数调用(即工具使用)。工具通过以下方式定义:

  • 名称
  • 描述
  • 参数的JSONSchema表示

这是一种不透明的函数接口表示,LLM并不关心函数的具体实现。例如:

javascript

css 复制代码
const weatherTool = {
    type: "function",
    function: {
        name: "get_weather",
        description: "Get current temperature for a given location.",
        parameters: {
            type: "object",
            properties: {
                location: {
                    type: "string",
                    description: "City and country e.g. Bogotá, Colombia",
                },
            },
        },
    },
};

参考文档:OpenAI的函数调用文档(链接)。是的,OpenAI几乎为整个社区定义了LLM标准

推理引擎允许在调用LLM时传入工具列表,LLM可以选择调用零个、一个或多个工具。开发者需要执行这些工具并将结果反馈给LLM以继续生成。

在后端,工具以特殊格式的chat_template传递给模型,然后通过模型特定的特殊标记从响应中解析为工具调用。


在InferenceClient上实现MCP客户端

了解了LLM中的工具概念后,我们来实现MCP客户端。

官方文档(modelcontextprotocol.io/quickstart/...)写得很清晰。你只需将其中提到的Anthropic客户端SDK替换为任何兼容OpenAI的客户端SDK即可。我们使用Hugging Face的InferenceClient作为推理客户端。

完整代码:McpClient.ts(链接 (#))。

McpClient类结构

McpClient类包含:

  • 推理客户端:支持任何推理提供商(huggingface/inference支持远程和本地端点)。
  • MCP客户端会话:为每个连接的MCP服务器创建一个会话(支持多个服务器)。
  • 可用工具列表:从连接的服务器获取并稍作格式调整后存储。

javascript

typescript 复制代码
export class McpClient {
    protected client: InferenceClient;
    protected provider: string;
    protected model: string;
    private clients: Map<ToolName, Client> = new Map();
    public readonly availableTools: ChatCompletionInputTool[] = [];

    constructor({ provider, model, apiKey }) {
        this.client = new InferenceClient(apiKey);
        this.provider = provider;
        this.model = model;
    }
}

连接MCP服务器

MCP官方SDK(@modelcontextprotocol/sdk/client)提供了Client类,其中的listTools()方法可以列出服务器的工具:

javascript

javascript 复制代码
async addMcpServer(server: StdioServerParameters): Promise<void> {
    const transport = new StdioClientTransport({
        ...server,
        env: { ...server.env, PATH: process.env.PATH ?? "" },
    });
    const mcp = new Client({ name: "@huggingface/mcp-client", version: packageVersion });
    await mcp.connect(transport);

    const toolsResult = await mcp.listTools();
    debug(
        "Connected to server with tools:",
        toolsResult.tools.map(({ name }) => name)
    );

    for (const tool of toolsResult.tools) {
        this.clients.set(tool.name, mcp);
    }

    this.availableTools.push(
        ...toolsResult.tools.map((tool) => ({
            type: "function",
            function: {
                name: tool.name,
                description: tool.description,
                parameters: tool.inputSchema,
            },
        }))
    );
}

StdioServerParameters是MCP SDK提供的接口,用于启动本地进程(目前MCP服务器均为本地进程)。


如何使用工具

使用工具很简单,只需将this.availableTools传递给LLM的chatCompletion接口,与消息数组一起提交:

javascript

kotlin 复制代码
const stream = this.client.chatCompletionStream({
    provider: this.provider,
    model: this.model,
    messages,
    tools: this.availableTools,
    tool_choice: "auto",
});

tool_choice: "auto"允许LLM生成零个、一个或多个工具调用。

在解析或流式处理输出时,LLM会生成工具调用(包括函数名称和JSON编码的参数)。开发者需要执行这些工具,MCP客户端SDK提供了client.callTool()方法:

javascript

ini 复制代码
const toolName = toolCall.function.name;
const toolArgs = JSON.parse(toolCall.function.arguments);

const toolMessage: ChatCompletionInputMessageTool = {
    role: "tool",
    tool_call_id: toolCall.id,
    content: "",
    name: toolName,
};

const client = this.clients.get(toolName);
if (client) {
    const result = await client.callTool({ name: toolName, arguments: toolArgs });
    toolMessage.content = result.content[0].text;
} else {
    toolMessage.content = `Error: No session found for tool: ${toolName}`;
}

最后,将工具执行结果添加到消息数组中并反馈给LLM。


AI代理的本质:一个简单的循环

有了支持工具的推理客户端后,AI代理本质上只是其上的一个while循环。

具体来说,AI代理包括:

  • 系统提示词
  • LLM推理客户端
  • MCP客户端,用于从多个MCP服务器接入工具
  • 基本控制流(while循环)

完整代码:Agent.ts(链接 (#))。

Agent类

Agent类继承自McpClient:

javascript

scala 复制代码
export class Agent extends McpClient {
    private readonly servers: StdioServerParameters[];
    protected messages: ChatCompletionInputMessage[];

    constructor({ provider, model, apiKey, servers, prompt }) {
        super({ provider, model, apiKey });
        this.servers = servers;
        this.messages = [
            {
                role: "system",
                content: prompt ?? DEFAULT_SYSTEM_PROMPT,
            },
        ];
    }
}

默认使用一个简单的系统提示词(参考GPT-4.1提示指南)。OpenAI建议开发者直接使用tools字段传递工具,而无需手动将工具描述注入提示词中。

加载工具

加载工具只需并行连接到所需的MCP服务器:

javascript

javascript 复制代码
async loadTools(): Promise<void> {
    await Promise.all(this.servers.map((s) => this.addMcpServer(s)));
}

此外,我们添加了两个额外的控制流工具:

  • task_complete:任务完成时调用,退出循环。
  • ask_question:向用户提问以获取更多信息,退出循环。

javascript

css 复制代码
const taskCompletionTool: ChatCompletionInputTool = {
    type: "function",
    function: {
        name: "task_complete",
        description: "Call this tool when the task given by the user is complete",
        parameters: { type: "object", properties: {} },
    },
};
const askQuestionTool: ChatCompletionInputTool = {
    type: "function",
    function: {
        name: "ask_question",
        description: "Ask a question to the user to get more info required to solve or clarify their problem.",
        parameters: { type: "object", properties: {} },
    },
};
const exitLoopTools = [taskCompletionTool, askQuestionTool];

完整的While循环

以下是代理的核心while循环:

javascript

ini 复制代码
let numOfTurns = 0;
let nextTurnShouldCallTools = true;
while (true) {
    try {
        yield* this.processSingleTurnWithTools(this.messages, {
            exitLoopTools,
            exitIfFirstChunkNoTool: numOfTurns > 0 && nextTurnShouldCallTools,
            abortSignal: opts.abortSignal,
        });
    } catch (err) {
        if (err instanceof Error && err.message === "AbortError") {
            return;
        }
        throw err;
    }
    numOfTurns++;
    const currentLast = this.messages.at(-1)!;
    if (
        currentLast.role === "tool" &&
        currentLast.name &&
        exitLoopTools.map((t) => t.function.name).includes(currentLast.name)
    ) {
        return;
    }
    if (currentLast.role !== "tool" && numOfTurns > MAX_NUM_TURNS) {
        return;
    }
    if (currentLast.role !== "tool" && nextTurnShouldCallTools) {
        return;
    }
    if (currentLast.role === "tool") {
        nextTurnShouldCallTools = false;
    } else {
        nextTurnShouldCallTools = true;
    }
}

循环的核心逻辑是:代理与LLM交替进行工具调用和结果反馈,直到LLM连续生成两个非工具消息,或者调用控制流工具(task_complete或ask_question)。


下一步

有了运行的MCP客户端和简单的代理构建方法后,可以探索更多可能性:

  • 尝试其他模型:

    • mistralai/Mistral-Small-3.1-24B-Instruct-2503:优化了函数调用。
    • Gemma 3 27B:适合函数调用,但需实现工具解析(欢迎PR!)。
  • 尝试其他推理提供商:

    • Cerebras、Cohere、Fal、Fireworks、Hyperbolic、Nebius、Novita、Replicate、SambaNova、Together等。
  • 接入本地LLM:使用llama.cpp或LM Studio。

项目完全开源,欢迎提交PR和贡献!


结论

通过MCP和现代LLM的工具调用支持,构建AI代理变得异常简单。Tiny Agents展示了一个不到50行代码的实现,结合MCP客户端和一个简单的while循环即可完成。未来,MCP的扩展(如远程服务器支持)将进一步简化Agentic AI的开发流程。

相关推荐
薛定谔的猫29 小时前
Cursor 系列(3):关于MCP
前端·cursor·mcp
王国强200915 小时前
Unla MCP 网关代理配置教程
mcp
kagg88617 小时前
mcp-gateway —— 隐藏mcp上下文以避免不必要的tokens开销
llm·mcp
AAA阿giao18 小时前
qoder-cli:下一代命令行 AI 编程代理——全面解析与深度实践指南
开发语言·前端·人工智能·ai编程·mcp·context7·qoder-cli
饭勺oO2 天前
AI 编程配置太头疼?ACP 帮你一键搞定,再也不用反复折腾!
ai·prompt·agent·acp·mcp·skills·agent skill
AlienZHOU2 天前
MCP 是最大骗局?Skills 才是救星?
agent·mcp·vibecoding
Linux内核拾遗2 天前
人人都在聊 MCP,它到底解决了什么?
aigc·ai编程·mcp
谷哥的小弟2 天前
SQLite MCP服务器安装以及客户端连接配置
服务器·数据库·人工智能·sqlite·大模型·源码·mcp
tyw152 天前
解决 Trae MySQL MCP 连接失败(Fail to start)
mcp·trae·fail to start·mysql mcp·mcp兼容
谷哥的小弟2 天前
File System MCP服务器安装以及客户端连接配置
服务器·人工智能·大模型·file system·mcp·ai项目