关于 Agent Mesh
Agent Mesh 是供 Node.js 和浏览器 Agent 加入网状网络的 JavaScript SDK,也是 stdio MCP 服务。经过身份验证的成员可发现并调用远程服务,同时遵循网络策略;该项目本身不发布服务。
主要功能
- 01以经过验证的成员身份加入网络
- 02发现服务和远程工具
- 03描述并调用远程工具
- 04响应发往 Agent 的 A2A 请求
- 05保存身份以供后续会话使用
适用场景
- 01让 MCP 客户端访问 Agent Mesh 上的工具
- 02构建调用网络服务的 Node.js Agent
- 03使 Agent 能够响应 A2A 请求
MCP 相关
`agentmesh` 可执行程序通过 stdio 暴露五个 MCP 工具,用于获取网络信息、发现服务和工具、描述工具及发起远程调用。MCP 客户端负责启动和停止进程;服务提供方通过 `agentmesh-node` 发布服务。
配置
本地服务的配置在各客户端中通用,复制后粘贴到客户端的 MCP 配置文件即可。
npx -y @agentmesh-p2p/sdkAGENTMESH_CONTROL_PLANE_URLControl plane URL supplied by your mesh operator. HTTPS is required beyond loopback unless explicitly permitted below.AGENTMESH_BOOTSTRAP_TOKEN_PATH密钥Absolute path to a bootstrap token file for first enrollment. Set this OR AGENTMESH_JWT_PATH; leave both empty only when resuming saved credentials.AGENTMESH_JWT_PATH密钥Absolute path to an OIDC/workload token file. Alternative to the bootstrap token; reread during credential refresh.AGENTMESH_STATE_DIRAbsolute directory for this client identity. Use a distinct directory per client and mesh. If omitted, uses ~/.config/agentmesh/mcp.AGENTMESH_INSECURE_CONTROL_PLANEAllow plaintext HTTP beyond loopback. Development only: exposes enrollment traffic to the network.本地服务需要在你的电脑上安装运行环境(如 Node.js 18+)。各客户端的配置文件位置不同,请参考客户端文档。
工具
本地服务需要在你的电脑上运行后才能读取工具,维护者暂未提供工具列表。
详细介绍
@agentmesh-p2p/sdk
用于在 Agent 进程内部加入 Agent Mesh 的原生 JavaScript SDK。对于使用 Node.js 编写或在浏览器页面中运行的 Agent,它可以替代 agentmesh-node 边车:
Agent 会向控制平面注册,通过路由器加入网格,发现并调用服务,为 Agent 自身处理 A2A 请求,并在运行期间遵循控制平面的密钥、封禁和策略。它不会发布服务;需要按名称被发现的工具或模型应运行在 agentmesh-node 后面。
指南:google.github.io/agentmesh/docs/guides/native-sdks,从空白机器开始,直至两个程序加入网格。 源码:github.com/google/agentmesh/tree/main/sdk/js。
安装
npm install @agentmesh-p2p/sdk @modelcontextprotocol/sdk zod
需要 Node.js 22.18 或更高版本。在浏览器中,请将其与页面打包(该包的 browser 字段会选择浏览器文件);指南中的在浏览器中部分提供了详细说明和示例页面。
通过 stdio 使用 MCP 客户端
该包的 agentmesh 可执行文件会以独立网格成员的身份加入,并提供五个 MCP 工具:get_mesh_info、discover_remote_services、find_remote_tools、describe_remote_tool 和 call_remote_tool。支持 stdio 的 MCP 客户端负责启动和停止进程;此调用方无需 node 守护进程。提供方仍通过 agentmesh-node 发布其服务。
要从源码检出目录试用该可执行文件:
cd sdk/js
npm ci
npm run build
在 MCP 客户端的配置中使用绝对路径:
{
"mcpServers": {
"agentmesh": {
"command": "node",
"args": ["/absolute/path/to/agentmesh/sdk/js/dist/cli.js"],
"env": {
"AGENTMESH_CONTROL_PLANE_URL": "https://your-control-plane.example",
"AGENTMESH_BOOTSTRAP_TOKEN_PATH": "/absolute/path/to/join-token",
"AGENTMESH_STATE_DIR": "/absolute/path/to/this-client-identity"
}
}
}
}
请向网格运营方或 agentmesh-one 获取控制平面 URL 和令牌文件。也可以将 AGENTMESH_JWT_PATH 设置为工作负载/OIDC 令牌文件;不要同时设置两个令牌路径。凭据从文件读取,绝不会使用命令行参数值。
首次运行时会注册并保存身份;后续运行会从状态目录恢复,无需引导令牌。JWT 注册会在刷新时重新读取令牌文件。每个客户端身份和网格都应使用独立的状态目录;不要让多个进程共用同一目录。默认目录为 ~/.config/agentmesh/mcp。除非明确设置 AGENTMESH_INSECURE_CONTROL_PLANE=true 允许在其他地址使用纯 HTTP,否则纯 HTTP 仅允许用于环回地址。
标准输出仅包含 MCP 消息;诊断信息会输出到标准错误。关闭 stdin 或发送 SIGINT/SIGTERM 会关闭网格会话。工具调用使用成员现有的经过身份验证的流和策略,包括提供方标签检查。远程工具结果会保留文本、非文本内容、结构化输出和错误信息。发现过程会遵循工具分页,拒绝循环目录,并将每次搜索限制为最多 64 个提供方、每个提供方 256 个服务、每个服务 256 个工具,以及 1024 个结果。请求时限为 10 秒;若搜索范围过大,请按对等方或服务缩小范围。intent 参数会被接受但忽略,与 node 当前工具的行为一致。
此成员不会发布服务,因此不提供 list_local_services 工具。推理和 A2A 发现会返回元数据,但此可执行文件不提供 HTTP 代理、/v1 端点、本地套接字或 local_proxy_url。如需调用这些协议,请使用 SDK 客户端或 agentmesh-node HTTP 代理。
使用
下面两个程序都位于
examples/,并会在仓库的测试中连接真实的网状网络。它们从
AGENTMESH_CONTROL_PLANE_URL 读取网状网络地址,并从
AGENTMESH_BOOTSTRAP_TOKEN_PATH 读取注册令牌;指南介绍了如何从 agentmesh-one 获取这两项信息,或向现有网状网络的运营者获取。
查找服务并调用它:
// Calls something on the mesh: a tool of an MCP service or a path of an
// inference or A2A service someone published, found by name, or an agent that
// published nothing, reached by its peer ID.
//
// node call.js mcp://everything echo '{"message": "hi"}'
// node call.js inference://ollama /v1/models
// node call.js 12D3KooW... a2a://agent /card
//
// AGENTMESH_CONTROL_PLANE_URL names the mesh. The first run enrolls with the file
// AGENTMESH_BOOTSTRAP_TOKEN_PATH (a token the mesh operator gave you) or
// AGENTMESH_JWT_PATH (a workload identity token your platform issues, such as a
// Kubernetes projected service account token), and keeps the identity and
// credential in AGENTMESH_STATE_DIR; later runs resume from there without it.
import { homedir } from "node:os";
import { AgentMesh, type DiscoveredProvider } from "@agentmesh-p2p/sdk";
let argv = process.argv.slice(2);
// A first argument that is not a service target is the peer ID of an agent.
let peerId: string | undefined;
if (argv[0] !== undefined && !argv[0].includes("://")) {
[peerId, ...argv] = argv as [string, ...string[]];
}
const [service = peerId !== undefined ? "a2a://agent" : "mcp://everything", toolOrPath = peerId !== undefined ? "/card" : "echo", args = '{"message": "hi"}'] = argv;
const mesh = await AgentMesh.enroll({
controlPlaneUrl: process.env.AGENTMESH_CONTROL_PLANE_URL ?? "https://mesh.example.com",
bootstrapTokenPath: process.env.AGENTMESH_BOOTSTRAP_TOKEN_PATH,
jwtPath: process.env.AGENTMESH_JWT_PATH,
stateDir: process.env.AGENTMESH_STATE_DIR ?? `${homedir()}/.config/agentmesh/caller`,
// A plaintext http:// control plane is otherwise accepted only on loopback.
allowInsecure: process.env.AGENTMESH_INSECURE_CONTROL_PLANE === "true",
});
const session = await mesh.join();
console.log(`on the mesh as ${session.peerId}`);
// The peer to call: an agent named by ID, or a provider of a published service.
let provider: DiscoveredProvider = { peerId: peerId ?? "", addrs: [] };
if (peerId !== undefined) {
// An agent is not in the discovery table; the SDK finds the path to its
// peer ID through the routers.
await session.connect(peerId);
} else {
const providers = await session.discover(service);
if (providers.length === 0) {
throw new Error(`no member of the mesh serves ${service}`);
}
// A provider record can outlive its member; the first that answers is used.
let reached = false;
for (const candidate of providers) {
try {
await session.connect(candidate);
provider = candidate;
reached = true;
break;
} catch (err) {
console.error(`${candidate.peerId}: ${(err as Error).message}`);
}
}
if (!reached) {
throw new Error(`no provider of ${service} is reachable`);
}
}
console.log(`${service} is served by ${provider.peerId}`);
if (service.startsWith("mcp://")) {
const tools = await session.listTools(provider, service);
console.log(`tools: ${tools.map((t) => t.name).join(", ")}`);
const result = await session.callTool(provider, service, toolOrPath, JSON.parse(args));
console.log(result.text.join("\n"));
} else {
const response = await session.request(provider, service, toolOrPath);
console.log(response.status, response.text());
}
await session.close();
成为其他人可以调用的 Agent。不会发布任何内容;调用方通过路由器使用 Agent 的对等端 ID(也就是它打印出来的 ID)访问它:
// An agent on the mesh: joins, then answers A2A requests from other members
// until stopped. It publishes nothing. There is no service name to look up; a
// caller reaches the agent by its peer ID, through a router, as a2a://agent.
// The mesh policy decides which members may call; the SDK turns the others
// away before anything reaches this code.
//
// node agent.js # answered by the handler below
// node agent.js http://127.0.0.1:9999 # forwarded to an A2A server beside it
//
// AGENTMESH_CONTROL_PLANE_URL names the mesh. The first run enrolls with the file
// AGENTMESH_BOOTSTRAP_TOKEN_PATH (a token the mesh operator gave you) or
// AGENTMESH_JWT_PATH (a workload identity token your platform issues, such as a
// Kubernetes projected service account token), and keeps the identity and
// credential in AGENTMESH_STATE_DIR; later runs resume from there, as the same
// peer, without it.
import { homedir } from "node:os";
import { AgentMesh } from "@agentmesh-p2p/sdk";
const [backendURL] = process.argv.slice(2);
const mesh = await AgentMesh.enroll({
controlPlaneUrl: process.env.AGENTMESH_CONTROL_PLANE_URL ?? "https://mesh.example.com",
bootstrapTokenPath: process.env.AGENTMESH_BOOTSTRAP_TOKEN_PATH,
jwtPath: process.env.AGENTMESH_JWT_PATH,
stateDir: process.env.AGENTMESH_STATE_DIR ?? `${homedir()}/.config/agentmesh/agent`,
// A plaintext http:// control plane is otherwise accepted only on loopback.
allowInsecure: process.env.AGENTMESH_INSECURE_CONTROL_PLANE === "true",
});
const session = await mesh.join();
// Answers every path with who was asked and who asked; a real agent runs an
// A2A server here, or beside this process at backendURL.
const target = await session.acceptA2A(
backendURL !== undefined
? { url: backendURL }
: { handler: (request, caller) => Response.json({ name: "agent", path: new URL(request.url).pathname, caller: caller.peerId }) },
);
console.log(`accepting ${target} as ${session.peerId}`);
const stop = () => void session.close().then(() => process.exit(0));
process.on("SIGINT", stop);
process.on("SIGTERM", stop);
如果保存在 stateDir 中的身份和凭据对该控制平面仍然有效,enroll 会复用它们;否则,必须且只能提供 bootstrapTokenPath、bootstrapToken、jwtPath 或 jwt 中的一项(令牌字符串,或在注册时及每次刷新时调用的 () => string | Promise<string> 回调)。从文件、回调或环境变量中读取令牌;不要将令牌放在命令行中。labels 会在注册时进行证明;join({ egressRequireLabels }) 则是会话调用的每个对等端都必须证明的最低要求,所有这些标签都必须满足,并且在整个会话期间持续有效。
仅在回环地址上接受明文 http:// 控制平面。对于你信任的网络,请传入 allowInsecure: true。
许可证
Apache-2.0。请前往 github.com/google/agentmesh 提交问题和贡献。
评论
一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。