摘要
AnythingMCP 代码模式分步教程:启动 code runner,开启代码模式,并保存一个代码工具,在一次工具调用中读取 ERP(weclapp)中的最近订单,以及 CRM(HubSpot)中的客户账户和未结商机。只读,使用调用者的权限,并记录审计日志。
TL;DR: 在客户电话之前,销售代表会让助手给出一份概览:ERP 中的最近订单,以及 CRM 中的客户负责人和未结商机。不使用代码模式时,这需要四五次工具调用,每次的结果都要由模型完整读取。使用已保存的代码工具则只需一次:customer_overview(customer) 会运行一小段 JavaScript 程序,由它发起调用、保留关键信息,并返回一份精简的摘要。本教程在自托管的 AnythingMCP(v0.52.1 或更高版本)上构建这个工具,以 weclapp 作为 ERP、HubSpot 作为 CRM;同样的模式适用于任意两个连接器。
你需要准备什么
- 自托管的 AnythingMCP v0.52.1 或更高版本。 代码模式即将登陆 AnythingMCP Cloud。
- 两个带有读取工具的连接器,这里是 weclapp 和 HubSpot,需已安装并能正常工作。
- 工作区中的管理员权限。
刚接触这个概念?什么是 MCP 代码模式?解释了为什么一个程序胜过多次工具调用。
步骤 1 — 启动 code runner
代码绝不会在 AnythingMCP 自身内部运行,而是在单独的 code-runner 容器中运行;没有它,所有代码运行都会被拒绝。在你的 .env 中添加:
COMPOSE_PROFILES=code-runner
CODE_RUNNER_URL=ws://code-runner:8777
如果你已经在使用 profile,请将它们一并列出,例如 COMPOSE_PROFILES=files,code-runner。然后执行:
docker compose up -d
runner 不保存任何密钥,不发布任何端口,也无法访问互联网;只有后端会通过内部 Docker 网络与它通信。它如何隔离每次运行:AI 智能体的安全代码执行。
步骤 2 — 为 MCP 服务器开启代码模式
代码模式默认对每个 MCP 服务器关闭,在代码模式为 off 的服务器上,已保存的代码工具会被拒绝。请将你的销售团队所使用服务器的代码模式设置为 tools(仅限已保存的代码工具)。inline 还会额外向智能体提供 code_execute 和 code_propose_tool;本教程不需要它。
在控制面板中打开 MCP Servers,再打开该服务器。在顶部的 Server settings 卡片中,将 Code mode 设为 Saved code tools only,然后点击 Save。之后重新连接 AI 客户端。
更喜欢用 API?向 PUT /api/mcp-servers/<id> 发送 {"codeMode": "tools"} 效果相同。该设置从 v0.52.1 开始提供;如果是 v0.52.0,请先升级。
步骤 3 — 记下两个连接器的 id
代码工具需要声明其程序可以调用哪些连接器。在控制面板中打开每个连接器:它的 id 是地址的最后一部分,即 /connectors/<id>。复制 weclapp 和 HubSpot 连接器的 id。
步骤 4 — 创建代码工具
打开 weclapp 连接器,进入 MCP Tools,点击 Add Tool。将 Tool Type 设置为 Code Tool,然后填写:
- Tool Name:
customer_overview - Description: "通话前的单个客户概览:ERP 中的最近 10 个订单、CRM 客户账户及其未结商机。传入公司名称或名称的一部分。" 描述是 AI 用来判断何时调用该工具的依据,所以要写明它返回什么。
- Input Parameters: 一个参数
customer,类型为 string,必填。

