pennyrouter 0.3.54 → 0.3.56
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/package.json +1 -1
- package/src/mcp.js +29 -0
- package/src/mcp.test.js +29 -1
package/package.json
CHANGED
package/src/mcp.js
CHANGED
|
@@ -11,6 +11,9 @@
|
|
|
11
11
|
thread costs the caller nothing. That asymmetry is the whole reason for two
|
|
12
12
|
tools rather than one with a flag.
|
|
13
13
|
thread_transfer capture the current local session and write it for the other harness.
|
|
14
|
+
load_tools make tools that PennyRouter left out of the request available. The gateway
|
|
15
|
+
reads this call from the conversation history and includes the named tools
|
|
16
|
+
on the next request; the call itself does no work here.
|
|
14
17
|
|
|
15
18
|
Because the destination is a parameter, "resume this Perplexity thread in Codex" and "…in
|
|
16
19
|
Claude Code" are the same call with a different `harness`. Each harness has its own writer:
|
|
@@ -119,6 +122,24 @@ const TOOLS = [
|
|
|
119
122
|
required: ["target_harness"],
|
|
120
123
|
},
|
|
121
124
|
},
|
|
125
|
+
{
|
|
126
|
+
name: "load_tools",
|
|
127
|
+
description:
|
|
128
|
+
"Load tools listed under \"Tools available on request\" in your instructions. Pass the " +
|
|
129
|
+
"exact tool names, or a namespace name to load all of its tools. They are callable on " +
|
|
130
|
+
"your next step. Only load what the current task needs.",
|
|
131
|
+
inputSchema: {
|
|
132
|
+
type: "object",
|
|
133
|
+
properties: {
|
|
134
|
+
names: {
|
|
135
|
+
type: "array",
|
|
136
|
+
items: { type: "string" },
|
|
137
|
+
description: "Tool or namespace names from the available-on-request list.",
|
|
138
|
+
},
|
|
139
|
+
},
|
|
140
|
+
required: ["names"],
|
|
141
|
+
},
|
|
142
|
+
},
|
|
122
143
|
];
|
|
123
144
|
|
|
124
145
|
function flattenClaudeProject(cwd) {
|
|
@@ -294,6 +315,14 @@ async function callTool(name, args, context) {
|
|
|
294
315
|
].join("\n");
|
|
295
316
|
}
|
|
296
317
|
|
|
318
|
+
if (name === "load_tools") {
|
|
319
|
+
const names = (Array.isArray(args?.names) ? args.names : [])
|
|
320
|
+
.map((value) => String(value).trim())
|
|
321
|
+
.filter(Boolean);
|
|
322
|
+
if (!names.length) throw new Error("names is required");
|
|
323
|
+
return `Loaded: ${names.join(", ")}. Call them directly now.`;
|
|
324
|
+
}
|
|
325
|
+
|
|
297
326
|
throw new Error(`unknown tool: ${name}`);
|
|
298
327
|
}
|
|
299
328
|
|
package/src/mcp.test.js
CHANGED
|
@@ -116,7 +116,7 @@ const workspace = mkdtempSync(join(tmpdir(), "pr-mcp-ws-"));
|
|
|
116
116
|
|
|
117
117
|
assert.deepEqual(
|
|
118
118
|
byId.get(2).result.tools.map((t) => t.name),
|
|
119
|
-
["thread_read", "thread_resume", "thread_transfer"],
|
|
119
|
+
["thread_read", "thread_resume", "thread_transfer", "load_tools"],
|
|
120
120
|
);
|
|
121
121
|
|
|
122
122
|
// Observed in a real session: a blocked tool call is indistinguishable from an empty one, so
|
|
@@ -153,6 +153,34 @@ const workspace = mkdtempSync(join(tmpdir(), "pr-mcp-ws-"));
|
|
|
153
153
|
assert.equal(byId.get(4).result.isError, undefined);
|
|
154
154
|
}
|
|
155
155
|
|
|
156
|
+
// -- load_tools ------------------------------------------------------------
|
|
157
|
+
// The gateway loads the tools by reading this call from history; the server only confirms, and
|
|
158
|
+
// must not contact the gateway.
|
|
159
|
+
{
|
|
160
|
+
let contacted = false;
|
|
161
|
+
const { server, port } = await stubGateway((req, body, res) => {
|
|
162
|
+
contacted = true;
|
|
163
|
+
res.end("{}");
|
|
164
|
+
});
|
|
165
|
+
const { messages } = await rpc(
|
|
166
|
+
[
|
|
167
|
+
{
|
|
168
|
+
jsonrpc: "2.0", id: 1, method: "tools/call",
|
|
169
|
+
params: { name: "load_tools", arguments: { names: ["list_database_connections", " "] } },
|
|
170
|
+
},
|
|
171
|
+
{ jsonrpc: "2.0", id: 2, method: "tools/call", params: { name: "load_tools", arguments: {} } },
|
|
172
|
+
],
|
|
173
|
+
{ port, home },
|
|
174
|
+
);
|
|
175
|
+
server.close();
|
|
176
|
+
const byId = new Map(messages.map((m) => [m.id, m]));
|
|
177
|
+
assert.equal(byId.get(1).result.content[0].text,
|
|
178
|
+
"Loaded: list_database_connections. Call them directly now.");
|
|
179
|
+
assert.equal(byId.get(1).result.isError, undefined);
|
|
180
|
+
assert.equal(byId.get(2).result.isError, true);
|
|
181
|
+
assert.equal(contacted, false);
|
|
182
|
+
}
|
|
183
|
+
|
|
156
184
|
// -- harness routing ------------------------------------------------------
|
|
157
185
|
// "Resume this in Codex" is the same call with a different `harness`, so the tool must dispatch
|
|
158
186
|
// to the right writer and print the right resume command. Skipped when codex is not installed:
|