@posthog/agent 2.3.510 → 2.3.513

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.3.510",
3
+ "version": "2.3.513",
4
4
  "repository": "https://github.com/PostHog/code",
5
5
  "description": "TypeScript agent framework wrapping Claude Agent SDK with Git-based task execution for PostHog",
6
6
  "exports": {
@@ -102,8 +102,8 @@
102
102
  "tsx": "^4.20.6",
103
103
  "typescript": "^5.5.0",
104
104
  "vitest": "^2.1.8",
105
- "@posthog/shared": "1.0.0",
106
105
  "@posthog/git": "1.0.0",
106
+ "@posthog/shared": "1.0.0",
107
107
  "@posthog/enricher": "1.0.0"
108
108
  },
109
109
  "dependencies": {
@@ -0,0 +1,112 @@
1
+ import * as fs from "node:fs";
2
+ import * as os from "node:os";
3
+ import * as path from "node:path";
4
+ import { afterEach, beforeEach, describe, expect, it } from "vitest";
5
+ import { loadUserClaudeJsonMcpServers } from "./mcp-config";
6
+
7
+ describe("loadUserClaudeJsonMcpServers", () => {
8
+ let tmpHome: string;
9
+
10
+ beforeEach(() => {
11
+ tmpHome = fs.mkdtempSync(path.join(os.tmpdir(), "claude-json-test-"));
12
+ });
13
+
14
+ afterEach(() => {
15
+ fs.rmSync(tmpHome, { recursive: true, force: true });
16
+ });
17
+
18
+ it.each([
19
+ { name: "~/.claude.json is missing", setup: () => undefined },
20
+ {
21
+ name: "~/.claude.json contains invalid JSON",
22
+ setup: (home: string) =>
23
+ fs.writeFileSync(path.join(home, ".claude.json"), "not json"),
24
+ },
25
+ ])("returns empty when $name", ({ setup }) => {
26
+ setup(tmpHome);
27
+ expect(
28
+ loadUserClaudeJsonMcpServers("/some/cwd", undefined, tmpHome),
29
+ ).toEqual({});
30
+ });
31
+
32
+ it("returns top-level mcpServers", () => {
33
+ const cfg = {
34
+ mcpServers: {
35
+ top: { type: "stdio", command: "npx", args: ["pkg"] },
36
+ },
37
+ };
38
+ fs.writeFileSync(path.join(tmpHome, ".claude.json"), JSON.stringify(cfg));
39
+ const servers = loadUserClaudeJsonMcpServers(
40
+ "/some/cwd",
41
+ undefined,
42
+ tmpHome,
43
+ );
44
+ expect(servers.top).toBeDefined();
45
+ });
46
+
47
+ it("returns project-scoped mcpServers when cwd matches a project entry", () => {
48
+ const cwd = "/Users/jane/proj";
49
+ const cfg = {
50
+ projects: {
51
+ [cwd]: {
52
+ mcpServers: {
53
+ playwright: {
54
+ type: "stdio",
55
+ command: "npx",
56
+ args: ["@playwright/mcp@latest"],
57
+ },
58
+ },
59
+ },
60
+ },
61
+ };
62
+ fs.writeFileSync(path.join(tmpHome, ".claude.json"), JSON.stringify(cfg));
63
+ const servers = loadUserClaudeJsonMcpServers(cwd, undefined, tmpHome);
64
+ expect(servers.playwright).toBeDefined();
65
+ });
66
+
67
+ it("project-scoped servers override top-level on key collision", () => {
68
+ const cwd = "/Users/jane/proj";
69
+ const cfg = {
70
+ mcpServers: {
71
+ shared: { type: "stdio", command: "global", args: [] },
72
+ },
73
+ projects: {
74
+ [cwd]: {
75
+ mcpServers: {
76
+ shared: { type: "stdio", command: "scoped", args: [] },
77
+ },
78
+ },
79
+ },
80
+ };
81
+ fs.writeFileSync(path.join(tmpHome, ".claude.json"), JSON.stringify(cfg));
82
+ const servers = loadUserClaudeJsonMcpServers(cwd, undefined, tmpHome);
83
+ expect((servers.shared as { command: string }).command).toBe("scoped");
84
+ });
85
+
86
+ it("ignores CLAUDE_CONFIG_DIR redirect (reads real ~/.claude.json)", () => {
87
+ const altDir = fs.mkdtempSync(path.join(os.tmpdir(), "alt-claude-"));
88
+ fs.writeFileSync(
89
+ path.join(altDir, ".claude.json"),
90
+ JSON.stringify({
91
+ mcpServers: { wrong: { type: "stdio", command: "x" } },
92
+ }),
93
+ );
94
+ fs.writeFileSync(
95
+ path.join(tmpHome, ".claude.json"),
96
+ JSON.stringify({
97
+ mcpServers: { right: { type: "stdio", command: "y" } },
98
+ }),
99
+ );
100
+ const original = process.env.CLAUDE_CONFIG_DIR;
101
+ process.env.CLAUDE_CONFIG_DIR = altDir;
102
+ try {
103
+ const servers = loadUserClaudeJsonMcpServers("/cwd", undefined, tmpHome);
104
+ expect(servers.right).toBeDefined();
105
+ expect(servers.wrong).toBeUndefined();
106
+ } finally {
107
+ if (original === undefined) delete process.env.CLAUDE_CONFIG_DIR;
108
+ else process.env.CLAUDE_CONFIG_DIR = original;
109
+ fs.rmSync(altDir, { recursive: true, force: true });
110
+ }
111
+ });
112
+ });
@@ -1,5 +1,50 @@
1
+ import * as fs from "node:fs";
2
+ import * as os from "node:os";
3
+ import * as path from "node:path";
1
4
  import type { NewSessionRequest } from "@agentclientprotocol/sdk";
2
5
  import type { McpServerConfig } from "@anthropic-ai/claude-agent-sdk";
6
+ import type { Logger } from "../../../utils/logger";
7
+
8
+ export function loadUserClaudeJsonMcpServers(
9
+ cwd: string,
10
+ logger?: Logger,
11
+ homeDir: string = os.homedir(),
12
+ ): Record<string, McpServerConfig> {
13
+ const claudeJsonPath = path.join(homeDir, ".claude.json");
14
+
15
+ let raw: string;
16
+ try {
17
+ raw = fs.readFileSync(claudeJsonPath, "utf8");
18
+ } catch {
19
+ return {};
20
+ }
21
+
22
+ let cfg: {
23
+ mcpServers?: unknown;
24
+ projects?: Record<string, { mcpServers?: unknown }>;
25
+ };
26
+ try {
27
+ cfg = JSON.parse(raw);
28
+ } catch (err) {
29
+ logger?.warn("Failed to parse ~/.claude.json", {
30
+ error: err instanceof Error ? err.message : String(err),
31
+ });
32
+ return {};
33
+ }
34
+
35
+ const topLevel =
36
+ cfg.mcpServers && typeof cfg.mcpServers === "object"
37
+ ? (cfg.mcpServers as Record<string, McpServerConfig>)
38
+ : {};
39
+
40
+ const project = cfg.projects?.[cwd];
41
+ const projectScoped =
42
+ project?.mcpServers && typeof project.mcpServers === "object"
43
+ ? (project.mcpServers as Record<string, McpServerConfig>)
44
+ : {};
45
+
46
+ return { ...topLevel, ...projectScoped };
47
+ }
3
48
 
4
49
  export function parseMcpServers(
5
50
  params: Pick<NewSessionRequest, "mcpServers">,
@@ -24,6 +24,7 @@ import {
24
24
  import type { CodeExecutionMode } from "../tools";
25
25
  import type { EffortLevel } from "../types";
26
26
  import { APPENDED_INSTRUCTIONS } from "./instructions";
27
+ import { loadUserClaudeJsonMcpServers } from "./mcp-config";
27
28
  import { DEFAULT_MODEL } from "./models";
28
29
  import type { SettingsManager } from "./settings";
29
30
 
@@ -91,8 +92,10 @@ export function buildSystemPrompt(
91
92
  function buildMcpServers(
92
93
  userServers: Record<string, McpServerConfig> | undefined,
93
94
  acpServers: Record<string, McpServerConfig>,
95
+ projectScopedServers: Record<string, McpServerConfig>,
94
96
  ): Record<string, McpServerConfig> {
95
97
  return {
98
+ ...projectScopedServers,
96
99
  ...(userServers || {}),
97
100
  ...acpServers,
98
101
  };
@@ -330,6 +333,7 @@ export function buildSessionOptions(params: BuildOptionsParams): Options {
330
333
  mcpServers: buildMcpServers(
331
334
  params.userProvidedOptions?.mcpServers,
332
335
  params.mcpServers,
336
+ loadUserClaudeJsonMcpServers(params.cwd, params.logger),
333
337
  ),
334
338
  env: buildEnvironment(),
335
339
  hooks: buildHooks(