@posthog/agent 2.1.111 → 2.1.112

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@posthog/agent",
3
- "version": "2.1.111",
3
+ "version": "2.1.112",
4
4
  "repository": "https://github.com/PostHog/twig",
5
5
  "description": "TypeScript agent framework wrapping Claude Agent SDK with Git-based task execution for PostHog",
6
6
  "exports": {
@@ -79,7 +79,6 @@
79
79
  "@anthropic-ai/claude-agent-sdk": "0.2.59",
80
80
  "@anthropic-ai/sdk": "^0.71.0",
81
81
  "@hono/node-server": "^1.19.9",
82
- "@modelcontextprotocol/sdk": "^1.25.3",
83
82
  "@opentelemetry/api-logs": "^0.208.0",
84
83
  "@opentelemetry/exporter-logs-otlp-http": "^0.208.0",
85
84
  "@opentelemetry/resources": "^2.0.0",
@@ -192,7 +192,7 @@ export class ClaudeAcpAgent extends BaseAcpAgent {
192
192
  const modelOptions = await this.getModelConfigOptions();
193
193
 
194
194
  // Deferred: slash commands + MCP metadata (not needed to return configOptions)
195
- this.deferBackgroundFetches(q, sessionId, mcpServers);
195
+ this.deferBackgroundFetches(q, sessionId);
196
196
 
197
197
  session.modelId = modelOptions.currentModelId;
198
198
  // Only call setModel if the resolved model differs from the default we
@@ -260,7 +260,7 @@ export class ClaudeAcpAgent extends BaseAcpAgent {
260
260
  }
261
261
 
262
262
  // Deferred: slash commands + MCP metadata (not needed to return configOptions)
263
- this.deferBackgroundFetches(q, sessionId, mcpServers);
263
+ this.deferBackgroundFetches(q, sessionId);
264
264
 
265
265
  const configOptions = await this.buildConfigOptions();
266
266
 
@@ -514,14 +514,10 @@ export class ClaudeAcpAgent extends BaseAcpAgent {
514
514
  * Fire-and-forget: fetch slash commands and MCP tool metadata in parallel.
515
515
  * Both populate caches used later — neither is needed to return configOptions.
516
516
  */
517
- private deferBackgroundFetches(
518
- q: Query,
519
- sessionId: string,
520
- mcpServers: ReturnType<typeof parseMcpServers>,
521
- ): void {
517
+ private deferBackgroundFetches(q: Query, sessionId: string): void {
522
518
  Promise.all([
523
519
  getAvailableSlashCommands(q),
524
- fetchMcpToolMetadata(mcpServers, this.logger),
520
+ fetchMcpToolMetadata(q, this.logger),
525
521
  ])
526
522
  .then(([slashCommands]) => {
527
523
  this.sendAvailableCommandsUpdate(sessionId, slashCommands);
@@ -1,10 +1,4 @@
1
- import type {
2
- McpHttpServerConfig,
3
- McpServerConfig,
4
- } from "@anthropic-ai/claude-agent-sdk";
5
- import { Client } from "@modelcontextprotocol/sdk/client/index.js";
6
- import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
7
- import type { Tool } from "@modelcontextprotocol/sdk/types.js";
1
+ import type { McpServerStatus, Query } from "@anthropic-ai/claude-agent-sdk";
8
2
  import { Logger } from "../../../utils/logger.js";
9
3
 
10
4
  export interface McpToolMetadata {
@@ -13,86 +7,74 @@ export interface McpToolMetadata {
13
7
 
14
8
  const mcpToolMetadataCache: Map<string, McpToolMetadata> = new Map();
15
9
 
10
+ const PENDING_RETRY_INTERVAL_MS = 1_000;
11
+ const PENDING_MAX_RETRIES = 10;
12
+
16
13
  function buildToolKey(serverName: string, toolName: string): string {
17
14
  return `mcp__${serverName}__${toolName}`;
18
15
  }
19
16
 
20
- function isHttpMcpServer(
21
- config: McpServerConfig,
22
- ): config is McpHttpServerConfig {
23
- return config.type === "http" && typeof config.url === "string";
24
- }
25
-
26
- async function fetchToolsFromHttpServer(
27
- _serverName: string,
28
- config: McpHttpServerConfig,
29
- ): Promise<Tool[]> {
30
- const transport = new StreamableHTTPClientTransport(new URL(config.url), {
31
- requestInit: {
32
- headers: config.headers ?? {},
33
- },
34
- });
35
-
36
- const client = new Client({
37
- name: "twig-metadata-fetcher",
38
- version: "1.0.0",
39
- });
40
-
41
- try {
42
- await client.connect(transport);
43
- const result = await client.listTools();
44
- return result.tools;
45
- } finally {
46
- await client.close().catch(() => {});
47
- }
48
- }
49
-
50
- function extractToolMetadata(tool: Tool): McpToolMetadata {
51
- return {
52
- readOnly: tool.annotations?.readOnlyHint === true,
53
- };
17
+ function delay(ms: number): Promise<void> {
18
+ return new Promise((resolve) => setTimeout(resolve, ms));
54
19
  }
55
20
 
56
21
  export async function fetchMcpToolMetadata(
57
- mcpServers: Record<string, McpServerConfig>,
22
+ q: Query,
58
23
  logger: Logger = new Logger({ debug: false, prefix: "[McpToolMetadata]" }),
59
24
  ): Promise<void> {
60
- const fetchPromises: Promise<void>[] = [];
25
+ let retries = 0;
26
+
27
+ while (retries <= PENDING_MAX_RETRIES) {
28
+ let statuses: McpServerStatus[];
29
+ try {
30
+ statuses = await q.mcpServerStatus();
31
+ } catch (error) {
32
+ logger.error("Failed to fetch MCP server status", {
33
+ error: error instanceof Error ? error.message : String(error),
34
+ });
35
+ return;
36
+ }
37
+
38
+ const pendingServers = statuses.filter((s) => s.status === "pending");
39
+
40
+ for (const server of statuses) {
41
+ if (server.status !== "connected" || !server.tools) {
42
+ continue;
43
+ }
44
+
45
+ let readOnlyCount = 0;
46
+ for (const tool of server.tools) {
47
+ const toolKey = buildToolKey(server.name, tool.name);
48
+ const readOnly = tool.annotations?.readOnly === true;
49
+ mcpToolMetadataCache.set(toolKey, { readOnly });
50
+ if (readOnly) readOnlyCount++;
51
+ }
52
+
53
+ logger.info("Fetched MCP tool metadata", {
54
+ serverName: server.name,
55
+ toolCount: server.tools.length,
56
+ readOnlyCount,
57
+ });
58
+ }
61
59
 
62
- for (const [serverName, config] of Object.entries(mcpServers)) {
63
- if (!isHttpMcpServer(config)) {
64
- continue;
60
+ if (pendingServers.length === 0) {
61
+ return;
65
62
  }
66
63
 
67
- const fetchPromise = fetchToolsFromHttpServer(serverName, config)
68
- .then((tools) => {
69
- const toolCount = tools.length;
70
- const readOnlyCount = tools.filter(
71
- (t) => t.annotations?.readOnlyHint === true,
72
- ).length;
73
-
74
- for (const tool of tools) {
75
- const toolKey = buildToolKey(serverName, tool.name);
76
- mcpToolMetadataCache.set(toolKey, extractToolMetadata(tool));
77
- }
78
-
79
- logger.info("Fetched MCP tool metadata", {
80
- serverName,
81
- toolCount,
82
- readOnlyCount,
83
- });
84
- })
85
- .catch((error) => {
86
- logger.error("Failed to fetch MCP tool metadata", {
87
- serverName,
88
- error: error instanceof Error ? error.message : String(error),
89
- });
64
+ retries++;
65
+ if (retries > PENDING_MAX_RETRIES) {
66
+ logger.warn("Gave up waiting for pending MCP servers", {
67
+ pendingServers: pendingServers.map((s) => s.name),
90
68
  });
69
+ return;
70
+ }
91
71
 
92
- fetchPromises.push(fetchPromise);
72
+ logger.info("Waiting for pending MCP servers", {
73
+ pendingServers: pendingServers.map((s) => s.name),
74
+ retry: retries,
75
+ });
76
+ await delay(PENDING_RETRY_INTERVAL_MS);
93
77
  }
94
-
95
- await Promise.all(fetchPromises);
96
78
  }
97
79
 
98
80
  export function isMcpToolReadOnly(toolName: string): boolean {