@skydiveai/pi-extensions 0.1.258-beta.8 → 0.1.258-beta.80

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.
Files changed (2) hide show
  1. package/dist/index.mjs +98 -16
  2. package/package.json +1 -1
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: o.tools.list
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 = JSON.stringify(serverConfig);
1931
+ const configKey = connectionKey(serverConfig);
1866
1932
  let action;
1867
1933
  let connected;
1868
- if (existing && existing.configKey === configKey) {
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(", ")}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@skydiveai/pi-extensions",
3
- "version": "0.1.258-beta.8",
3
+ "version": "0.1.258-beta.80",
4
4
  "homepage": "https://skydive.com",
5
5
  "license": "MIT",
6
6
  "author": "Create, Inc.",