@polpo-ai/tools 0.6.25 → 0.6.26
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/__tests__/mcp-client.test.d.ts +2 -0
- package/dist/__tests__/mcp-client.test.d.ts.map +1 -0
- package/dist/__tests__/mcp-client.test.js +158 -0
- package/dist/__tests__/mcp-client.test.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/mcp-client.d.ts +68 -0
- package/dist/mcp-client.d.ts.map +1 -0
- package/dist/mcp-client.js +236 -0
- package/dist/mcp-client.js.map +1 -0
- package/package.json +9 -7
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mcp-client.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/mcp-client.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
|
2
|
+
import { resolveAgentMcpTools } from "../mcp-client.js";
|
|
3
|
+
/**
|
|
4
|
+
* These tests cover the wiring around the AI SDK MCP client (vault
|
|
5
|
+
* templating, host allowlist, namespacing, dispose) by mocking the
|
|
6
|
+
* client itself. We do NOT spin up a real MCP server here — that's
|
|
7
|
+
* an integration concern. The unit-level invariants we lock in are:
|
|
8
|
+
*
|
|
9
|
+
* - `${vault:service:key}` is replaced with the resolved credential.
|
|
10
|
+
* - A missing vault credential throws a clean, named error.
|
|
11
|
+
* - Tools are namespaced `mcp__<server>__<tool>` so collisions across
|
|
12
|
+
* servers are impossible.
|
|
13
|
+
* - One bad server doesn't take down the whole resolve — the rest
|
|
14
|
+
* of the agent's MCPs still come up.
|
|
15
|
+
* - `dispose()` closes every transport that was successfully opened.
|
|
16
|
+
* - `POLPO_MCP_ALLOWED_HOSTS` rejects off-list hosts before any
|
|
17
|
+
* network I/O.
|
|
18
|
+
*/
|
|
19
|
+
const closeMock = vi.fn(async () => { });
|
|
20
|
+
const toolsMock = vi.fn(async () => ({
|
|
21
|
+
ping: {
|
|
22
|
+
description: "ping the server",
|
|
23
|
+
inputSchema: { type: "object", properties: {} },
|
|
24
|
+
execute: vi.fn(async () => "pong"),
|
|
25
|
+
},
|
|
26
|
+
echo: {
|
|
27
|
+
description: "echo back",
|
|
28
|
+
inputSchema: { type: "object", properties: { msg: { type: "string" } } },
|
|
29
|
+
execute: vi.fn(async (args) => `echo:${args.msg}`),
|
|
30
|
+
},
|
|
31
|
+
}));
|
|
32
|
+
const createClientMock = vi.fn(async () => ({
|
|
33
|
+
tools: toolsMock,
|
|
34
|
+
close: closeMock,
|
|
35
|
+
}));
|
|
36
|
+
vi.mock("@ai-sdk/mcp", () => ({
|
|
37
|
+
createMCPClient: (arg) => createClientMock(arg),
|
|
38
|
+
}));
|
|
39
|
+
vi.mock("@ai-sdk/mcp/mcp-stdio", () => ({
|
|
40
|
+
Experimental_StdioMCPTransport: class {
|
|
41
|
+
spec;
|
|
42
|
+
constructor(spec) {
|
|
43
|
+
this.spec = spec;
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
}));
|
|
47
|
+
const fakeVault = {
|
|
48
|
+
getKey(service, key) {
|
|
49
|
+
if (service === "polpo" && key === "api_key")
|
|
50
|
+
return "secret-123";
|
|
51
|
+
return undefined;
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
describe("resolveAgentMcpTools", () => {
|
|
55
|
+
beforeEach(() => {
|
|
56
|
+
closeMock.mockClear();
|
|
57
|
+
toolsMock.mockClear();
|
|
58
|
+
createClientMock.mockClear();
|
|
59
|
+
delete process.env.POLPO_MCP_ALLOWED_HOSTS;
|
|
60
|
+
});
|
|
61
|
+
afterEach(() => {
|
|
62
|
+
delete process.env.POLPO_MCP_ALLOWED_HOSTS;
|
|
63
|
+
});
|
|
64
|
+
it("returns empty + no-op dispose when the agent declares no servers", async () => {
|
|
65
|
+
const result = await resolveAgentMcpTools("agent-1", undefined, undefined);
|
|
66
|
+
expect(result.tools).toEqual([]);
|
|
67
|
+
await expect(result.dispose()).resolves.toBeUndefined();
|
|
68
|
+
expect(createClientMock).not.toHaveBeenCalled();
|
|
69
|
+
});
|
|
70
|
+
it("namespaces tools as mcp__<server>__<tool>", async () => {
|
|
71
|
+
const servers = {
|
|
72
|
+
polpo: { type: "http", url: "https://api.polpo.sh/mcp" },
|
|
73
|
+
};
|
|
74
|
+
const result = await resolveAgentMcpTools("orchestrator", servers, undefined);
|
|
75
|
+
const names = result.tools.map((t) => t.name).sort();
|
|
76
|
+
expect(names).toEqual(["mcp__polpo__echo", "mcp__polpo__ping"]);
|
|
77
|
+
});
|
|
78
|
+
it("templates ${vault:service:key} into header values", async () => {
|
|
79
|
+
const servers = {
|
|
80
|
+
polpo: {
|
|
81
|
+
type: "http",
|
|
82
|
+
url: "https://api.polpo.sh/mcp",
|
|
83
|
+
headers: { Authorization: "Bearer ${vault:polpo:api_key}" },
|
|
84
|
+
},
|
|
85
|
+
};
|
|
86
|
+
await resolveAgentMcpTools("agent-1", servers, fakeVault);
|
|
87
|
+
const call = createClientMock.mock.calls[0]?.[0];
|
|
88
|
+
expect(call.transport.headers.Authorization).toBe("Bearer secret-123");
|
|
89
|
+
});
|
|
90
|
+
it("errors clearly when a vault placeholder cannot be resolved", async () => {
|
|
91
|
+
// We swallow per-server errors and log them, so the resolve still
|
|
92
|
+
// succeeds with an empty tool array — the agent simply doesn't get
|
|
93
|
+
// tools from the broken server.
|
|
94
|
+
const errSpy = vi.spyOn(console, "error").mockImplementation(() => { });
|
|
95
|
+
const servers = {
|
|
96
|
+
bad: {
|
|
97
|
+
type: "http",
|
|
98
|
+
url: "https://api.example.com/mcp",
|
|
99
|
+
headers: { Authorization: "Bearer ${vault:nonexistent:key}" },
|
|
100
|
+
},
|
|
101
|
+
};
|
|
102
|
+
const result = await resolveAgentMcpTools("agent-1", servers, fakeVault);
|
|
103
|
+
expect(result.tools).toEqual([]);
|
|
104
|
+
expect(errSpy.mock.calls[0]?.[1]).toContain('Vault credential "nonexistent:key"');
|
|
105
|
+
errSpy.mockRestore();
|
|
106
|
+
});
|
|
107
|
+
it("isolates failures — one broken server doesn't kill the rest", async () => {
|
|
108
|
+
const errSpy = vi.spyOn(console, "error").mockImplementation(() => { });
|
|
109
|
+
// First call throws, second succeeds.
|
|
110
|
+
createClientMock
|
|
111
|
+
.mockRejectedValueOnce(new Error("boom"))
|
|
112
|
+
.mockResolvedValueOnce({ tools: toolsMock, close: closeMock });
|
|
113
|
+
const servers = {
|
|
114
|
+
broken: { type: "http", url: "https://broken.example.com/mcp" },
|
|
115
|
+
working: { type: "http", url: "https://working.example.com/mcp" },
|
|
116
|
+
};
|
|
117
|
+
const result = await resolveAgentMcpTools("agent-1", servers, undefined);
|
|
118
|
+
expect(result.tools.map((t) => t.name)).toEqual([
|
|
119
|
+
"mcp__working__ping",
|
|
120
|
+
"mcp__working__echo",
|
|
121
|
+
]);
|
|
122
|
+
errSpy.mockRestore();
|
|
123
|
+
});
|
|
124
|
+
it("dispose closes every successfully-opened transport", async () => {
|
|
125
|
+
const servers = {
|
|
126
|
+
a: { type: "http", url: "https://a.example.com/mcp" },
|
|
127
|
+
b: { type: "http", url: "https://b.example.com/mcp" },
|
|
128
|
+
};
|
|
129
|
+
const result = await resolveAgentMcpTools("agent-1", servers, undefined);
|
|
130
|
+
await result.dispose();
|
|
131
|
+
expect(closeMock).toHaveBeenCalledTimes(2);
|
|
132
|
+
});
|
|
133
|
+
it("rejects HTTP servers outside POLPO_MCP_ALLOWED_HOSTS", async () => {
|
|
134
|
+
const errSpy = vi.spyOn(console, "error").mockImplementation(() => { });
|
|
135
|
+
process.env.POLPO_MCP_ALLOWED_HOSTS = "*.polpo.sh,internal.example.com";
|
|
136
|
+
const servers = {
|
|
137
|
+
external: { type: "http", url: "https://attacker.example.com/mcp" },
|
|
138
|
+
ok: { type: "http", url: "https://api.polpo.sh/mcp" },
|
|
139
|
+
};
|
|
140
|
+
const result = await resolveAgentMcpTools("agent-1", servers, undefined);
|
|
141
|
+
expect(result.tools.map((t) => t.name)).toEqual([
|
|
142
|
+
"mcp__ok__ping",
|
|
143
|
+
"mcp__ok__echo",
|
|
144
|
+
]);
|
|
145
|
+
expect(errSpy.mock.calls[0]?.[1]).toContain("not in POLPO_MCP_ALLOWED_HOSTS");
|
|
146
|
+
errSpy.mockRestore();
|
|
147
|
+
});
|
|
148
|
+
it("adapts MCP execute to Polpo's ToolResult shape (text content)", async () => {
|
|
149
|
+
const servers = {
|
|
150
|
+
polpo: { type: "http", url: "https://api.polpo.sh/mcp" },
|
|
151
|
+
};
|
|
152
|
+
const { tools } = await resolveAgentMcpTools("agent-1", servers, undefined);
|
|
153
|
+
const echo = tools.find((t) => t.name === "mcp__polpo__echo");
|
|
154
|
+
const result = await echo.execute("call-1", { msg: "hi" });
|
|
155
|
+
expect(result.content).toEqual([{ type: "text", text: "echo:hi" }]);
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
//# sourceMappingURL=mcp-client.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mcp-client.test.js","sourceRoot":"","sources":["../../src/__tests__/mcp-client.test.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -15,6 +15,8 @@ export type { ExtendedToolName, CreateAllToolsOptions } from "./system-tools.js"
|
|
|
15
15
|
export { createOutcomeTools } from "./outcome-tools.js";
|
|
16
16
|
export { createHttpTools, ALL_HTTP_TOOL_NAMES } from "./http-tools.js";
|
|
17
17
|
export { createVaultToolsCore, createVaultTools, ALL_VAULT_TOOL_NAMES } from "./vault-tools.js";
|
|
18
|
+
export { resolveAgentMcpTools } from "./mcp-client.js";
|
|
19
|
+
export type { McpServerSpec, ResolvedMcpTools, VaultLookup } from "./mcp-client.js";
|
|
18
20
|
export { createBrowserTools, ALL_BROWSER_TOOL_NAMES, cleanupAgentBrowserSession } from "./browser-tools.js";
|
|
19
21
|
export { createEmailTools, ALL_EMAIL_TOOL_NAMES } from "./email-tools.js";
|
|
20
22
|
export { createExcelTools, ALL_EXCEL_TOOL_NAMES } from "./excel-tools.js";
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAGH,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,IAAI,iBAAiB,EAAE,cAAc,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,uBAAuB,EAAE,MAAM,mBAAmB,CAAC;AAC9K,YAAY,EAAE,gBAAgB,EAAE,qBAAqB,EAAE,MAAM,mBAAmB,CAAC;AAGjF,OAAO,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AACxD,OAAO,EAAE,eAAe,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AACvE,OAAO,EAAE,oBAAoB,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAGH,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,IAAI,iBAAiB,EAAE,cAAc,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,uBAAuB,EAAE,MAAM,mBAAmB,CAAC;AAC9K,YAAY,EAAE,gBAAgB,EAAE,qBAAqB,EAAE,MAAM,mBAAmB,CAAC;AAGjF,OAAO,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AACxD,OAAO,EAAE,eAAe,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AACvE,OAAO,EAAE,oBAAoB,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAChG,OAAO,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AACvD,YAAY,EAAE,aAAa,EAAE,gBAAgB,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAGpF,OAAO,EAAE,kBAAkB,EAAE,sBAAsB,EAAE,0BAA0B,EAAE,MAAM,oBAAoB,CAAC;AAC5G,OAAO,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAC1E,OAAO,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAC1E,OAAO,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AACpE,OAAO,EAAE,eAAe,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AACvE,OAAO,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAC1E,OAAO,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAC1E,OAAO,EAAE,iBAAiB,EAAE,qBAAqB,EAAE,MAAM,mBAAmB,CAAC;AAC7E,OAAO,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAC1E,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAGtD,OAAO,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAC;AAC/D,OAAO,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAC;AAGrD,OAAO,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAC1F,OAAO,EAAE,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AACrD,OAAO,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAGnD,YAAY,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -16,6 +16,7 @@ export { createSystemTools, createSystemTools as createCodingTools, createAllToo
|
|
|
16
16
|
export { createOutcomeTools } from "./outcome-tools.js";
|
|
17
17
|
export { createHttpTools, ALL_HTTP_TOOL_NAMES } from "./http-tools.js";
|
|
18
18
|
export { createVaultToolsCore, createVaultTools, ALL_VAULT_TOOL_NAMES } from "./vault-tools.js";
|
|
19
|
+
export { resolveAgentMcpTools } from "./mcp-client.js";
|
|
19
20
|
// Extended tool factories
|
|
20
21
|
export { createBrowserTools, ALL_BROWSER_TOOL_NAMES, cleanupAgentBrowserSession } from "./browser-tools.js";
|
|
21
22
|
export { createEmailTools, ALL_EMAIL_TOOL_NAMES } from "./email-tools.js";
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,oBAAoB;AACpB,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,IAAI,iBAAiB,EAAE,cAAc,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,uBAAuB,EAAE,MAAM,mBAAmB,CAAC;AAG9K,qDAAqD;AACrD,OAAO,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AACxD,OAAO,EAAE,eAAe,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AACvE,OAAO,EAAE,oBAAoB,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,oBAAoB;AACpB,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,IAAI,iBAAiB,EAAE,cAAc,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,uBAAuB,EAAE,MAAM,mBAAmB,CAAC;AAG9K,qDAAqD;AACrD,OAAO,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AACxD,OAAO,EAAE,eAAe,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AACvE,OAAO,EAAE,oBAAoB,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAChG,OAAO,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AAGvD,0BAA0B;AAC1B,OAAO,EAAE,kBAAkB,EAAE,sBAAsB,EAAE,0BAA0B,EAAE,MAAM,oBAAoB,CAAC;AAC5G,OAAO,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAC1E,OAAO,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAC1E,OAAO,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AACpE,OAAO,EAAE,eAAe,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AACvE,OAAO,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAC1E,OAAO,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAC1E,OAAO,EAAE,iBAAiB,EAAE,qBAAqB,EAAE,MAAM,mBAAmB,CAAC;AAC7E,OAAO,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAC1E,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAEtD,8CAA8C;AAC9C,OAAO,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAC;AAC/D,OAAO,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAC;AAErD,qBAAqB;AACrB,OAAO,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAC1F,OAAO,EAAE,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AACrD,OAAO,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolve MCP-server-provided tools for a single agent and adapt them to
|
|
3
|
+
* Polpo's internal `PolpoTool` shape. This is the agent-side glue that
|
|
4
|
+
* lets a Polpo agent declare `mcpServers` and have the resulting tools
|
|
5
|
+
* appear next to its native ones (read/write/bash/...) with the same
|
|
6
|
+
* runtime contract.
|
|
7
|
+
*
|
|
8
|
+
* Tools are namespaced as `mcp__<serverName>__<toolName>` so two servers
|
|
9
|
+
* can both expose `read_file` (or whatever) without collision, mirroring
|
|
10
|
+
* Claude Code's convention. The LLM picks the right one because the
|
|
11
|
+
* server-prefixed name is unambiguous.
|
|
12
|
+
*
|
|
13
|
+
* Lifecycle: returns a `dispose()` that closes every opened transport.
|
|
14
|
+
* Callers must invoke it once the request finishes (typically wired into
|
|
15
|
+
* `streamText`'s `onFinish`). Skipping leaks file descriptors / open
|
|
16
|
+
* HTTP keep-alives.
|
|
17
|
+
*
|
|
18
|
+
* Auth: header values support the existing `${vault:KEY}` template
|
|
19
|
+
* syntax, resolved against the agent's vault entries. A missing vault
|
|
20
|
+
* key surfaces as an `Error` so the agent runtime fails fast and visibly
|
|
21
|
+
* — better than silently sending `Bearer ${vault:FOO}` to the server.
|
|
22
|
+
*
|
|
23
|
+
* Security: when an allowlist of hostnames is configured (env
|
|
24
|
+
* `POLPO_MCP_ALLOWED_HOSTS`, comma-separated, supports `*.foo.com`
|
|
25
|
+
* wildcards), HTTP/SSE servers outside the list are refused. Critical
|
|
26
|
+
* in cloud to block SSRF / metadata-IP pivots; in self-host the
|
|
27
|
+
* allowlist is unset and any host works.
|
|
28
|
+
*/
|
|
29
|
+
import type { PolpoTool } from "@polpo-ai/core";
|
|
30
|
+
/** A single MCP server config — mirrors the type in `@polpo-ai/sdk`. */
|
|
31
|
+
export type McpServerSpec = {
|
|
32
|
+
type?: "stdio";
|
|
33
|
+
command: string;
|
|
34
|
+
args?: string[];
|
|
35
|
+
env?: Record<string, string>;
|
|
36
|
+
} | {
|
|
37
|
+
type: "sse";
|
|
38
|
+
url: string;
|
|
39
|
+
headers?: Record<string, string>;
|
|
40
|
+
} | {
|
|
41
|
+
type: "http";
|
|
42
|
+
url: string;
|
|
43
|
+
headers?: Record<string, string>;
|
|
44
|
+
};
|
|
45
|
+
/**
|
|
46
|
+
* Minimal subset of the existing `ResolvedVault` interface we depend on.
|
|
47
|
+
* Decoupled to keep `@polpo-ai/tools` from importing the shell's vault
|
|
48
|
+
* resolver — callers pass in any object that conforms.
|
|
49
|
+
*/
|
|
50
|
+
export interface VaultLookup {
|
|
51
|
+
getKey(service: string, key: string): string | undefined;
|
|
52
|
+
}
|
|
53
|
+
export interface ResolvedMcpTools {
|
|
54
|
+
/** Polpo-format tools — drop straight into the agent's tool array. */
|
|
55
|
+
tools: PolpoTool<any>[];
|
|
56
|
+
/** Close every opened transport. Best-effort; idempotent. */
|
|
57
|
+
dispose: () => Promise<void>;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Open every MCP server declared on the agent config. Returns the
|
|
61
|
+
* aggregated Polpo tools + a single `dispose` that closes them all.
|
|
62
|
+
*
|
|
63
|
+
* Failures on individual servers are logged but don't abort the rest:
|
|
64
|
+
* an agent with one broken MCP and one good one still gets the good
|
|
65
|
+
* tools, with a console error pointing at the bad config.
|
|
66
|
+
*/
|
|
67
|
+
export declare function resolveAgentMcpTools(agentName: string, mcpServers: Record<string, McpServerSpec> | undefined, vault: VaultLookup | undefined): Promise<ResolvedMcpTools>;
|
|
68
|
+
//# sourceMappingURL=mcp-client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mcp-client.d.ts","sourceRoot":"","sources":["../src/mcp-client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAc,MAAM,gBAAgB,CAAC;AAE5D,wEAAwE;AACxE,MAAM,MAAM,aAAa,GACrB;IACE,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC9B,GACD;IAAE,IAAI,EAAE,KAAK,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAAE,GAC9D;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAAE,CAAC;AAEpE;;;;GAIG;AACH,MAAM,WAAW,WAAW;IAC1B,MAAM,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC;CAC1D;AAED,MAAM,WAAW,gBAAgB;IAC/B,sEAAsE;IACtE,KAAK,EAAE,SAAS,CAAC,GAAG,CAAC,EAAE,CAAC;IACxB,6DAA6D;IAC7D,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC9B;AA2ID;;;;;;;GAOG;AACH,wBAAsB,oBAAoB,CACxC,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,GAAG,SAAS,EACrD,KAAK,EAAE,WAAW,GAAG,SAAS,GAC7B,OAAO,CAAC,gBAAgB,CAAC,CAoF3B"}
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolve MCP-server-provided tools for a single agent and adapt them to
|
|
3
|
+
* Polpo's internal `PolpoTool` shape. This is the agent-side glue that
|
|
4
|
+
* lets a Polpo agent declare `mcpServers` and have the resulting tools
|
|
5
|
+
* appear next to its native ones (read/write/bash/...) with the same
|
|
6
|
+
* runtime contract.
|
|
7
|
+
*
|
|
8
|
+
* Tools are namespaced as `mcp__<serverName>__<toolName>` so two servers
|
|
9
|
+
* can both expose `read_file` (or whatever) without collision, mirroring
|
|
10
|
+
* Claude Code's convention. The LLM picks the right one because the
|
|
11
|
+
* server-prefixed name is unambiguous.
|
|
12
|
+
*
|
|
13
|
+
* Lifecycle: returns a `dispose()` that closes every opened transport.
|
|
14
|
+
* Callers must invoke it once the request finishes (typically wired into
|
|
15
|
+
* `streamText`'s `onFinish`). Skipping leaks file descriptors / open
|
|
16
|
+
* HTTP keep-alives.
|
|
17
|
+
*
|
|
18
|
+
* Auth: header values support the existing `${vault:KEY}` template
|
|
19
|
+
* syntax, resolved against the agent's vault entries. A missing vault
|
|
20
|
+
* key surfaces as an `Error` so the agent runtime fails fast and visibly
|
|
21
|
+
* — better than silently sending `Bearer ${vault:FOO}` to the server.
|
|
22
|
+
*
|
|
23
|
+
* Security: when an allowlist of hostnames is configured (env
|
|
24
|
+
* `POLPO_MCP_ALLOWED_HOSTS`, comma-separated, supports `*.foo.com`
|
|
25
|
+
* wildcards), HTTP/SSE servers outside the list are refused. Critical
|
|
26
|
+
* in cloud to block SSRF / metadata-IP pivots; in self-host the
|
|
27
|
+
* allowlist is unset and any host works.
|
|
28
|
+
*/
|
|
29
|
+
/**
|
|
30
|
+
* Replace `${vault:service:key}` placeholders in a string. Reuses the
|
|
31
|
+
* existing service/key-credential model — the MCP credentials live in
|
|
32
|
+
* the same vault as everything else (no parallel abstraction).
|
|
33
|
+
*
|
|
34
|
+
* Example: `Bearer ${vault:polpo:api_key}` resolves to the `api_key`
|
|
35
|
+
* credential of the `polpo` vault entry.
|
|
36
|
+
*/
|
|
37
|
+
function applyVault(input, vault) {
|
|
38
|
+
if (!vault)
|
|
39
|
+
return input;
|
|
40
|
+
return input.replace(/\$\{vault:([a-z0-9_-]+):([a-z0-9_-]+)\}/gi, (_, service, key) => {
|
|
41
|
+
const value = vault.getKey(service, key);
|
|
42
|
+
if (value === undefined) {
|
|
43
|
+
throw new Error(`Vault credential "${service}:${key}" not found — required by MCP server config`);
|
|
44
|
+
}
|
|
45
|
+
return value;
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
function resolveHeaders(headers, vault) {
|
|
49
|
+
if (!headers)
|
|
50
|
+
return undefined;
|
|
51
|
+
const out = {};
|
|
52
|
+
for (const [k, v] of Object.entries(headers)) {
|
|
53
|
+
out[k] = applyVault(v, vault);
|
|
54
|
+
}
|
|
55
|
+
return out;
|
|
56
|
+
}
|
|
57
|
+
function parseAllowedHosts() {
|
|
58
|
+
const raw = process.env.POLPO_MCP_ALLOWED_HOSTS;
|
|
59
|
+
if (!raw)
|
|
60
|
+
return null;
|
|
61
|
+
return raw
|
|
62
|
+
.split(",")
|
|
63
|
+
.map((s) => s.trim())
|
|
64
|
+
.filter(Boolean);
|
|
65
|
+
}
|
|
66
|
+
function hostAllowed(hostname, patterns) {
|
|
67
|
+
for (const p of patterns) {
|
|
68
|
+
if (p === hostname)
|
|
69
|
+
return true;
|
|
70
|
+
if (p.startsWith("*.") && hostname.endsWith(p.slice(1)))
|
|
71
|
+
return true;
|
|
72
|
+
}
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Adapt an AI SDK `Tool` (from `mcpClient.tools()`) into a Polpo
|
|
77
|
+
* `PolpoTool`. The runtime contracts overlap on `description` + an input
|
|
78
|
+
* schema + an executor; the wrapper bridges the small differences:
|
|
79
|
+
*
|
|
80
|
+
* - AI SDK execute takes `(args, ctx)` and returns the raw server output
|
|
81
|
+
* (string, object, or `CallToolResult`). Polpo's executor returns
|
|
82
|
+
* `{ content: [{ type: "text", text }], details }`.
|
|
83
|
+
* - MCP can return image content; we pass it through verbatim when
|
|
84
|
+
* present so vision-capable agents can consume it.
|
|
85
|
+
* - Errors from the underlying transport bubble as `Error` text content
|
|
86
|
+
* — the agent loop already knows how to display tool errors.
|
|
87
|
+
*/
|
|
88
|
+
function adaptMcpTool(serverName, toolName, aiTool) {
|
|
89
|
+
return {
|
|
90
|
+
name: `mcp__${serverName}__${toolName}`,
|
|
91
|
+
label: toolName,
|
|
92
|
+
description: aiTool.description ?? "",
|
|
93
|
+
// AI SDK wraps schemas in a Zod/JSON Schema container; Polpo passes
|
|
94
|
+
// the raw schema through to `jsonSchema()` downstream which accepts
|
|
95
|
+
// arbitrary JSON-Schema-shaped objects, so this works without a
|
|
96
|
+
// typebox conversion.
|
|
97
|
+
parameters: aiTool.inputSchema,
|
|
98
|
+
execute: async (toolCallId, params, signal) => {
|
|
99
|
+
try {
|
|
100
|
+
const raw = await aiTool.execute(params, {
|
|
101
|
+
toolCallId,
|
|
102
|
+
messages: [],
|
|
103
|
+
abortSignal: signal,
|
|
104
|
+
});
|
|
105
|
+
return coerceMcpResult(raw);
|
|
106
|
+
}
|
|
107
|
+
catch (err) {
|
|
108
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
109
|
+
return {
|
|
110
|
+
content: [{ type: "text", text: `MCP tool error: ${message}` }],
|
|
111
|
+
details: { error: message },
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
},
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* MCP servers can return: a plain string, a structured object, or a
|
|
119
|
+
* `CallToolResult` with a `content[]` array. Normalize into Polpo's
|
|
120
|
+
* `ToolResult`. Pass image content through; serialize unknown shapes
|
|
121
|
+
* as JSON so the LLM at least gets readable text.
|
|
122
|
+
*/
|
|
123
|
+
function coerceMcpResult(raw) {
|
|
124
|
+
if (raw == null) {
|
|
125
|
+
return { content: [{ type: "text", text: "" }], details: null };
|
|
126
|
+
}
|
|
127
|
+
if (typeof raw === "string") {
|
|
128
|
+
return { content: [{ type: "text", text: raw }], details: raw };
|
|
129
|
+
}
|
|
130
|
+
if (typeof raw === "object" && raw !== null && Array.isArray(raw.content)) {
|
|
131
|
+
const content = raw.content
|
|
132
|
+
.map((part) => {
|
|
133
|
+
if (part?.type === "text" && typeof part.text === "string") {
|
|
134
|
+
return { type: "text", text: part.text };
|
|
135
|
+
}
|
|
136
|
+
if (part?.type === "image" && typeof part.data === "string") {
|
|
137
|
+
return {
|
|
138
|
+
type: "image",
|
|
139
|
+
data: part.data,
|
|
140
|
+
mimeType: part.mimeType ?? "image/png",
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
// Unknown content part — serialize so we don't lose information
|
|
144
|
+
return { type: "text", text: JSON.stringify(part) };
|
|
145
|
+
})
|
|
146
|
+
.filter(Boolean);
|
|
147
|
+
return { content, details: raw };
|
|
148
|
+
}
|
|
149
|
+
return {
|
|
150
|
+
content: [{ type: "text", text: JSON.stringify(raw) }],
|
|
151
|
+
details: raw,
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Open every MCP server declared on the agent config. Returns the
|
|
156
|
+
* aggregated Polpo tools + a single `dispose` that closes them all.
|
|
157
|
+
*
|
|
158
|
+
* Failures on individual servers are logged but don't abort the rest:
|
|
159
|
+
* an agent with one broken MCP and one good one still gets the good
|
|
160
|
+
* tools, with a console error pointing at the bad config.
|
|
161
|
+
*/
|
|
162
|
+
export async function resolveAgentMcpTools(agentName, mcpServers, vault) {
|
|
163
|
+
if (!mcpServers || Object.keys(mcpServers).length === 0) {
|
|
164
|
+
return { tools: [], dispose: async () => { } };
|
|
165
|
+
}
|
|
166
|
+
// Lazy-loaded so projects that never use MCP don't pay the import cost
|
|
167
|
+
// (the SDK pulls in @modelcontextprotocol/sdk transitively which is
|
|
168
|
+
// non-trivial — a few hundred KB at module-init time).
|
|
169
|
+
// Cast through any: vitest's vi.mock substitutes a single-arg factory
|
|
170
|
+
// that doesn't match the published SDK's variadic typing. Runtime is
|
|
171
|
+
// identical.
|
|
172
|
+
const { createMCPClient } = (await import("@ai-sdk/mcp"));
|
|
173
|
+
const allowedHosts = parseAllowedHosts();
|
|
174
|
+
const closers = [];
|
|
175
|
+
const tools = [];
|
|
176
|
+
for (const [serverName, spec] of Object.entries(mcpServers)) {
|
|
177
|
+
try {
|
|
178
|
+
// Validate host upfront for HTTP/SSE — stdio is local-only and
|
|
179
|
+
// already gated by the sandbox boundary.
|
|
180
|
+
if (spec.type === "http" || spec.type === "sse") {
|
|
181
|
+
if (allowedHosts) {
|
|
182
|
+
const url = new URL(spec.url);
|
|
183
|
+
if (!hostAllowed(url.hostname, allowedHosts)) {
|
|
184
|
+
throw new Error(`MCP host "${url.hostname}" not in POLPO_MCP_ALLOWED_HOSTS`);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
let transport;
|
|
189
|
+
if (spec.type === "sse") {
|
|
190
|
+
transport = {
|
|
191
|
+
type: "sse",
|
|
192
|
+
url: spec.url,
|
|
193
|
+
headers: resolveHeaders(spec.headers, vault),
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
else if (spec.type === "stdio" || spec.command) {
|
|
197
|
+
const stdioSpec = spec;
|
|
198
|
+
const { Experimental_StdioMCPTransport } = await import("@ai-sdk/mcp/mcp-stdio");
|
|
199
|
+
transport = new Experimental_StdioMCPTransport({
|
|
200
|
+
command: stdioSpec.command,
|
|
201
|
+
args: stdioSpec.args,
|
|
202
|
+
env: stdioSpec.env,
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
else {
|
|
206
|
+
// Default to HTTP — handles both `{ type: "http", url }` and the
|
|
207
|
+
// forgiving `{ url }` shorthand.
|
|
208
|
+
const httpSpec = spec;
|
|
209
|
+
transport = {
|
|
210
|
+
type: "http",
|
|
211
|
+
url: httpSpec.url,
|
|
212
|
+
headers: resolveHeaders(httpSpec.headers, vault),
|
|
213
|
+
// Defense-in-depth: refuse 3xx so an attacker can't bounce us
|
|
214
|
+
// off-allowlist via a redirect.
|
|
215
|
+
redirect: "error",
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
const client = await createMCPClient({ transport });
|
|
219
|
+
closers.push(() => client.close().catch(() => { }));
|
|
220
|
+
const serverTools = await client.tools();
|
|
221
|
+
for (const [toolName, aiTool] of Object.entries(serverTools)) {
|
|
222
|
+
tools.push(adaptMcpTool(serverName, toolName, aiTool));
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
catch (err) {
|
|
226
|
+
console.error(`[mcp] agent="${agentName}" server="${serverName}" failed:`, err instanceof Error ? err.message : err);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
return {
|
|
230
|
+
tools,
|
|
231
|
+
dispose: async () => {
|
|
232
|
+
await Promise.all(closers.map((fn) => fn()));
|
|
233
|
+
},
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
//# sourceMappingURL=mcp-client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mcp-client.js","sourceRoot":"","sources":["../src/mcp-client.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@polpo-ai/tools",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.26",
|
|
4
4
|
"description": "Agent tools for Polpo — coding, browser, email, search, and more. Core tools work everywhere, extended tools are opt-in.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -16,19 +16,21 @@
|
|
|
16
16
|
"README.md"
|
|
17
17
|
],
|
|
18
18
|
"dependencies": {
|
|
19
|
+
"@ai-sdk/mcp": "^1.0.36",
|
|
20
|
+
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
19
21
|
"@sinclair/typebox": "^0.34.0",
|
|
20
22
|
"nanoid": "^5.0.0",
|
|
21
|
-
"@polpo-ai/core": "0.6.
|
|
23
|
+
"@polpo-ai/core": "0.6.26"
|
|
22
24
|
},
|
|
23
25
|
"optionalDependencies": {
|
|
26
|
+
"docx": "^9.5.0",
|
|
27
|
+
"exceljs": "^4.4.0",
|
|
24
28
|
"execa": "^9.0.0",
|
|
25
|
-
"playwright-core": "^1.52.0",
|
|
26
|
-
"nodemailer": "^8.0.0",
|
|
27
29
|
"imapflow": "^1.2.0",
|
|
28
|
-
"
|
|
30
|
+
"mammoth": "^1.11.0",
|
|
31
|
+
"nodemailer": "^8.0.0",
|
|
29
32
|
"pdf-lib": "^1.17.0",
|
|
30
|
-
"
|
|
31
|
-
"mammoth": "^1.11.0"
|
|
33
|
+
"playwright-core": "^1.52.0"
|
|
32
34
|
},
|
|
33
35
|
"peerDependencies": {
|
|
34
36
|
"zod": ">=3.0.0 || >=4.0.0"
|