@agentwares/agentguard 0.1.6 → 0.1.7

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/CHANGELOG.md CHANGED
@@ -3,6 +3,20 @@
3
3
  `@agentwares/agentguard`, the CLI and MCP proxy. A tag `agentguard-v<version>` publishes the
4
4
  GitHub Release for a version with its section below as the notes.
5
5
 
6
+ ## 0.1.7 — 2026-10-07
7
+
8
+ ### Fixed
9
+
10
+ - **No endless loop on an upstream that repeats its `tools/list` cursor.** The proxy followed
11
+ `nextCursor` with no bound, so an upstream that handed back the cursor it was given (or cycled
12
+ through a few) kept the proxy asking for the same page forever, its tool table growing with
13
+ every answer. It now reads every page to the end, stops at a cursor it has already followed, and
14
+ reads at most 20 pages and 2,000 tools per upstream.
15
+ - **A failed or partial `tools/list` no longer empties an upstream's tools.** If a refresh errors,
16
+ or stops at that ceiling, the proxy keeps the upstream's last good list and logs why
17
+ (`tools/list for "<upstream>" not applied: … ; keeping the last good list (N tools)`). It used
18
+ to drop every tool of that upstream on any `tools/list` error.
19
+
6
20
  ## 0.1.6 — 2026-10-07
7
21
 
8
22
  ### Added
package/dist/cli.js CHANGED
@@ -1918,6 +1918,48 @@ function localhostHosts(port) {
1918
1918
  return [...hosts, ...hosts.map((h) => `${h}:${port}`)];
1919
1919
  }
1920
1920
 
1921
+ // ../../packages/mcp-kit/dist/chunk-M567BWMA.js
1922
+ var LIST_MAX_PAGES = 20;
1923
+ var LIST_MAX_ITEMS = 2e3;
1924
+ async function readAllPages(fetchPage, opts = {}) {
1925
+ const maxPages = opts.maxPages ?? LIST_MAX_PAGES;
1926
+ const maxItems = opts.maxItems ?? LIST_MAX_ITEMS;
1927
+ const method = opts.method ?? "tools/list";
1928
+ const items = [];
1929
+ const followed = /* @__PURE__ */ new Set();
1930
+ let cursor;
1931
+ for (let page = 1; ; page++) {
1932
+ const res = await fetchPage(cursor, page);
1933
+ if (Array.isArray(res.items)) items.push(...res.items);
1934
+ const next = res.nextCursor;
1935
+ const more = typeof next === "string" && next !== "";
1936
+ if (items.length > maxItems) {
1937
+ return {
1938
+ items,
1939
+ pages: page,
1940
+ truncated: {
1941
+ ceiling: "items",
1942
+ reason: `${method} listed more than ${maxItems} items in ${page} page${page === 1 ? "" : "s"}${more ? " and had more" : ""}; at most ${maxItems} are read`
1943
+ }
1944
+ };
1945
+ }
1946
+ if (!more) return { items, pages: page };
1947
+ if (followed.has(next)) return { items, pages: page, repeatedCursor: true };
1948
+ if (page >= maxPages) {
1949
+ return {
1950
+ items,
1951
+ pages: page,
1952
+ truncated: {
1953
+ ceiling: "pages",
1954
+ reason: `${method} had more than ${maxPages} pages (${items.length} items read); at most ${maxPages} pages are read`
1955
+ }
1956
+ };
1957
+ }
1958
+ followed.add(next);
1959
+ cursor = next;
1960
+ }
1961
+ }
1962
+
1921
1963
  // ../../packages/mcp-kit/dist/index.js
1922
1964
  import { McpServer as McpServer2 } from "@modelcontextprotocol/sdk/server/mcp.js";
1923
1965
  async function serveStdio(serverOrOpts) {
@@ -3557,6 +3599,13 @@ var ProxyRuntime = class {
3557
3599
  }
3558
3600
  }
3559
3601
  }
3602
+ /**
3603
+ * Re-read an upstream's tools, every page (mcp-kit's `readAllPages`): at most 20 requests, each
3604
+ * under the upstream's `timeout_ms`, ending at a cursor already followed. It used to follow
3605
+ * `nextCursor` with no bound, so an upstream that echoed its cursor kept the proxy asking for
3606
+ * the same page forever. A refresh that fails, or stops at the 20-page / 2,000-tool ceiling,
3607
+ * changes nothing: the agent keeps the last good list, and the log says why.
3608
+ */
3560
3609
  async refreshTools(name) {
3561
3610
  const conn = this.connections.get(name);
3562
3611
  if (!conn?.connected) return;
@@ -3564,22 +3613,23 @@ var ProxyRuntime = class {
3564
3613
  conn.tools = [];
3565
3614
  return;
3566
3615
  }
3616
+ const kept = () => `keeping the last good list (${conn.tools.length} tool${conn.tools.length === 1 ? "" : "s"})`;
3567
3617
  try {
3568
- const tools = [];
3569
- let cursor;
3570
- do {
3618
+ const read = await readAllPages(async (cursor) => {
3571
3619
  const page = await conn.client.listTools(cursor ? { cursor } : void 0, {
3572
3620
  timeout: conn.upstream.timeout_ms
3573
3621
  });
3574
- tools.push(...page.tools);
3575
- cursor = page.nextCursor;
3576
- } while (cursor);
3577
- conn.tools = tools;
3622
+ return { items: page.tools, nextCursor: page.nextCursor };
3623
+ });
3624
+ if (read.truncated) {
3625
+ this.log(`tools/list for "${name}" not applied: ${read.truncated.reason}; ${kept()}`);
3626
+ return;
3627
+ }
3628
+ conn.tools = read.items;
3578
3629
  } catch (err) {
3579
3630
  this.log(
3580
- `tools/list failed for "${name}": ${err instanceof Error ? err.message : String(err)}`
3631
+ `tools/list failed for "${name}": ${err instanceof Error ? err.message : String(err)}; ${kept()}`
3581
3632
  );
3582
- conn.tools = [];
3583
3633
  }
3584
3634
  }
3585
3635
  rebuildExposed() {