@salesforce/graphiti 10.10.2

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 (282) hide show
  1. package/AGENT_GUIDE.md +424 -0
  2. package/CHANGELOG.md +448 -0
  3. package/LICENSE.txt +82 -0
  4. package/README.md +204 -0
  5. package/TASK.md +249 -0
  6. package/dist/cli.d.ts +7 -0
  7. package/dist/cli.js +683 -0
  8. package/dist/cli.js.map +1 -0
  9. package/dist/commands/args.d.ts +13 -0
  10. package/dist/commands/args.js +207 -0
  11. package/dist/commands/args.js.map +1 -0
  12. package/dist/commands/build.d.ts +11 -0
  13. package/dist/commands/build.js +209 -0
  14. package/dist/commands/build.js.map +1 -0
  15. package/dist/commands/connect.d.ts +8 -0
  16. package/dist/commands/connect.js +55 -0
  17. package/dist/commands/connect.js.map +1 -0
  18. package/dist/commands/describe.d.ts +6 -0
  19. package/dist/commands/describe.js +229 -0
  20. package/dist/commands/describe.js.map +1 -0
  21. package/dist/commands/meta.d.ts +9 -0
  22. package/dist/commands/meta.js +140 -0
  23. package/dist/commands/meta.js.map +1 -0
  24. package/dist/commands/navigate.d.ts +14 -0
  25. package/dist/commands/navigate.js +105 -0
  26. package/dist/commands/navigate.js.map +1 -0
  27. package/dist/commands/query-helpers.d.ts +80 -0
  28. package/dist/commands/query-helpers.js +865 -0
  29. package/dist/commands/query-helpers.js.map +1 -0
  30. package/dist/commands/query.d.ts +26 -0
  31. package/dist/commands/query.js +901 -0
  32. package/dist/commands/query.js.map +1 -0
  33. package/dist/commands/review.d.ts +18 -0
  34. package/dist/commands/review.js +533 -0
  35. package/dist/commands/review.js.map +1 -0
  36. package/dist/commands/session-mgmt.d.ts +25 -0
  37. package/dist/commands/session-mgmt.js +206 -0
  38. package/dist/commands/session-mgmt.js.map +1 -0
  39. package/dist/commands/type.d.ts +6 -0
  40. package/dist/commands/type.js +342 -0
  41. package/dist/commands/type.js.map +1 -0
  42. package/dist/commands/validate-input.d.ts +6 -0
  43. package/dist/commands/validate-input.js +32 -0
  44. package/dist/commands/validate-input.js.map +1 -0
  45. package/dist/intent/build-aggregate.d.ts +33 -0
  46. package/dist/intent/build-aggregate.js +134 -0
  47. package/dist/intent/build-aggregate.js.map +1 -0
  48. package/dist/intent/build-create.d.ts +14 -0
  49. package/dist/intent/build-create.js +16 -0
  50. package/dist/intent/build-create.js.map +1 -0
  51. package/dist/intent/build-delete.d.ts +30 -0
  52. package/dist/intent/build-delete.js +53 -0
  53. package/dist/intent/build-delete.js.map +1 -0
  54. package/dist/intent/build-detail.d.ts +32 -0
  55. package/dist/intent/build-detail.js +80 -0
  56. package/dist/intent/build-detail.js.map +1 -0
  57. package/dist/intent/build-discover.d.ts +30 -0
  58. package/dist/intent/build-discover.js +149 -0
  59. package/dist/intent/build-discover.js.map +1 -0
  60. package/dist/intent/build-list.d.ts +28 -0
  61. package/dist/intent/build-list.js +75 -0
  62. package/dist/intent/build-list.js.map +1 -0
  63. package/dist/intent/build-mutation.d.ts +23 -0
  64. package/dist/intent/build-mutation.js +54 -0
  65. package/dist/intent/build-mutation.js.map +1 -0
  66. package/dist/intent/build-output.d.ts +27 -0
  67. package/dist/intent/build-output.js +60 -0
  68. package/dist/intent/build-output.js.map +1 -0
  69. package/dist/intent/build-raw.d.ts +23 -0
  70. package/dist/intent/build-raw.js +54 -0
  71. package/dist/intent/build-raw.js.map +1 -0
  72. package/dist/intent/build-update.d.ts +14 -0
  73. package/dist/intent/build-update.js +16 -0
  74. package/dist/intent/build-update.js.map +1 -0
  75. package/dist/intent/get-schema-with-priming.d.ts +26 -0
  76. package/dist/intent/get-schema-with-priming.js +32 -0
  77. package/dist/intent/get-schema-with-priming.js.map +1 -0
  78. package/dist/intent/select-child-relationship.d.ts +19 -0
  79. package/dist/intent/select-child-relationship.js +38 -0
  80. package/dist/intent/select-child-relationship.js.map +1 -0
  81. package/dist/intent/types.d.ts +159 -0
  82. package/dist/intent/types.js +21 -0
  83. package/dist/intent/types.js.map +1 -0
  84. package/dist/lib/apply-command.d.ts +15 -0
  85. package/dist/lib/apply-command.js +238 -0
  86. package/dist/lib/apply-command.js.map +1 -0
  87. package/dist/lib/auth.d.ts +38 -0
  88. package/dist/lib/auth.js +113 -0
  89. package/dist/lib/auth.js.map +1 -0
  90. package/dist/lib/codegen.d.ts +32 -0
  91. package/dist/lib/codegen.js +700 -0
  92. package/dist/lib/codegen.js.map +1 -0
  93. package/dist/lib/command-registry.d.ts +59 -0
  94. package/dist/lib/command-registry.js +366 -0
  95. package/dist/lib/command-registry.js.map +1 -0
  96. package/dist/lib/formatter.d.ts +76 -0
  97. package/dist/lib/formatter.js +419 -0
  98. package/dist/lib/formatter.js.map +1 -0
  99. package/dist/lib/fs-utils.d.ts +23 -0
  100. package/dist/lib/fs-utils.js +46 -0
  101. package/dist/lib/fs-utils.js.map +1 -0
  102. package/dist/lib/graphql-name.d.ts +27 -0
  103. package/dist/lib/graphql-name.js +32 -0
  104. package/dist/lib/graphql-name.js.map +1 -0
  105. package/dist/lib/interactive.d.ts +6 -0
  106. package/dist/lib/interactive.js +562 -0
  107. package/dist/lib/interactive.js.map +1 -0
  108. package/dist/lib/introspect.d.ts +40 -0
  109. package/dist/lib/introspect.js +280 -0
  110. package/dist/lib/introspect.js.map +1 -0
  111. package/dist/lib/object-info.d.ts +79 -0
  112. package/dist/lib/object-info.js +313 -0
  113. package/dist/lib/object-info.js.map +1 -0
  114. package/dist/lib/path-selection.d.ts +50 -0
  115. package/dist/lib/path-selection.js +146 -0
  116. package/dist/lib/path-selection.js.map +1 -0
  117. package/dist/lib/prime-schema.d.ts +59 -0
  118. package/dist/lib/prime-schema.js +158 -0
  119. package/dist/lib/prime-schema.js.map +1 -0
  120. package/dist/lib/query-builder.d.ts +10 -0
  121. package/dist/lib/query-builder.js +168 -0
  122. package/dist/lib/query-builder.js.map +1 -0
  123. package/dist/lib/query-commands.d.ts +19 -0
  124. package/dist/lib/query-commands.js +262 -0
  125. package/dist/lib/query-commands.js.map +1 -0
  126. package/dist/lib/session.d.ts +205 -0
  127. package/dist/lib/session.js +1075 -0
  128. package/dist/lib/session.js.map +1 -0
  129. package/dist/lib/tokenize.d.ts +12 -0
  130. package/dist/lib/tokenize.js +48 -0
  131. package/dist/lib/tokenize.js.map +1 -0
  132. package/dist/lib/uiapi.d.ts +109 -0
  133. package/dist/lib/uiapi.js +159 -0
  134. package/dist/lib/uiapi.js.map +1 -0
  135. package/dist/lib/validator.d.ts +27 -0
  136. package/dist/lib/validator.js +100 -0
  137. package/dist/lib/validator.js.map +1 -0
  138. package/dist/lib/variable-promotion.d.ts +69 -0
  139. package/dist/lib/variable-promotion.js +95 -0
  140. package/dist/lib/variable-promotion.js.map +1 -0
  141. package/dist/lib/walker.d.ts +147 -0
  142. package/dist/lib/walker.js +723 -0
  143. package/dist/lib/walker.js.map +1 -0
  144. package/dist/mcp/server.d.ts +7 -0
  145. package/dist/mcp/server.js +34 -0
  146. package/dist/mcp/server.js.map +1 -0
  147. package/dist/mcp/stdio.d.ts +7 -0
  148. package/dist/mcp/stdio.js +25 -0
  149. package/dist/mcp/stdio.js.map +1 -0
  150. package/dist/mcp/tools/echo.d.ts +7 -0
  151. package/dist/mcp/tools/echo.js +17 -0
  152. package/dist/mcp/tools/echo.js.map +1 -0
  153. package/dist/mcp/tools/sf-gql-aggregate.d.ts +11 -0
  154. package/dist/mcp/tools/sf-gql-aggregate.js +75 -0
  155. package/dist/mcp/tools/sf-gql-aggregate.js.map +1 -0
  156. package/dist/mcp/tools/sf-gql-create.d.ts +11 -0
  157. package/dist/mcp/tools/sf-gql-create.js +35 -0
  158. package/dist/mcp/tools/sf-gql-create.js.map +1 -0
  159. package/dist/mcp/tools/sf-gql-delete.d.ts +11 -0
  160. package/dist/mcp/tools/sf-gql-delete.js +31 -0
  161. package/dist/mcp/tools/sf-gql-delete.js.map +1 -0
  162. package/dist/mcp/tools/sf-gql-detail.d.ts +11 -0
  163. package/dist/mcp/tools/sf-gql-detail.js +58 -0
  164. package/dist/mcp/tools/sf-gql-detail.js.map +1 -0
  165. package/dist/mcp/tools/sf-gql-discover.d.ts +9 -0
  166. package/dist/mcp/tools/sf-gql-discover.js +51 -0
  167. package/dist/mcp/tools/sf-gql-discover.js.map +1 -0
  168. package/dist/mcp/tools/sf-gql-list.d.ts +11 -0
  169. package/dist/mcp/tools/sf-gql-list.js +53 -0
  170. package/dist/mcp/tools/sf-gql-list.js.map +1 -0
  171. package/dist/mcp/tools/sf-gql-raw.d.ts +11 -0
  172. package/dist/mcp/tools/sf-gql-raw.js +39 -0
  173. package/dist/mcp/tools/sf-gql-raw.js.map +1 -0
  174. package/dist/mcp/tools/sf-gql-update.d.ts +11 -0
  175. package/dist/mcp/tools/sf-gql-update.js +35 -0
  176. package/dist/mcp/tools/sf-gql-update.js.map +1 -0
  177. package/dist/mcp/tools/shared/zod-schemas.d.ts +49 -0
  178. package/dist/mcp/tools/shared/zod-schemas.js +46 -0
  179. package/dist/mcp/tools/shared/zod-schemas.js.map +1 -0
  180. package/package.json +36 -0
  181. package/ralph-loop.sh +120 -0
  182. package/scripts/smoke-orderby.sh +190 -0
  183. package/src/__tests__/helpers/prime-deps.ts +46 -0
  184. package/src/__tests__/helpers/schema.ts +73 -0
  185. package/src/__tests__/helpers/stdout.ts +33 -0
  186. package/src/__tests__/setup.ts +19 -0
  187. package/src/cli.ts +764 -0
  188. package/src/commands/__tests__/query.spec.ts +137 -0
  189. package/src/commands/args.ts +306 -0
  190. package/src/commands/build.ts +288 -0
  191. package/src/commands/connect.ts +60 -0
  192. package/src/commands/describe.ts +246 -0
  193. package/src/commands/meta.ts +171 -0
  194. package/src/commands/navigate.ts +134 -0
  195. package/src/commands/query-helpers.ts +1202 -0
  196. package/src/commands/query.ts +1085 -0
  197. package/src/commands/review.ts +670 -0
  198. package/src/commands/session-mgmt.ts +256 -0
  199. package/src/commands/type.ts +437 -0
  200. package/src/commands/validate-input.ts +38 -0
  201. package/src/intent/__tests__/build-aggregate.spec.ts +931 -0
  202. package/src/intent/__tests__/build-create-validation.spec.ts +135 -0
  203. package/src/intent/__tests__/build-delete.spec.ts +121 -0
  204. package/src/intent/__tests__/build-detail.spec.ts +333 -0
  205. package/src/intent/__tests__/build-discover.spec.ts +432 -0
  206. package/src/intent/__tests__/build-list.spec.ts +284 -0
  207. package/src/intent/__tests__/build-mutation.spec.ts +108 -0
  208. package/src/intent/__tests__/build-output.spec.ts +55 -0
  209. package/src/intent/__tests__/build-raw.spec.ts +153 -0
  210. package/src/intent/__tests__/build-update-validation.spec.ts +134 -0
  211. package/src/intent/build-aggregate.ts +182 -0
  212. package/src/intent/build-create.ts +19 -0
  213. package/src/intent/build-delete.ts +62 -0
  214. package/src/intent/build-detail.ts +95 -0
  215. package/src/intent/build-discover.ts +199 -0
  216. package/src/intent/build-list.ts +91 -0
  217. package/src/intent/build-mutation.ts +75 -0
  218. package/src/intent/build-output.ts +72 -0
  219. package/src/intent/build-raw.ts +61 -0
  220. package/src/intent/build-update.ts +19 -0
  221. package/src/intent/get-schema-with-priming.ts +43 -0
  222. package/src/intent/select-child-relationship.ts +48 -0
  223. package/src/intent/types.ts +181 -0
  224. package/src/lib/__tests__/apply-command.parity.spec.ts +97 -0
  225. package/src/lib/__tests__/apply-command.spec.ts +171 -0
  226. package/src/lib/__tests__/auth.spec.ts +292 -0
  227. package/src/lib/__tests__/formatter.spec.ts +86 -0
  228. package/src/lib/__tests__/graphql-name.spec.ts +64 -0
  229. package/src/lib/__tests__/introspect.spec.ts +399 -0
  230. package/src/lib/__tests__/object-info.spec.ts +124 -0
  231. package/src/lib/__tests__/path-selection.spec.ts +219 -0
  232. package/src/lib/__tests__/prime-schema.spec.ts +269 -0
  233. package/src/lib/__tests__/query-builder.spec.ts +95 -0
  234. package/src/lib/__tests__/query-commands.spec.ts +74 -0
  235. package/src/lib/__tests__/session.spec.ts +292 -0
  236. package/src/lib/__tests__/tokenize.spec.ts +33 -0
  237. package/src/lib/__tests__/uiapi.spec.ts +192 -0
  238. package/src/lib/__tests__/variable-promotion.spec.ts +211 -0
  239. package/src/lib/__tests__/walker.spec.ts +250 -0
  240. package/src/lib/apply-command.ts +286 -0
  241. package/src/lib/auth.ts +157 -0
  242. package/src/lib/codegen.ts +899 -0
  243. package/src/lib/command-registry.ts +434 -0
  244. package/src/lib/formatter.ts +587 -0
  245. package/src/lib/fs-utils.ts +46 -0
  246. package/src/lib/graphql-name.ts +35 -0
  247. package/src/lib/interactive.ts +634 -0
  248. package/src/lib/introspect.ts +320 -0
  249. package/src/lib/object-info.ts +409 -0
  250. package/src/lib/path-selection.ts +162 -0
  251. package/src/lib/prime-schema.ts +195 -0
  252. package/src/lib/query-builder.ts +193 -0
  253. package/src/lib/query-commands.ts +290 -0
  254. package/src/lib/session.ts +1304 -0
  255. package/src/lib/tokenize.ts +43 -0
  256. package/src/lib/uiapi.ts +176 -0
  257. package/src/lib/validator.ts +143 -0
  258. package/src/lib/variable-promotion.ts +145 -0
  259. package/src/lib/walker.ts +975 -0
  260. package/src/mcp/__tests__/server.spec.ts +155 -0
  261. package/src/mcp/server.ts +38 -0
  262. package/src/mcp/stdio.ts +29 -0
  263. package/src/mcp/tools/__tests__/sf-gql-aggregate.spec.ts +173 -0
  264. package/src/mcp/tools/__tests__/sf-gql-create.spec.ts +235 -0
  265. package/src/mcp/tools/__tests__/sf-gql-delete.spec.ts +194 -0
  266. package/src/mcp/tools/__tests__/sf-gql-detail.spec.ts +246 -0
  267. package/src/mcp/tools/__tests__/sf-gql-discover.spec.ts +320 -0
  268. package/src/mcp/tools/__tests__/sf-gql-list.spec.ts +128 -0
  269. package/src/mcp/tools/__tests__/sf-gql-raw.spec.ts +141 -0
  270. package/src/mcp/tools/__tests__/sf-gql-update.spec.ts +207 -0
  271. package/src/mcp/tools/echo.ts +24 -0
  272. package/src/mcp/tools/sf-gql-aggregate.ts +102 -0
  273. package/src/mcp/tools/sf-gql-create.ts +55 -0
  274. package/src/mcp/tools/sf-gql-delete.ts +49 -0
  275. package/src/mcp/tools/sf-gql-detail.ts +85 -0
  276. package/src/mcp/tools/sf-gql-discover.ts +67 -0
  277. package/src/mcp/tools/sf-gql-list.ts +73 -0
  278. package/src/mcp/tools/sf-gql-raw.ts +56 -0
  279. package/src/mcp/tools/sf-gql-update.ts +55 -0
  280. package/src/mcp/tools/shared/zod-schemas.ts +55 -0
  281. package/tsconfig.json +18 -0
  282. package/vitest.config.ts +14 -0
