@skydiveai/pi-extensions 0.1.258-beta.6 → 0.1.258-beta.60
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/index.d.mts +1 -19
- package/dist/index.mjs +98 -16
- package/package.json +1 -1
package/dist/index.d.mts
CHANGED
|
@@ -1,27 +1,9 @@
|
|
|
1
|
-
/// <reference types="node" />
|
|
2
1
|
import { A2AOptions, Middleware, ProtocolsOptions } from "@skydiveai/pi-server";
|
|
3
2
|
import { AgentSession, ExtensionFactory } from "@earendil-works/pi-coding-agent";
|
|
4
3
|
import { Type } from "typebox";
|
|
5
4
|
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
6
|
-
import * as _$qs from "qs";
|
|
7
|
-
import * as _$express from "express";
|
|
8
5
|
import { Logger } from "pino";
|
|
9
6
|
|
|
10
|
-
//#region ../../node_modules/@types/express-serve-static-core/index.d.ts
|
|
11
|
-
declare global {
|
|
12
|
-
namespace Express {
|
|
13
|
-
// These open interfaces may be extended in an application-specific manner via declaration merging.
|
|
14
|
-
// See for example method-override.d.ts (https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/method-override/index.d.ts)
|
|
15
|
-
interface Request {}
|
|
16
|
-
interface Response {}
|
|
17
|
-
interface Locals {}
|
|
18
|
-
interface Application {}
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
interface ParamsDictionary {
|
|
22
|
-
[key: string]: string;
|
|
23
|
-
}
|
|
24
|
-
//#endregion
|
|
25
7
|
//#region src/harness.d.ts
|
|
26
8
|
type CreateHarnessOptions = ProtocolsOptions & {
|
|
27
9
|
cwd: string;
|
|
@@ -44,7 +26,7 @@ declare function createHarness(options: CreateHarnessOptions): {
|
|
|
44
26
|
};
|
|
45
27
|
protocols: {
|
|
46
28
|
handlers: {
|
|
47
|
-
/** Express-style; mount at /a2a. */a2a:
|
|
29
|
+
/** Express-style; mount at /a2a. */a2a: RequestHandler; /** GET /.well-known/agent-card.json. */
|
|
48
30
|
agentCard: Middleware; /** Mirrors each vendor's API shape. */
|
|
49
31
|
openai: {
|
|
50
32
|
v1: {
|
package/dist/index.mjs
CHANGED
|
@@ -1288,17 +1288,23 @@ function legacyMcpToolName({ serverName, toolName }) {
|
|
|
1288
1288
|
* a small, focused unit-test surface (typebox validation, error formatting).
|
|
1289
1289
|
*/
|
|
1290
1290
|
const MCP_CONFIG_FILENAME = "mcp.config.json";
|
|
1291
|
+
const ToolSelectionSchema = {
|
|
1292
|
+
tools: Type.Optional(Type.Union([Type.Array(Type.String()), Type.Null()])),
|
|
1293
|
+
excludeTools: Type.Optional(Type.Union([Type.Array(Type.String()), Type.Null()]))
|
|
1294
|
+
};
|
|
1291
1295
|
const StdioServerSchema = Type.Object({
|
|
1292
1296
|
transport: Type.Literal("stdio"),
|
|
1293
1297
|
command: Type.String(),
|
|
1294
1298
|
args: Type.Array(Type.String()),
|
|
1295
1299
|
env: Type.Optional(Type.Union([Type.Record(Type.String(), Type.String()), Type.Null()])),
|
|
1296
|
-
cwd: Type.Optional(Type.Union([Type.String(), Type.Null()]))
|
|
1300
|
+
cwd: Type.Optional(Type.Union([Type.String(), Type.Null()])),
|
|
1301
|
+
...ToolSelectionSchema
|
|
1297
1302
|
});
|
|
1298
1303
|
const HttpServerSchema = Type.Object({
|
|
1299
1304
|
transport: Type.Literal("http"),
|
|
1300
1305
|
url: Type.String(),
|
|
1301
|
-
headers: Type.Optional(Type.Union([Type.Record(Type.String(), Type.String()), Type.Null()]))
|
|
1306
|
+
headers: Type.Optional(Type.Union([Type.Record(Type.String(), Type.String()), Type.Null()])),
|
|
1307
|
+
...ToolSelectionSchema
|
|
1302
1308
|
});
|
|
1303
1309
|
const ServerSchema = Type.Union([StdioServerSchema, HttpServerSchema]);
|
|
1304
1310
|
const McpConfigSchema = Type.Object({ servers: Type.Record(Type.String(), ServerSchema) });
|
|
@@ -1325,11 +1331,15 @@ async function loadMcpConfig(path) {
|
|
|
1325
1331
|
for (const [id, server] of Object.entries(json.servers)) if (server.transport === "stdio") servers[id] = {
|
|
1326
1332
|
...server,
|
|
1327
1333
|
env: server.env ?? null,
|
|
1328
|
-
cwd: server.cwd ?? null
|
|
1334
|
+
cwd: server.cwd ?? null,
|
|
1335
|
+
tools: server.tools ?? null,
|
|
1336
|
+
excludeTools: server.excludeTools ?? null
|
|
1329
1337
|
};
|
|
1330
1338
|
else servers[id] = {
|
|
1331
1339
|
...server,
|
|
1332
|
-
headers: server.headers ?? null
|
|
1340
|
+
headers: server.headers ?? null,
|
|
1341
|
+
tools: server.tools ?? null,
|
|
1342
|
+
excludeTools: server.excludeTools ?? null
|
|
1333
1343
|
};
|
|
1334
1344
|
return { servers };
|
|
1335
1345
|
}
|
|
@@ -1491,6 +1501,54 @@ function toProviderSafeSchema(inputSchema) {
|
|
|
1491
1501
|
* reloads — only changed configs reconnect.
|
|
1492
1502
|
*/
|
|
1493
1503
|
const log$17 = logger.child({ module: "mcp-extension" });
|
|
1504
|
+
function resolveToolSelection(server) {
|
|
1505
|
+
return {
|
|
1506
|
+
allow: server?.tools?.length ? new Set(server.tools) : null,
|
|
1507
|
+
deny: new Set(server?.excludeTools ?? [])
|
|
1508
|
+
};
|
|
1509
|
+
}
|
|
1510
|
+
/**
|
|
1511
|
+
* Connection identity of a server config: everything except the per-server
|
|
1512
|
+
* tool selection. `tools`/`excludeTools` only filter the advertised list at
|
|
1513
|
+
* registration time, so an edit to them must re-register tools, not tear
|
|
1514
|
+
* down and re-handshake the client (kills the stdio process, re-runs the
|
|
1515
|
+
* http OAuth/handshake for an identical connection).
|
|
1516
|
+
*/
|
|
1517
|
+
function connectionKey(serverConfig) {
|
|
1518
|
+
const { tools: _tools, excludeTools: _excludeTools, ...connection } = serverConfig;
|
|
1519
|
+
return JSON.stringify(connection);
|
|
1520
|
+
}
|
|
1521
|
+
/**
|
|
1522
|
+
* Apply a server's configured tool selection to its advertised list.
|
|
1523
|
+
* Runs BEFORE the budget allocator: a filtered set is what competes for
|
|
1524
|
+
* the per-server / total / token caps, so an allowlisted tool wins the
|
|
1525
|
+
* budget contest instead of being truncated behind tools the config
|
|
1526
|
+
* never asked for. Intentional exclusions are NOT budget drops — they
|
|
1527
|
+
* don't count toward `droppedTools` and produce no truncation warning.
|
|
1528
|
+
*/
|
|
1529
|
+
function applyToolSelection(tools, selection) {
|
|
1530
|
+
if (selection.allow === null && selection.deny.size === 0) return {
|
|
1531
|
+
list: tools,
|
|
1532
|
+
filteredOut: 0,
|
|
1533
|
+
unknownTools: [],
|
|
1534
|
+
unknownExcludeTools: []
|
|
1535
|
+
};
|
|
1536
|
+
const kept = [];
|
|
1537
|
+
for (const tool of tools) {
|
|
1538
|
+
if (selection.allow !== null && !selection.allow.has(tool.name)) continue;
|
|
1539
|
+
if (selection.deny.has(tool.name)) continue;
|
|
1540
|
+
kept.push(tool);
|
|
1541
|
+
}
|
|
1542
|
+
const notAdvertised = (names) => [...names].filter((name) => !tools.some((tool) => tool.name === name));
|
|
1543
|
+
const unknownTools = selection.allow === null ? [] : notAdvertised(selection.allow);
|
|
1544
|
+
const unknownExcludeTools = selection.deny.size === 0 ? [] : notAdvertised(selection.deny);
|
|
1545
|
+
return {
|
|
1546
|
+
list: kept,
|
|
1547
|
+
filteredOut: tools.length - kept.length,
|
|
1548
|
+
unknownTools,
|
|
1549
|
+
unknownExcludeTools
|
|
1550
|
+
};
|
|
1551
|
+
}
|
|
1494
1552
|
async function closeConnected(connected) {
|
|
1495
1553
|
try {
|
|
1496
1554
|
await connected.client.close();
|
|
@@ -1797,9 +1855,11 @@ var McpExtension = class {
|
|
|
1797
1855
|
else if (outcome.change === "refreshed") summary.refreshed.push(id);
|
|
1798
1856
|
}
|
|
1799
1857
|
const alloc = outcomes.filter((o) => Boolean(o.tools)).map((o) => {
|
|
1858
|
+
const selection = resolveToolSelection(config.servers[o.id]);
|
|
1859
|
+
const picked = applyToolSelection(o.tools.list, selection);
|
|
1800
1860
|
const { tools: list, skipped } = providerSafeTools({
|
|
1801
1861
|
serverId: o.id,
|
|
1802
|
-
tools:
|
|
1862
|
+
tools: picked.list
|
|
1803
1863
|
});
|
|
1804
1864
|
return {
|
|
1805
1865
|
id: o.id,
|
|
@@ -1807,6 +1867,9 @@ var McpExtension = class {
|
|
|
1807
1867
|
list,
|
|
1808
1868
|
advertised: o.tools.list.length,
|
|
1809
1869
|
skipped,
|
|
1870
|
+
filtered: picked.filteredOut,
|
|
1871
|
+
unknownTools: picked.unknownTools,
|
|
1872
|
+
unknownExcludeTools: picked.unknownExcludeTools,
|
|
1810
1873
|
room: Math.min(list.length, limits.maxToolsPerServer),
|
|
1811
1874
|
registered: 0,
|
|
1812
1875
|
serverTokens: 0,
|
|
@@ -1850,7 +1913,10 @@ var McpExtension = class {
|
|
|
1850
1913
|
registered: s.registered,
|
|
1851
1914
|
tokens: s.serverTokens,
|
|
1852
1915
|
droppedReason,
|
|
1853
|
-
skipped: s.skipped
|
|
1916
|
+
skipped: s.skipped,
|
|
1917
|
+
filtered: s.filtered,
|
|
1918
|
+
unknownTools: s.unknownTools,
|
|
1919
|
+
unknownExcludeTools: s.unknownExcludeTools
|
|
1854
1920
|
};
|
|
1855
1921
|
}
|
|
1856
1922
|
return summary;
|
|
@@ -1862,10 +1928,10 @@ var McpExtension = class {
|
|
|
1862
1928
|
* map/summary, so callers can run these concurrently.
|
|
1863
1929
|
*/
|
|
1864
1930
|
async reconcileServer({ id, serverConfig, existing, connectTimeoutMs }) {
|
|
1865
|
-
const configKey =
|
|
1931
|
+
const configKey = connectionKey(serverConfig);
|
|
1866
1932
|
let action;
|
|
1867
1933
|
let connected;
|
|
1868
|
-
if (existing && existing.
|
|
1934
|
+
if (existing && existing.connectionKey === configKey) {
|
|
1869
1935
|
connected = existing;
|
|
1870
1936
|
action = "reused";
|
|
1871
1937
|
} else {
|
|
@@ -1891,7 +1957,7 @@ var McpExtension = class {
|
|
|
1891
1957
|
id,
|
|
1892
1958
|
store: {
|
|
1893
1959
|
client: result.client,
|
|
1894
|
-
configKey,
|
|
1960
|
+
connectionKey: configKey,
|
|
1895
1961
|
status: "pending_auth",
|
|
1896
1962
|
stderrBuffer: result.stderrBuffer,
|
|
1897
1963
|
cliHint: result.cliHint
|
|
@@ -1909,7 +1975,7 @@ var McpExtension = class {
|
|
|
1909
1975
|
id,
|
|
1910
1976
|
store: {
|
|
1911
1977
|
client: result.client,
|
|
1912
|
-
configKey,
|
|
1978
|
+
connectionKey: configKey,
|
|
1913
1979
|
status: "timeout",
|
|
1914
1980
|
stderrBuffer: result.stderrBuffer,
|
|
1915
1981
|
cliHint: null
|
|
@@ -1925,7 +1991,7 @@ var McpExtension = class {
|
|
|
1925
1991
|
};
|
|
1926
1992
|
connected = {
|
|
1927
1993
|
client: result.client,
|
|
1928
|
-
configKey,
|
|
1994
|
+
connectionKey: configKey,
|
|
1929
1995
|
status: "connected",
|
|
1930
1996
|
stderrBuffer: result.stderr,
|
|
1931
1997
|
cliHint: null
|
|
@@ -1939,7 +2005,7 @@ var McpExtension = class {
|
|
|
1939
2005
|
id,
|
|
1940
2006
|
store: {
|
|
1941
2007
|
client: retry.client,
|
|
1942
|
-
configKey,
|
|
2008
|
+
connectionKey: configKey,
|
|
1943
2009
|
status: "pending_auth",
|
|
1944
2010
|
stderrBuffer: null,
|
|
1945
2011
|
cliHint: retry.cliHint
|
|
@@ -1957,7 +2023,7 @@ var McpExtension = class {
|
|
|
1957
2023
|
id,
|
|
1958
2024
|
store: {
|
|
1959
2025
|
client: retry.client,
|
|
1960
|
-
configKey,
|
|
2026
|
+
connectionKey: configKey,
|
|
1961
2027
|
status: "timeout",
|
|
1962
2028
|
stderrBuffer: retry.stderrBuffer,
|
|
1963
2029
|
cliHint: null
|
|
@@ -1985,7 +2051,7 @@ var McpExtension = class {
|
|
|
1985
2051
|
};
|
|
1986
2052
|
connected = {
|
|
1987
2053
|
client: retry.client,
|
|
1988
|
-
configKey,
|
|
2054
|
+
connectionKey: configKey,
|
|
1989
2055
|
status: "connected",
|
|
1990
2056
|
stderrBuffer: retry.stderr,
|
|
1991
2057
|
cliHint: null
|
|
@@ -2017,7 +2083,7 @@ var McpExtension = class {
|
|
|
2017
2083
|
id,
|
|
2018
2084
|
store: {
|
|
2019
2085
|
client: retry.client,
|
|
2020
|
-
configKey,
|
|
2086
|
+
connectionKey: configKey,
|
|
2021
2087
|
status: "pending_auth",
|
|
2022
2088
|
stderrBuffer: null,
|
|
2023
2089
|
cliHint: retry.cliHint
|
|
@@ -2049,7 +2115,7 @@ var McpExtension = class {
|
|
|
2049
2115
|
if (retry.status === "connected") {
|
|
2050
2116
|
connected = {
|
|
2051
2117
|
client: retry.client,
|
|
2052
|
-
configKey,
|
|
2118
|
+
connectionKey: configKey,
|
|
2053
2119
|
status: "connected",
|
|
2054
2120
|
stderrBuffer: retry.stderr,
|
|
2055
2121
|
cliHint: null
|
|
@@ -2304,11 +2370,27 @@ function skippedSchemaLines(summary) {
|
|
|
2304
2370
|
for (const [id, count] of entries) for (const { name, reason } of count.skipped) lines.push(` - ${id}/${name}: ${reason}`);
|
|
2305
2371
|
return lines;
|
|
2306
2372
|
}
|
|
2373
|
+
/**
|
|
2374
|
+
* Lines naming allowlist entries in mcp.config.json that matched no tool the
|
|
2375
|
+
* server advertises — almost always a typo, and silent otherwise: the agent
|
|
2376
|
+
* just sees a narrower tool set and can't tell the config is wrong.
|
|
2377
|
+
*/
|
|
2378
|
+
function unknownToolLines(summary) {
|
|
2379
|
+
const entries = Object.entries(summary.toolCounts).filter(([, count]) => count.unknownTools.length > 0 || count.unknownExcludeTools.length > 0);
|
|
2380
|
+
if (entries.length === 0) return [];
|
|
2381
|
+
const lines = [];
|
|
2382
|
+
for (const [id, count] of entries) {
|
|
2383
|
+
if (count.unknownTools.length > 0) lines.push(` NOTE: ${id}.tools in mcp.config.json names tool(s) the server does not advertise: ${count.unknownTools.join(", ")}.`);
|
|
2384
|
+
if (count.unknownExcludeTools.length > 0) lines.push(` NOTE: ${id}.excludeTools in mcp.config.json names tool(s) the server does not advertise: ${count.unknownExcludeTools.join(", ")}.`);
|
|
2385
|
+
}
|
|
2386
|
+
return lines;
|
|
2387
|
+
}
|
|
2307
2388
|
function summaryText(summary) {
|
|
2308
2389
|
const lines = [];
|
|
2309
2390
|
lines.push(`MCP reconcile complete: ${summary.totalTools} tool(s) live.`);
|
|
2310
2391
|
lines.push(...truncationLines(summary));
|
|
2311
2392
|
lines.push(...skippedSchemaLines(summary));
|
|
2393
|
+
lines.push(...unknownToolLines(summary));
|
|
2312
2394
|
if (summary.added.length > 0) lines.push(` Added: ${summary.added.join(", ")}`);
|
|
2313
2395
|
if (summary.refreshed.length > 0) lines.push(` Refreshed: ${summary.refreshed.join(", ")}`);
|
|
2314
2396
|
if (summary.removed.length > 0) lines.push(` Removed: ${summary.removed.join(", ")}`);
|