步骤 5 — 编写程序
Program 是一个异步 JavaScript 函数的函数体。工具的输入在 args 中;amcp.run(name, args) 调用另一个工具,并以解析后的 JSON 返回其结果(如果工具失败则抛出异常)。你 return 的内容就是 AI 收到的内容。
// Input: args.customer, a company name or part of it.
const name = String(args.customer ?? '').trim();
if (!name) return { error: 'Pass a customer name.' };
// ERP (weclapp): the customer, then its latest sales orders.
const parties = await amcp.run('weclapp_list_customers', {
filter: `company-ilike=%${name}%`,
pageSize: 5,
});
const customer = (parties.result ?? [])[0];
if (!customer) return { error: `No customer matching "${name}" in the ERP.` };
const orders = await amcp.run('weclapp_list_sales_orders', {
filter: `customerId-eq=${customer.id}`,
sort: '-orderDate',
pageSize: 10,
});
// CRM (HubSpot): the company record and its open deals.
const companies = await amcp.run('hubspot_search_objects', {
object_type: 'companies',
query: customer.company,
properties: ['name', 'domain', 'hubspot_owner_id', 'lifecyclestage'],
limit: 1,
});
const company = companies.results?.[0];
let deals = [];
if (company) {
const found = await amcp.run('hubspot_search_objects', {
object_type: 'deals',
filter_groups: [{ filters: [
{ propertyName: 'associations.company', operator: 'EQ', value: company.id },
{ propertyName: 'hs_is_closed', operator: 'EQ', value: 'false' },
] }],
properties: ['dealname', 'amount', 'dealstage', 'closedate'],
limit: 20,
});
deals = found.results ?? [];
}
// Only this goes back to the model.
return {
customer: { number: customer.customerNumber, name: customer.company },
lastOrders: (orders.result ?? []).map((o) => ({
number: o.orderNumber, date: o.orderDate, status: o.status, net: o.netAmount,
})),
crm: company
? { owner: company.properties.hubspot_owner_id, stage: company.properties.lifecyclestage }
: null,
openDeals: deals.map((d) => ({
name: d.properties.dealname, amount: d.properties.amount,
stage: d.properties.dealstage, close: d.properties.closedate,
})),
};
字段名是 weclapp 和 HubSpot API 返回的字段名。在依赖某个字段之前,先在其 Test 面板中运行一次底层工具并查看结果;在程序内部,await amcp.describe('weclapp_list_sales_orders') 会返回某个工具的输入 schema。
关于关联匹配的说明。 该程序按名称匹配 CRM 公司,对于会前简报来说已经足够。如果你把 ERP 客户编号存储在了 HubSpot 属性中,请改为按该属性过滤,这样匹配就是精确的。
步骤 6 — 声明程序可以使用什么
Uses (connectors) 列出程序可以调用的连接器,以及(可选的)确切的工具。粘贴步骤 3 中的两个 id:
[
{ "connectorId": "<weclapp connector id>", "tools": ["weclapp_list_customers", "weclapp_list_sales_orders"] },
{ "connectorId": "<hubspot connector id>", "tools": ["hubspot_search_objects"] }
]
指定工具是可选的(不写 "tools" 则允许整个连接器),但列出一份简短的清单能让该工具更易于审查。只接受同一工作区的连接器。即使用户可以直接调用某个工具,只要它不在这份清单中,调用就会失败并报错。
步骤 7 — 以调用者身份运行,只读
- Run as: Invoker(调用者自己的权限)。 每一次内部调用都会根据使用该工具的人的角色进行检查。没有 HubSpot 访问权限的销售代表,在 HubSpot 部分会收到错误,就像他们直接调用 HubSpot 一样。(Definer 则按作者的授权范围运行,类似存储过程;它面向管理员和 Enterprise 版。)
- Allow write tools: 关闭。 此时程序只能调用只读工具;
weclapp_list_*和hubspot_search_objects都是读取工具。允许写入的代码工具需要 Enterprise 版。
点击 Create Tool。
步骤 8 — 在 Claude 或 ChatGPT 中试用
先在控制面板中用该工具的 Test 按钮检查程序:它会在 code runner 中运行代码,规则与真实调用相同。然后再从连接到该 MCP 服务器的 AI 客户端进行测试。该工具在每个带有 weclapp 连接器且已开启代码模式的服务器上都可用。在你的 AI 客户端中重新连接或刷新工具,然后提问:
"我十分钟后要给 Lindenhof Bau 打电话。他们最近订了什么,谁负责这个客户账户,有没有未结商机?"
助手只调用一次 customer_overview,并根据精简的结果撰写简报,而不是去读取完整的订单列表和 CRM 记录。如果出了问题,工具结果会说明原因:调用了 Uses 之外的工具('hubspot_search_objects' is not a tool this code tool can use)、API 返回了错误,或者超时。修复程序、保存,然后再问一次。
每次调用时会发生什么
- 一次运行,一条记录。 工具的每次调用都会记录为一次代码运行,程序发起的每个工具调用也都会像其他调用一样,连同用户一起记入审计日志。
- 限制。 一次运行最长 10 秒,最多发起 500 次工具调用(同时最多 16 个),最多返回 1 MB。超出限制的程序会以清晰的错误停止,且不影响其他任何内容。
- 不可嵌套。 代码工具不能调用另一个代码工具。
- 仅限你自己的服务器。 代码工具只在工作区自己的 MCP 服务器上提供,绝不会出现在共享的
/mcp端点上。
套餐
各版本可保存的代码工具数量,以 v0.52 发布时公布的为准:Community 3 个,Starter 5 个,Team 20 个,Enterprise 不限。允许写入的代码工具和"以 definer 身份运行"属于 Enterprise 版。比较套餐。
FAQ
我需要懂 JavaScript 吗?
掌握基础的 JavaScript 就够了:await、对象、map 和 filter。你也可以让智能体起草程序,并将其作为工具提议,交由你审查。
程序可以直接调用外部 API 吗? 不可以。它没有网络访问权限,只能调用 Uses 中列出的 MCP 工具;要访问其他 API,请先将其添加为连接器。
代码工具应该放在哪个连接器上? 放在它所用到的任意一个连接器上都可以;该连接器被分配到哪里,工具就会出现在哪里。请选择其受众应该获得该工具的那个连接器。
相关内容
- 什么是 MCP 代码模式?
- AI 智能体的安全代码执行:隔离 runner 的工作原理
- 代码模式实用范例:月度营收、发票对账、100 次价格查询
- 智能体提议代码工具,管理员审批
- 用 AI 运行你的 ERP:weclapp
- 使用 Docker Compose 部署 AnythingMCP
- 将 weclapp 和 HubSpot 连接到 Claude
这份指南对你有帮助吗?