@@ -0,0 +1,155 @@
1
+ /**
2
+ * Copyright (c) 2026, Salesforce, Inc.,
3
+ * All rights reserved.
4
+ * For full license text, see the LICENSE.txt file
5
+ */
6
+
7
+ import { dirname, resolve } from "node:path";
8
+ import { fileURLToPath } from "node:url";
9
+ import { Client } from "@modelcontextprotocol/sdk/client/index.js";
10
+ import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
11
+ import { InMemoryTransport } from "@modelcontextprotocol/sdk/inMemory.js";
12
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
13
+ import { describe, expect, it } from "vitest";
14
+ import { createServer } from "../server.js";
15
+
16
+ const __dirname = dirname(fileURLToPath(import.meta.url));
17
+ // Resolves to packages/graphiti/src/mcp/stdio.ts — spawned via tsx so the
18
+ // FR-1.7 test exercises the live source with no build step or dist staleness.
19
+ const STDIO_SRC = resolve(__dirname, "..", "stdio.ts");
20
+
21
+ async function connect(): Promise<{ client: Client; server: McpServer }> {
22
+ const server = createServer();
23
+ const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
24
+ const client = new Client({ name: "graphiti-mcp-test-client", version: "0.0.0" });
25
+ await Promise.all([server.connect(serverTransport), client.connect(clientTransport)]);
26
+ return { client, server };
27
+ }
28
+
29
+ describe("mcp/server", () => {
30
+ it("initialize round-trips with the expected server identity", async () => {
31
+ const { client, server } = await connect();
32
+ try {
33
+ const info = client.getServerVersion();
34
+ expect(info?.name).toBe("graphiti-mcp");
35
+ expect(info?.version).toBeTruthy();
36
+
37
+ const capabilities = client.getServerCapabilities();
38
+ expect(capabilities?.tools).toBeDefined();
39
+ } finally {
40
+ await client.close();
41
+ await server.close();
42
+ }
43
+ });
44
+
45
+ it("tools/list returns the registered tools", async () => {
46
+ const { client, server } = await connect();
47
+ try {
48
+ const result = await client.listTools();
49
+ const names = result.tools.map((t) => t.name).sort();
50
+ expect(names).toEqual([
51
+ "echo",
52
+ "sf_gql_aggregate",
53
+ "sf_gql_create",
54
+ "sf_gql_delete",
55
+ "sf_gql_detail",
56
+ "sf_gql_discover",
57
+ "sf_gql_list",
58
+ "sf_gql_raw",
59
+ "sf_gql_update",
60
+ ]);
61
+ const echo = result.tools.find((t) => t.name === "echo");
62
+ const inputSchema = echo?.inputSchema as
63
+ | { type?: string; properties?: { message?: unknown } }
64
+ | undefined;
65
+ expect(inputSchema?.type).toBe("object");
66
+ expect(inputSchema?.properties?.message).toBeDefined();
67
+ } finally {
68
+ await client.close();
69
+ await server.close();
70
+ }
71
+ });
72
+
73
+ it("tools/call echo returns the message verbatim", async () => {
74
+ const { client, server } = await connect();
75
+ try {
76
+ const result = await client.callTool({
77
+ name: "echo",
78
+ arguments: { message: "hello graphiti-mcp" },
79
+ });
80
+ const content = result.content as { type: string; text?: string }[];
81
+ expect(content[0]?.type).toBe("text");
82
+ expect(content[0]?.text).toBe("hello graphiti-mcp");
83
+ } finally {
84
+ await client.close();
85
+ await server.close();
86
+ }
87
+ });
88
+
89
+ it("tools/call unknown tool returns FR-1.6 error envelope", async () => {
90
+ const { client, server } = await connect();
91
+ try {
92
+ const result = await client.callTool({ name: "does-not-exist" });
93
+ expect(result.isError).toBe(true);
94
+ const content = result.content as { type: string; text?: string }[];
95
+ expect(content[0]?.type).toBe("text");
96
+ expect(content[0]?.text ?? "").toMatch(/does-not-exist/);
97
+ } finally {
98
+ await client.close();
99
+ await server.close();
100
+ }
101
+ });
102
+
103
+ it("tools/call echo with missing required arg returns zod validation error", async () => {
104
+ const { client, server } = await connect();
105
+ try {
106
+ const result = await client.callTool({ name: "echo", arguments: {} });
107
+ expect(result.isError).toBe(true);
108
+ const content = result.content as { type: string; text?: string }[];
109
+ expect(content[0]?.type).toBe("text");
110
+ const text = content[0]?.text ?? "";
111
+ expect(text).toMatch(/Input validation error/);
112
+ expect(text).toMatch(/message/);
113
+ } finally {
114
+ await client.close();
115
+ await server.close();
116
+ }
117
+ });
118
+
119
+ it("FR-1.7: stdio bin exits cleanly on stdin close after handshake", async () => {
120
+ // End-to-end coverage for FR-1.7: spawn the real bin, complete a
121
+ // handshake, close stdin, and assert the child exits. This pins that
122
+ // `main()` actually wires `transport.onclose` to a process-exit path —
123
+ // regressions like dropping the `await closed` or returning early from
124
+ // main fail this test. (It does *not* prove the synchronous-close race
125
+ // fix in isolation; over a real subprocess stdin doesn't close fast
126
+ // enough to fire onclose inside connect(). That race is guarded by
127
+ // code review of stdio.ts ordering.)
128
+ const transport = new StdioClientTransport({
129
+ command: "npx",
130
+ args: ["tsx", STDIO_SRC],
131
+ stderr: "pipe",
132
+ });
133
+ const client = new Client({ name: "stdio-bin-test", version: "0.0.0" }, { capabilities: {} });
134
+
135
+ // StdioClientTransport fires onclose when the child process exits.
136
+ const exitPromise = new Promise<void>((resolve) => {
137
+ transport.onclose = () => resolve();
138
+ });
139
+
140
+ await client.connect(transport);
141
+ // Confirm the handshake actually completed before we try to drain.
142
+ expect(client.getServerVersion()?.name).toBe("graphiti-mcp");
143
+
144
+ // Closing the client closes its writer, which closes the child's stdin,
145
+ // which fires StdioServerTransport.onclose on the server side and
146
+ // resolves runStdio's `closed` promise — so the child exits.
147
+ await client.close();
148
+ const result = await Promise.race([
149
+ exitPromise.then(() => "exited" as const),
150
+ new Promise<"timeout">((resolve) => setTimeout(() => resolve("timeout"), 5000)),
151
+ ]);
152
+
153
+ expect(result).toBe("exited");
154
+ }, 60_000);
155
+ });
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Copyright (c) 2026, Salesforce, Inc.,
3
+ * All rights reserved.
4
+ * For full license text, see the LICENSE.txt file
5
+ */
6
+
7
+ import { readFileSync } from "node:fs";
8
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
9
+ import { registerEchoTool } from "./tools/echo.js";
10
+ import { registerSfGqlAggregateTool } from "./tools/sf-gql-aggregate.js";
11
+ import { registerSfGqlCreateTool } from "./tools/sf-gql-create.js";
12
+ import { registerSfGqlDeleteTool } from "./tools/sf-gql-delete.js";
13
+ import { registerSfGqlDetailTool } from "./tools/sf-gql-detail.js";
14
+ import { registerSfGqlDiscoverTool } from "./tools/sf-gql-discover.js";
15
+ import { registerSfGqlListTool } from "./tools/sf-gql-list.js";
16
+ import { registerSfGqlRawTool } from "./tools/sf-gql-raw.js";
17
+ import { registerSfGqlUpdateTool } from "./tools/sf-gql-update.js";
18
+
19
+ const packageJson = JSON.parse(
20
+ readFileSync(new URL("../../package.json", import.meta.url), "utf8"),
21
+ ) as { version: string };
22
+
23
+ export function createServer(): McpServer {
24
+ const server = new McpServer({
25
+ name: "graphiti-mcp",
26
+ version: packageJson.version,
27
+ });
28
+ registerEchoTool(server);
29
+ registerSfGqlAggregateTool(server);
30
+ registerSfGqlCreateTool(server);
31
+ registerSfGqlDeleteTool(server);
32
+ registerSfGqlDetailTool(server);
33
+ registerSfGqlDiscoverTool(server);
34
+ registerSfGqlListTool(server);
35
+ registerSfGqlRawTool(server);
36
+ registerSfGqlUpdateTool(server);
37
+ return server;
38
+ }
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Copyright (c) 2026, Salesforce, Inc.,
4
+ * All rights reserved.
5
+ * For full license text, see the LICENSE.txt file
6
+ */
7
+
8
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
9
+ import { createServer } from "./server.js";
10
+
11
+ async function main(): Promise<void> {
12
+ const server = createServer();
13
+ const transport = new StdioServerTransport();
14
+ // FR-1.7: register onclose before connect — a synchronous close during
15
+ // connect() would otherwise be missed and main would hang indefinitely.
16
+ const closed = new Promise<void>((resolve) => {
17
+ transport.onclose = () => resolve();
18
+ });
19
+ await server.connect(transport);
20
+ await closed;
21
+ }
22
+
23
+ main().catch((err) => {
24
+ // CRITICAL: never write to stdout — that channel is the JSON-RPC frame stream.
25
+ process.stderr.write(
26
+ `graphiti-mcp fatal: ${err instanceof Error ? (err.stack ?? err.message) : String(err)}\n`,
27
+ );
28
+ process.exit(1);
29
+ });
@@ -0,0 +1,173 @@
1
+ /**
2
+ * Copyright (c) 2026, Salesforce, Inc.,
3
+ * All rights reserved.
4
+ * For full license text, see the LICENSE.txt file
5
+ */
6
+
7
+ import path from "node:path";
8
+ import { Client } from "@modelcontextprotocol/sdk/client/index.js";
9
+ import { InMemoryTransport } from "@modelcontextprotocol/sdk/inMemory.js";
10
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
11
+ import { buildSchema, introspectionFromSchema } from "graphql";
12
+ import { describe, expect, it } from "vitest";
13
+ import { atomicWriteJson } from "../../../lib/fs-utils.js";
14
+ import {
15
+ schemaCacheKeyForInstanceUrl,
16
+ schemaDir,
17
+ type SchemaMetadata,
18
+ } from "../../../lib/introspect.js";
19
+ import { type PrimeDeps } from "../../../lib/prime-schema.js";
20
+ import { primeSchemaCache } from "../../../lib/walker.js";
21
+ import { registerSfGqlAggregateTool } from "../sf-gql-aggregate.js";
22
+
23
+ const ORG = "test-tool-aggregate";
24
+ const ORG_URL = "https://test-tool-aggregate.my.salesforce.com";
25
+ const SCHEMA = buildSchema(`
26
+ type Query { uiapi: UIAPI! }
27
+ type UIAPI { aggregate: RecordQueryAggregate! }
28
+ type RecordQueryAggregate {
29
+ Account(first: Int, after: String, where: Account_Filter, groupBy: Account_GroupBy): AccountAggregateConnection
30
+ }
31
+ input Account_Filter { Industry: PicklistOperators }
32
+ input Account_GroupBy { Industry: GroupByClause }
33
+ input PicklistOperators { eq: String }
34
+ input GroupByClause { group: Boolean }
35
+
36
+ type AccountAggregateConnection {
37
+ edges: [AccountAggregateEdge!]!
38
+ pageInfo: PageInfo!
39
+ }
40
+ type AccountAggregateEdge { node: AccountResult!, cursor: String! }
41
+ type AccountResult { aggregate: AccountAggregate }
42
+ type AccountAggregate {
43
+ Id: IDAggregate
44
+ Industry: PicklistAggregate
45
+ }
46
+ type IDAggregate { value: ID, count: LongValue, countDistinct: LongValue }
47
+ type PicklistAggregate { value: String, count: LongValue, countDistinct: LongValue }
48
+ type LongValue { value: Float }
49
+ type PageInfo { hasNextPage: Boolean!, endCursor: String }
50
+ `);
51
+ primeSchemaCache(ORG, SCHEMA);
52
+ primeSchemaCache(ORG_URL, SCHEMA);
53
+
54
+ const primeDeps: PrimeDeps = {
55
+ getOrgAuth: async () => ({
56
+ alias: ORG,
57
+ username: "u",
58
+ instanceUrl: ORG_URL,
59
+ accessToken: "t",
60
+ orgId: "00D",
61
+ }),
62
+ downloadSchema: async (auth) => {
63
+ const cacheKey = schemaCacheKeyForInstanceUrl(auth.instanceUrl);
64
+ const filePath = path.join(schemaDir(), `${cacheKey}.json`);
65
+ atomicWriteJson(filePath, { data: introspectionFromSchema(SCHEMA) });
66
+ const meta: SchemaMetadata = {
67
+ cacheKey,
68
+ instanceUrl: auth.instanceUrl,
69
+ typeCount: 0,
70
+ downloadedAt: new Date().toISOString(),
71
+ filePath,
72
+ };
73
+ return meta;
74
+ },
75
+ };
76
+
77
+ async function connect(): Promise<{ client: Client; server: McpServer }> {
78
+ const server = new McpServer({ name: "graphiti-mcp", version: "test" });
79
+ registerSfGqlAggregateTool(server, { primeDeps });
80
+ const [c, s] = InMemoryTransport.createLinkedPair();
81
+ const client = new Client({ name: "test", version: "0.0.0" });
82
+ await Promise.all([server.connect(s), client.connect(c)]);
83
+ return { client, server };
84
+ }
85
+
86
+ describe("mcp/tools/sf-gql-aggregate", () => {
87
+ it("tools/list advertises sf_gql_aggregate with org/object/groupBy/aggregations properties", async () => {
88
+ const { client, server } = await connect();
89
+ try {
90
+ const list = await client.listTools();
91
+ const tool = list.tools.find((t) => t.name === "sf_gql_aggregate");
92
+ expect(tool).toBeDefined();
93
+ const props =
94
+ (tool!.inputSchema as { properties?: Record<string, unknown> }).properties ?? {};
95
+ expect(props.org).toBeDefined();
96
+ expect(props.object).toBeDefined();
97
+ expect(props.groupBy).toBeDefined();
98
+ expect(props.aggregations).toBeDefined();
99
+ } finally {
100
+ await client.close();
101
+ await server.close();
102
+ }
103
+ });
104
+
105
+ it("tools/call sf_gql_aggregate returns ToolOutput JSON envelope", async () => {
106
+ const { client, server } = await connect();
107
+ try {
108
+ const result = await client.callTool({
109
+ name: "sf_gql_aggregate",
110
+ arguments: {
111
+ org: ORG,
112
+ object: "Account",
113
+ groupBy: ["Industry"],
114
+ aggregations: [{ function: "count" }],
115
+ },
116
+ });
117
+ const content = result.content as { type: string; text?: string }[];
118
+ expect(content[0]?.type).toBe("text");
119
+ const parsed = JSON.parse(content[0]?.text ?? "{}") as {
120
+ query: string;
121
+ variables: unknown[];
122
+ types: string;
123
+ warnings: string[];
124
+ };
125
+ expect(parsed.query).toMatch(/\bAccountAggregate\b/);
126
+ expect(parsed.query).toMatch(/countId\s*:\s*Id\s*\{\s*count\s*\{\s*value/s);
127
+ expect(parsed.query).toMatch(/Industry\s*:\s*\{\s*group\s*:\s*true\s*\}/s);
128
+ expect(Array.isArray(parsed.variables)).toBe(true);
129
+ expect(typeof parsed.types).toBe("string");
130
+ } finally {
131
+ await client.close();
132
+ await server.close();
133
+ }
134
+ });
135
+
136
+ it("tools/call sf_gql_aggregate with no groupBy and no aggregations defaults to count(Id)", async () => {
137
+ const { client, server } = await connect();
138
+ try {
139
+ const result = await client.callTool({
140
+ name: "sf_gql_aggregate",
141
+ arguments: { org: ORG, object: "Account" },
142
+ });
143
+ expect(result.isError).toBeFalsy();
144
+ const content = result.content as { type: string; text?: string }[];
145
+ const parsed = JSON.parse(content[0]?.text ?? "{}");
146
+ expect(parsed.query).toMatch(/countId\s*:\s*Id\s*\{\s*count\s*\{\s*value/s);
147
+ } finally {
148
+ await client.close();
149
+ await server.close();
150
+ }
151
+ });
152
+
153
+ it("tools/call sf_gql_aggregate with sum aggregation missing field returns validation error", async () => {
154
+ const { client, server } = await connect();
155
+ try {
156
+ const result = await client.callTool({
157
+ name: "sf_gql_aggregate",
158
+ arguments: {
159
+ org: ORG,
160
+ object: "Account",
161
+ groupBy: [],
162
+ aggregations: [{ function: "sum" }],
163
+ },
164
+ });
165
+ expect(result.isError).toBe(true);
166
+ const content = result.content as { type: string; text?: string }[];
167
+ expect(content[0]?.text ?? "").toMatch(/field/i);
168
+ } finally {
169
+ await client.close();
170
+ await server.close();
171
+ }
172
+ });
173
+ });
@@ -0,0 +1,235 @@
1
+ /**
2
+ * Copyright (c) 2026, Salesforce, Inc.,
3
+ * All rights reserved.
4
+ * For full license text, see the LICENSE.txt file
5
+ */
6
+
7
+ import path from "node:path";
8
+ import { Client } from "@modelcontextprotocol/sdk/client/index.js";
9
+ import { InMemoryTransport } from "@modelcontextprotocol/sdk/inMemory.js";
10
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
11
+ import { buildSchema, introspectionFromSchema } from "graphql";
12
+ import { describe, expect, it } from "vitest";
13
+ import { atomicWriteJson } from "../../../lib/fs-utils.js";
14
+ import {
15
+ schemaCacheKeyForInstanceUrl,
16
+ schemaDir,
17
+ type SchemaMetadata,
18
+ } from "../../../lib/introspect.js";
19
+ import { type PrimeDeps } from "../../../lib/prime-schema.js";
20
+ import { primeSchemaCache } from "../../../lib/walker.js";
21
+ import { registerSfGqlCreateTool } from "../sf-gql-create.js";
22
+
23
+ const ORG = "test-tool-create";
24
+ const ORG_URL = "https://test-tool-create.my.salesforce.com";
25
+ const SCHEMA = buildSchema(`
26
+ type Query { _placeholder: Boolean }
27
+ type Mutation { uiapi(input: UIAPIMutationsInput): UIAPIMutations! }
28
+ input UIAPIMutationsInput { allOrNone: Boolean }
29
+ type UIAPIMutations {
30
+ AccountCreate(input: AccountCreateInput!): AccountCreatePayload
31
+ }
32
+ input AccountCreateInput { Account: AccountCreateRepresentation! }
33
+ input AccountCreateRepresentation { Name: String, Industry: String }
34
+ type AccountCreatePayload { Record: Account }
35
+ type Account { Id: ID!, Name: StringValue, Industry: StringValue, Owner: User }
36
+ type StringValue { value: String }
37
+ type User { Id: ID!, Name: StringValue }
38
+ `);
39
+ primeSchemaCache(ORG, SCHEMA);
40
+ primeSchemaCache(ORG_URL, SCHEMA);
41
+
42
+ const primeDeps: PrimeDeps = {
43
+ getOrgAuth: async () => ({
44
+ alias: ORG,
45
+ username: "u",
46
+ instanceUrl: ORG_URL,
47
+ accessToken: "t",
48
+ orgId: "00D",
49
+ }),
50
+ downloadSchema: async (auth) => {
51
+ const cacheKey = schemaCacheKeyForInstanceUrl(auth.instanceUrl);
52
+ const filePath = path.join(schemaDir(), `${cacheKey}.json`);
53
+ atomicWriteJson(filePath, { data: introspectionFromSchema(SCHEMA) });
54
+ const meta: SchemaMetadata = {
55
+ cacheKey,
56
+ instanceUrl: auth.instanceUrl,
57
+ typeCount: 0,
58
+ downloadedAt: new Date().toISOString(),
59
+ filePath,
60
+ };
61
+ return meta;
62
+ },
63
+ };
64
+
65
+ async function connect(): Promise<{ client: Client; server: McpServer }> {
66
+ const server = new McpServer({ name: "graphiti-mcp", version: "test" });
67
+ registerSfGqlCreateTool(server, { primeDeps });
68
+ const [c, s] = InMemoryTransport.createLinkedPair();
69
+ const client = new Client({ name: "test", version: "0.0.0" });
70
+ await Promise.all([server.connect(s), client.connect(c)]);
71
+ return { client, server };
72
+ }
73
+
74
+ describe("mcp/tools/sf-gql-create", () => {
75
+ it("tools/list advertises sf_gql_create with org/object/returnFields properties", async () => {
76
+ const { client, server } = await connect();
77
+ try {
78
+ const list = await client.listTools();
79
+ const tool = list.tools.find((t) => t.name === "sf_gql_create");
80
+ expect(tool).toBeDefined();
81
+ const props =
82
+ (tool!.inputSchema as { properties?: Record<string, unknown> }).properties ?? {};
83
+ expect(props.org).toBeDefined();
84
+ expect(props.object).toBeDefined();
85
+ expect(props.returnFields).toBeDefined();
86
+ } finally {
87
+ await client.close();
88
+ await server.close();
89
+ }
90
+ });
91
+
92
+ it("tools/call sf_gql_create returns ToolOutput with mutation keyword and value wrappers", async () => {
93
+ const { client, server } = await connect();
94
+ try {
95
+ const result = await client.callTool({
96
+ name: "sf_gql_create",
97
+ arguments: {
98
+ org: ORG,
99
+ object: "Account",
100
+ returnFields: ["Id", "Name", "Industry"],
101
+ },
102
+ });
103
+ const content = result.content as { type: string; text?: string }[];
104
+ expect(content[0]?.type).toBe("text");
105
+ const parsed = JSON.parse(content[0]?.text ?? "{}") as {
106
+ query: string;
107
+ variables: unknown[];
108
+ types: string;
109
+ };
110
+ expect(parsed.query).toMatch(/mutation CreateAccount/);
111
+ expect(parsed.query).toMatch(/\$input:\s*AccountCreateInput!/);
112
+ expect(parsed.query).toMatch(/AccountCreate\(input:\s*\$input\)/);
113
+ expect(parsed.query).toMatch(/Name\s*\{\s*value/s);
114
+ expect(parsed.query).toMatch(/\bId\b/);
115
+ expect(parsed.variables).toHaveLength(1);
116
+ expect(parsed.variables[0]).toEqual({
117
+ name: "input",
118
+ type: "AccountCreateInput!",
119
+ required: true,
120
+ });
121
+ expect(typeof parsed.types).toBe("string");
122
+ } finally {
123
+ await client.close();
124
+ await server.close();
125
+ }
126
+ });
127
+
128
+ it("tools/call sf_gql_create with no returnFields defaults to Id only", async () => {
129
+ const { client, server } = await connect();
130
+ try {
131
+ const result = await client.callTool({
132
+ name: "sf_gql_create",
133
+ arguments: { org: ORG, object: "Account" },
134
+ });
135
+ const content = result.content as { type: string; text?: string }[];
136
+ const parsed = JSON.parse(content[0]?.text ?? "{}");
137
+ expect(parsed.query).toMatch(/\bId\b/);
138
+ expect(parsed.query).not.toMatch(/Name\s*\{/);
139
+ } finally {
140
+ await client.close();
141
+ await server.close();
142
+ }
143
+ });
144
+
145
+ it("tools/call sf_gql_create with custom inputVariable declares that variable", async () => {
146
+ const { client, server } = await connect();
147
+ try {
148
+ const result = await client.callTool({
149
+ name: "sf_gql_create",
150
+ arguments: { org: ORG, object: "Account", inputVariable: "accountInput" },
151
+ });
152
+ const content = result.content as { type: string; text?: string }[];
153
+ const parsed = JSON.parse(content[0]?.text ?? "{}");
154
+ expect(parsed.query).toMatch(/\$accountInput:\s*AccountCreateInput!/);
155
+ expect(parsed.query).toMatch(/input:\s*\$accountInput/);
156
+ } finally {
157
+ await client.close();
158
+ await server.close();
159
+ }
160
+ });
161
+
162
+ it("tools/call sf_gql_create with custom operationName uses it", async () => {
163
+ const { client, server } = await connect();
164
+ try {
165
+ const result = await client.callTool({
166
+ name: "sf_gql_create",
167
+ arguments: { org: ORG, object: "Account", operationName: "MakeAccount" },
168
+ });
169
+ const content = result.content as { type: string; text?: string }[];
170
+ const parsed = JSON.parse(content[0]?.text ?? "{}");
171
+ expect(parsed.query).toMatch(/mutation MakeAccount/);
172
+ } finally {
173
+ await client.close();
174
+ await server.close();
175
+ }
176
+ });
177
+
178
+ it("tools/call sf_gql_create strips leading $ from inputVariable", async () => {
179
+ const { client, server } = await connect();
180
+ try {
181
+ const result = await client.callTool({
182
+ name: "sf_gql_create",
183
+ arguments: { org: ORG, object: "Account", inputVariable: "$myInput" },
184
+ });
185
+ expect(result.isError).toBeFalsy();
186
+ const content = result.content as { type: string; text?: string }[];
187
+ const parsed = JSON.parse(content[0]?.text ?? "{}");
188
+ expect(parsed.query).toMatch(/\$myInput:\s*AccountCreateInput!/);
189
+ expect(parsed.query).toMatch(/input:\s*\$myInput/);
190
+ expect(parsed.query).not.toMatch(/\$\$myInput/);
191
+ } finally {
192
+ await client.close();
193
+ await server.close();
194
+ }
195
+ });
196
+
197
+ it("tools/call sf_gql_create with dotted returnField surfaces walker error as warning", async () => {
198
+ const { client, server } = await connect();
199
+ try {
200
+ const result = await client.callTool({
201
+ name: "sf_gql_create",
202
+ arguments: { org: ORG, object: "Account", returnFields: ["Id", "Owner.Name"] },
203
+ });
204
+ expect(result.isError).toBeFalsy();
205
+ const content = result.content as { type: string; text?: string }[];
206
+ const parsed = JSON.parse(content[0]?.text ?? "{}") as {
207
+ query: string;
208
+ warnings: string[];
209
+ };
210
+ expect(parsed.query).toMatch(/\bId\b/);
211
+ expect(parsed.warnings.some((w) => w.includes("not available in mutation results"))).toBe(
212
+ true,
213
+ );
214
+ } finally {
215
+ await client.close();
216
+ await server.close();
217
+ }
218
+ });
219
+
220
+ it("tools/call sf_gql_create with empty returnFields returns error", async () => {
221
+ const { client, server } = await connect();
222
+ try {
223
+ const result = await client.callTool({
224
+ name: "sf_gql_create",
225
+ arguments: { org: ORG, object: "Account", returnFields: [] },
226
+ });
227
+ expect(result.isError).toBe(true);
228
+ const content = result.content as { type: string; text?: string }[];
229
+ expect(content[0]?.text).toMatch(/returnFields must contain at least one field/);
230
+ } finally {
231
+ await client.close();
232
+ await server.close();
233
+ }
234
+ });
235
+ });