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

相关推荐
TeamDev6 小时前
使用 MCP 自动化 JxBrowser
浏览器自动化·jxbrowser·mcp·模型上下文协议·mcp 自动化·jxbrowser 自动化·jxbrowser mcp
ChaITSimpleLove14 小时前
使用 .net10 构建 AI 友好的 RSS 订阅机器人
人工智能·.net·mcp·ai bot·rss bot
妮妮分享1 天前
维智 MCP 接口服务技术支持指南
mcp·mcp server·维智 mcp·智能体接口
感谢地心引力1 天前
【AI】免费的代价?Google AI Studio 使用指南与 Cherry Studio + MCP 实战教程
人工智能·ai·google·chatgpt·gemini·mcp·cherry studio
AI架构师易筋1 天前
模型上下文协议(MCP)完全指南:从AI代理痛点到实战开发
人工智能·microsoft·语言模型·llm·mcp
qdprobot1 天前
齐护AiTall pro ESP32S3 小智AI对话 MQTT MCP 开发板Mixly Scratch Steam图形化编程创客教育
人工智能·mqtt·scratch·mixly·mcp·小智ai·齐护机器人aitall pro
路西法012 天前
Office-Word-MCP-Server在Cursor中使用方法
cursor·mcp
Light603 天前
【MCP原生时代】第2篇|前端如何舞动 MCP:新一代交互范式——从 Hook 到流式渲染,打造 AI 原生前端体验
状态模式·前端架构·mcp·react hook·流式渲染·ai交互
渣渣苏4 天前
MCP实战指南
mcp
爬点儿啥4 天前
[Ai Agent] 10 MCP基础:快速编写你自己的MCP服务器(Server)
人工智能·ai·langchain·agent·transport·mcp