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的开发流程。

相关推荐
MobotStone3 小时前
MCP还是AI智能体?如何为你的AI应用选择最佳"大脑"架构
mcp
shelgi5 小时前
Cursor结合MCP实现自动编写项目文档
人工智能·mcp
小白跃升坊6 小时前
干货分享|智能问数方案及步骤详解
ai·大语言模型·it运维·mcp·max kb
yaocheng的ai分身6 小时前
MCP的Resources 和 Prompts
mcp
yaocheng的ai分身7 小时前
Building MCP Servers: Part 3 — Adding Prompts
mcp
一只韩非子7 小时前
什么是MCP?为什么引入MCP?(通俗易懂版)
人工智能·aigc·mcp
MCPFlow9 小时前
Cursor+高德MCP制定五一出游攻略
mcp
叫我阿杰好了9 小时前
Trae中 使用MCP 案例
mcp·trae
Yan-英杰10 小时前
百度搜索AI开放计划:让应用连接精准流量的秘诀
ai·mcp·百度搜索开放平台·百度ai开放计划·mcpserver·create2025
码云之上10 小时前
聊聊MCP Client及其实践
前端·架构·mcp