@stigmer/mcp-server 3.34.0 → 3.36.0

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 (157) hide show
  1. package/cli/mcp-server-stigmer.d.ts.map +1 -1
  2. package/cli/mcp-server-stigmer.js +2 -2
  3. package/cli/mcp-server-stigmer.js.map +1 -1
  4. package/config.d.ts.map +1 -1
  5. package/config.js.map +1 -1
  6. package/domains/agentexecutions/approve.d.ts.map +1 -1
  7. package/domains/agentexecutions/approve.js.map +1 -1
  8. package/domains/agentexecutions/fetch.d.ts.map +1 -1
  9. package/domains/agentexecutions/fetch.js.map +1 -1
  10. package/domains/agentexecutions/run.d.ts.map +1 -1
  11. package/domains/agentexecutions/run.js.map +1 -1
  12. package/domains/agentexecutions/tools.d.ts.map +1 -1
  13. package/domains/agentexecutions/tools.js.map +1 -1
  14. package/domains/agents/apply.d.ts.map +1 -1
  15. package/domains/agents/apply.js.map +1 -1
  16. package/domains/agents/delete.d.ts.map +1 -1
  17. package/domains/agents/delete.js.map +1 -1
  18. package/domains/agents/fetch.d.ts.map +1 -1
  19. package/domains/agents/fetch.js.map +1 -1
  20. package/domains/agents/resources.d.ts.map +1 -1
  21. package/domains/agents/resources.js.map +1 -1
  22. package/domains/agents/tools.d.ts.map +1 -1
  23. package/domains/agents/tools.js.map +1 -1
  24. package/domains/apply-visibility.d.ts.map +1 -1
  25. package/domains/apply-visibility.js.map +1 -1
  26. package/domains/channels/calls.d.ts.map +1 -1
  27. package/domains/channels/calls.js.map +1 -1
  28. package/domains/channels/errors.d.ts.map +1 -1
  29. package/domains/channels/errors.js.map +1 -1
  30. package/domains/channels/tools.d.ts.map +1 -1
  31. package/domains/channels/tools.js.map +1 -1
  32. package/domains/client.d.ts.map +1 -1
  33. package/domains/client.js.map +1 -1
  34. package/domains/conversation/calls.d.ts.map +1 -1
  35. package/domains/conversation/calls.js.map +1 -1
  36. package/domains/conversation/errors.d.ts.map +1 -1
  37. package/domains/conversation/errors.js.map +1 -1
  38. package/domains/conversation/tools.d.ts.map +1 -1
  39. package/domains/conversation/tools.js.map +1 -1
  40. package/domains/environments/apply.d.ts.map +1 -1
  41. package/domains/environments/apply.js.map +1 -1
  42. package/domains/environments/delete.d.ts.map +1 -1
  43. package/domains/environments/delete.js.map +1 -1
  44. package/domains/environments/fetch.d.ts.map +1 -1
  45. package/domains/environments/fetch.js.map +1 -1
  46. package/domains/environments/resources.d.ts.map +1 -1
  47. package/domains/environments/resources.js.map +1 -1
  48. package/domains/environments/tools.d.ts.map +1 -1
  49. package/domains/environments/tools.js.map +1 -1
  50. package/domains/executions/cancel.d.ts.map +1 -1
  51. package/domains/executions/cancel.js.map +1 -1
  52. package/domains/executions/tools.d.ts.map +1 -1
  53. package/domains/executions/tools.js.map +1 -1
  54. package/domains/marshal.d.ts.map +1 -1
  55. package/domains/marshal.js.map +1 -1
  56. package/domains/mcpservers/apply.d.ts.map +1 -1
  57. package/domains/mcpservers/apply.js.map +1 -1
  58. package/domains/mcpservers/delete.d.ts.map +1 -1
  59. package/domains/mcpservers/delete.js.map +1 -1
  60. package/domains/mcpservers/fetch.d.ts.map +1 -1
  61. package/domains/mcpservers/fetch.js.map +1 -1
  62. package/domains/mcpservers/resources.d.ts.map +1 -1
  63. package/domains/mcpservers/resources.js.map +1 -1
  64. package/domains/mcpservers/tools.d.ts.map +1 -1
  65. package/domains/mcpservers/tools.js.map +1 -1
  66. package/domains/memory/calls.d.ts.map +1 -1
  67. package/domains/memory/calls.js.map +1 -1
  68. package/domains/memory/context.d.ts.map +1 -1
  69. package/domains/memory/context.js.map +1 -1
  70. package/domains/memory/errors.d.ts.map +1 -1
  71. package/domains/memory/errors.js.map +1 -1
  72. package/domains/memory/tools.d.ts.map +1 -1
  73. package/domains/memory/tools.js.map +1 -1
  74. package/domains/resourcehandler.d.ts.map +1 -1
  75. package/domains/resourcehandler.js.map +1 -1
  76. package/domains/resourceuri.d.ts.map +1 -1
  77. package/domains/resourceuri.js.map +1 -1
  78. package/domains/rpcerr.d.ts.map +1 -1
  79. package/domains/rpcerr.js.map +1 -1
  80. package/domains/search/tools.d.ts.map +1 -1
  81. package/domains/search/tools.js.map +1 -1
  82. package/domains/skills/delete.d.ts.map +1 -1
  83. package/domains/skills/delete.js.map +1 -1
  84. package/domains/skills/fetch.d.ts.map +1 -1
  85. package/domains/skills/fetch.js.map +1 -1
  86. package/domains/skills/resources.d.ts.map +1 -1
  87. package/domains/skills/resources.js.map +1 -1
  88. package/domains/skills/tools.d.ts.map +1 -1
  89. package/domains/skills/tools.js.map +1 -1
  90. package/domains/skills/versions.d.ts.map +1 -1
  91. package/domains/skills/versions.js.map +1 -1
  92. package/domains/toolresult.d.ts.map +1 -1
  93. package/domains/toolresult.js.map +1 -1
  94. package/domains/workflowexecutions/approvals.d.ts.map +1 -1
  95. package/domains/workflowexecutions/approvals.js.map +1 -1
  96. package/domains/workflowexecutions/run.d.ts.map +1 -1
  97. package/domains/workflowexecutions/run.js.map +1 -1
  98. package/domains/workflowexecutions/tools.d.ts.map +1 -1
  99. package/domains/workflowexecutions/tools.js.map +1 -1
  100. package/domains/workflows/apply.d.ts.map +1 -1
  101. package/domains/workflows/apply.js.map +1 -1
  102. package/domains/workflows/delete.d.ts.map +1 -1
  103. package/domains/workflows/delete.js.map +1 -1
  104. package/domains/workflows/fetch.d.ts.map +1 -1
  105. package/domains/workflows/fetch.js.map +1 -1
  106. package/domains/workflows/resources.d.ts.map +1 -1
  107. package/domains/workflows/resources.js.map +1 -1
  108. package/domains/workflows/taskkinds.d.ts.map +1 -1
  109. package/domains/workflows/taskkinds.js.map +1 -1
  110. package/domains/workflows/tools.d.ts.map +1 -1
  111. package/domains/workflows/tools.js.map +1 -1
  112. package/domains/workflows/validate.d.ts.map +1 -1
  113. package/domains/workflows/validate.js.map +1 -1
  114. package/domains/workflows/versions.d.ts.map +1 -1
  115. package/domains/workflows/versions.js.map +1 -1
  116. package/gen/agent.d.ts.map +1 -1
  117. package/gen/agent.js.map +1 -1
  118. package/gen/apply-runtime.d.ts.map +1 -1
  119. package/gen/apply-runtime.js.map +1 -1
  120. package/gen/environment.d.ts.map +1 -1
  121. package/gen/environment.js.map +1 -1
  122. package/gen/mcpserver.d.ts.map +1 -1
  123. package/gen/mcpserver.js.map +1 -1
  124. package/gen/workflow.d.ts.map +1 -1
  125. package/gen/workflow.js +2 -2
  126. package/gen/workflow.js.map +1 -1
  127. package/index.d.ts.map +1 -1
  128. package/index.js.map +1 -1
  129. package/logger.d.ts.map +1 -1
  130. package/logger.js.map +1 -1
  131. package/package.json +3 -3
  132. package/readiness.d.ts.map +1 -1
  133. package/readiness.js.map +1 -1
  134. package/server.d.ts.map +1 -1
  135. package/server.js.map +1 -1
  136. package/src/gen/workflow.ts +2 -2
  137. package/src/config.test.ts +0 -88
  138. package/src/domains/apply.integration.test.ts +0 -313
  139. package/src/domains/channels/channels.integration.test.ts +0 -272
  140. package/src/domains/channels/errors.test.ts +0 -52
  141. package/src/domains/conversation/conversation.integration.test.ts +0 -233
  142. package/src/domains/deletes.integration.test.ts +0 -147
  143. package/src/domains/executions.integration.test.ts +0 -376
  144. package/src/domains/memory/context.test.ts +0 -95
  145. package/src/domains/memory/errors.test.ts +0 -54
  146. package/src/domains/memory/memory.integration.test.ts +0 -253
  147. package/src/domains/reads.integration.test.ts +0 -163
  148. package/src/domains/resources.integration.test.ts +0 -157
  149. package/src/domains/resourceuri.test.ts +0 -104
  150. package/src/domains/rpcerr.test.ts +0 -62
  151. package/src/domains/search/search.integration.test.ts +0 -141
  152. package/src/domains/versions.integration.test.ts +0 -212
  153. package/src/domains/workflows/validate.integration.test.ts +0 -117
  154. package/src/domains/workflows/workflow-tools.integration.test.ts +0 -148
  155. package/src/http.integration.test.ts +0 -235
  156. package/src/readiness.test.ts +0 -90
  157. package/src/server.integration.test.ts +0 -82
@@ -1,235 +0,0 @@
1
- // HTTP transport hardening + OAuth discovery tests.
2
- //
3
- // Boots the real Streamable HTTP transport on a loopback port and exercises the
4
- // additive surface added in Phase 6: the /health probe, RFC 9728 protected
5
- // resource metadata (GET + CORS preflight), and the WWW-Authenticate challenge
6
- // on token-less requests. Token validation is never performed here — presence is
7
- // the only check (Go parity: internal/server/http.go).
8
-
9
- import { createServer as createNetServer, type AddressInfo } from "node:net";
10
-
11
- import { afterAll, beforeAll, describe, expect, it } from "vitest";
12
-
13
- import type { Config } from "./config";
14
- import { configureLogger } from "./logger";
15
- import { routedServerFactory, serveHttp } from "./server";
16
-
17
- configureLogger({ level: "error", format: "text" });
18
-
19
- let port: number;
20
- let controller: AbortController;
21
- let serving: Promise<void>;
22
-
23
- function freePort(): Promise<number> {
24
- return new Promise((resolve) => {
25
- const s = createNetServer();
26
- s.listen(0, "127.0.0.1", () => {
27
- const p = (s.address() as AddressInfo).port;
28
- s.close(() => resolve(p));
29
- });
30
- });
31
- }
32
-
33
- beforeAll(async () => {
34
- port = await freePort();
35
- const cfg: Config = {
36
- stigmerServerAddress: "localhost:7234",
37
- apiKey: "",
38
- transport: "http",
39
- roster: "full",
40
- httpPort: String(port),
41
- httpAuthEnabled: true,
42
- oauth: {
43
- enabled: true,
44
- resource: "https://mcp.stigmer.ai",
45
- authorizationServers: ["https://auth.example.com"],
46
- scopesSupported: ["read", "write"],
47
- },
48
- logFormat: "text",
49
- logLevel: "error",
50
- };
51
- controller = new AbortController();
52
- // The REAL route dispatch, so these tests hold the production route
53
- // table: full roster on "/", channels on "/channels", conversation on
54
- // "/conversation", 404 anywhere else.
55
- serving = serveHttp(
56
- routedServerFactory({ serverAddress: cfg.stigmerServerAddress, apiKey: "" }),
57
- cfg,
58
- controller.signal,
59
- );
60
- // Give the listener a tick to bind.
61
- await new Promise((r) => setTimeout(r, 100));
62
- });
63
-
64
- afterAll(async () => {
65
- controller.abort();
66
- await serving;
67
- });
68
-
69
- const base = () => `http://127.0.0.1:${port}`;
70
-
71
- /**
72
- * Every refusal must be a JSON-RPC error object with Content-Type
73
- * application/json (oss#316 — strict MCP clients parse the body; text/plain
74
- * surfaced as opaque content-type errors). Returns the error for
75
- * code-specific assertions.
76
- */
77
- async function expectJsonRpcError(res: Response): Promise<{ code: number; message: string }> {
78
- expect(res.headers.get("content-type")).toBe("application/json");
79
- const body = (await res.json()) as { jsonrpc: string; error: { code: number; message: string }; id: null };
80
- expect(body.jsonrpc).toBe("2.0");
81
- expect(body.id).toBeNull();
82
- expect(typeof body.error.code).toBe("number");
83
- return body.error;
84
- }
85
-
86
- describe("HTTP transport hardening + OAuth discovery", () => {
87
- it("answers the /health probe without auth", async () => {
88
- const res = await fetch(`${base()}/health`);
89
- expect(res.status).toBe(200);
90
- expect(await res.json()).toEqual({ status: "ok" });
91
- });
92
-
93
- it("reports unready on /ready when the backend hop is down (no auth required)", async () => {
94
- // The suite's backend address points at nothing — exactly the failure
95
- // class /ready exists to expose while /health stays green (oss#316).
96
- const res = await fetch(`${base()}/ready`);
97
- expect(res.status).toBe(503);
98
- const body = (await res.json()) as { status: string; reason?: string };
99
- expect(body.status).toBe("unready");
100
- expect(body.reason).toContain("backend health check failed");
101
- });
102
-
103
- it("serves RFC 9728 protected resource metadata", async () => {
104
- const res = await fetch(`${base()}/.well-known/oauth-protected-resource`);
105
- expect(res.status).toBe(200);
106
- expect(res.headers.get("access-control-allow-origin")).toBe("*");
107
- expect(await res.json()).toEqual({
108
- resource: "https://mcp.stigmer.ai",
109
- authorization_servers: ["https://auth.example.com"],
110
- bearer_methods_supported: ["header"],
111
- resource_name: "Stigmer MCP Server",
112
- scopes_supported: ["read", "write"],
113
- });
114
- });
115
-
116
- it("answers the CORS preflight for the metadata document", async () => {
117
- const res = await fetch(`${base()}/.well-known/oauth-protected-resource`, { method: "OPTIONS" });
118
- expect(res.status).toBe(204);
119
- expect(res.headers.get("access-control-allow-origin")).toBe("*");
120
- expect(res.headers.get("access-control-allow-methods")).toContain("GET");
121
- });
122
-
123
- it("challenges token-less requests with WWW-Authenticate and a JSON-RPC body", async () => {
124
- const res = await fetch(`${base()}/`, { method: "POST", body: "{}" });
125
- expect(res.status).toBe(401);
126
- const challenge = res.headers.get("www-authenticate") ?? "";
127
- expect(challenge).toContain('realm="stigmer"');
128
- expect(challenge).toContain(
129
- 'resource_metadata="https://mcp.stigmer.ai/.well-known/oauth-protected-resource"',
130
- );
131
- expect(challenge).toContain('scope="read write"');
132
- const error = await expectJsonRpcError(res);
133
- expect(error.code).toBe(-32000);
134
- expect(error.message).toContain("missing or malformed Authorization");
135
- });
136
-
137
- it("rejects a malformed Authorization header as token-less", async () => {
138
- // A non-"Bearer " scheme yields an empty token and is treated as missing.
139
- const res = await fetch(`${base()}/`, {
140
- method: "POST",
141
- headers: { authorization: "Basic Zm9vOmJhcg==" },
142
- body: "{}",
143
- });
144
- expect(res.status).toBe(401);
145
- expect(res.headers.get("www-authenticate") ?? "").toContain('realm="stigmer"');
146
- await expectJsonRpcError(res);
147
- });
148
-
149
- it("returns the SDK's session-not-found shape (404, code -32001) for an unknown session", async () => {
150
- // -32001 on a 404 is the streamable-HTTP recovery signal: on it a
151
- // conformant client MUST open a new session with a fresh initialize —
152
- // how clients survive the bridge's in-memory sessions dying on restart.
153
- const res = await fetch(`${base()}/`, {
154
- method: "POST",
155
- headers: { authorization: "Bearer test-token", "mcp-session-id": "does-not-exist" },
156
- body: "{}",
157
- });
158
- expect(res.status).toBe(404);
159
- const error = await expectJsonRpcError(res);
160
- expect(error.code).toBe(-32001);
161
- expect(error.message).toContain("unknown or expired MCP session");
162
- });
163
-
164
- it("rejects a sessionless non-initialize POST", async () => {
165
- const res = await fetch(`${base()}/`, {
166
- method: "POST",
167
- headers: { authorization: "Bearer test-token", "content-type": "application/json" },
168
- body: JSON.stringify({ jsonrpc: "2.0", id: 1, method: "tools/list" }),
169
- });
170
- expect(res.status).toBe(400);
171
- const error = await expectJsonRpcError(res);
172
- expect(error.code).toBe(-32000);
173
- expect(error.message).toContain("an initialize request is required");
174
- });
175
-
176
- it("rejects a sessionless GET (no Mcp-Session-Id)", async () => {
177
- const res = await fetch(`${base()}/`, {
178
- method: "GET",
179
- headers: { authorization: "Bearer test-token" },
180
- });
181
- expect(res.status).toBe(400);
182
- const error = await expectJsonRpcError(res);
183
- expect(error.code).toBe(-32000);
184
- expect(error.message).toContain("Mcp-Session-Id header is required");
185
- });
186
- });
187
-
188
- /** POST a real MCP initialize to `path`; returns the raw Response. */
189
- function initialize(path: string): Promise<Response> {
190
- return fetch(`${base()}${path}`, {
191
- method: "POST",
192
- headers: {
193
- authorization: "Bearer test-token",
194
- "content-type": "application/json",
195
- accept: "application/json, text/event-stream",
196
- },
197
- body: JSON.stringify({
198
- jsonrpc: "2.0",
199
- id: 1,
200
- method: "initialize",
201
- params: {
202
- protocolVersion: "2025-03-26",
203
- capabilities: {},
204
- clientInfo: { name: "http-integration", version: "test" },
205
- },
206
- }),
207
- });
208
- }
209
-
210
- describe("HTTP route dispatch (the closed route table)", () => {
211
- it.each([
212
- ["/", "mcp-server-stigmer"],
213
- ["/channels", "mcp-server-stigmer-channels"],
214
- ["/conversation", "mcp-server-stigmer-conversation"],
215
- ["/memory", "mcp-server-stigmer-memory"],
216
- ])("serves the %s roster as %s", async (path, serverName) => {
217
- const res = await initialize(path);
218
- expect(res.status).toBe(200);
219
- // The initialize result rides an SSE frame; the serverInfo name is
220
- // the roster's identity and must match the route exactly.
221
- expect(await res.text()).toContain(`"name":"${serverName}"`);
222
- });
223
-
224
- it("refuses an unknown route with 404 instead of a default roster", async () => {
225
- // The 2026-08-05 incident regression pin: a bridge that does not
226
- // recognize a route must say so at connect time — silently serving
227
- // the full roster there once handed an agent every management tool
228
- // except the send tool its attachment existed for.
229
- const res = await initialize("/no-such-roster");
230
- expect(res.status).toBe(404);
231
- const error = await expectJsonRpcError(res);
232
- expect(error.code).toBe(-32000);
233
- expect(error.message).toContain("unknown MCP route: /no-such-roster");
234
- });
235
- });
@@ -1,90 +0,0 @@
1
- // Unit tests for the cached readiness check (oss#316). The backend RPC is
2
- // injected, so caching, in-flight dedupe, and verdict propagation are pinned
3
- // without a live gRPC server; the /ready route's wiring (including the real
4
- // checkBackendHealth against a dead backend) is covered by
5
- // http.integration.test.ts.
6
-
7
- import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
8
-
9
- import {
10
- createReadinessCheck,
11
- READINESS_CACHE_TTL_MS,
12
- type ReadinessResult,
13
- } from "./readiness";
14
-
15
- const READY: ReadinessResult = { ready: true };
16
- const UNREADY: ReadinessResult = { ready: false, reason: "backend health status: NOT_SERVING" };
17
-
18
- beforeEach(() => {
19
- vi.useFakeTimers();
20
- });
21
-
22
- afterEach(() => {
23
- vi.useRealTimers();
24
- });
25
-
26
- describe("createReadinessCheck", () => {
27
- it("propagates the ready verdict", async () => {
28
- const check = createReadinessCheck("addr", async () => READY);
29
- await expect(check()).resolves.toEqual({ ready: true });
30
- });
31
-
32
- it("propagates the unready verdict with its reason", async () => {
33
- const check = createReadinessCheck("addr", async () => UNREADY);
34
- await expect(check()).resolves.toEqual(UNREADY);
35
- });
36
-
37
- it("serves cached verdicts within the TTL without re-dialing", async () => {
38
- const probe = vi.fn(async () => READY);
39
- const check = createReadinessCheck("addr", probe);
40
- await check();
41
- vi.advanceTimersByTime(READINESS_CACHE_TTL_MS - 1);
42
- await check();
43
- expect(probe).toHaveBeenCalledTimes(1);
44
- });
45
-
46
- it("re-checks after the TTL expires", async () => {
47
- const probe = vi.fn(async () => READY);
48
- const check = createReadinessCheck("addr", probe);
49
- await check();
50
- vi.advanceTimersByTime(READINESS_CACHE_TTL_MS + 1);
51
- await check();
52
- expect(probe).toHaveBeenCalledTimes(2);
53
- });
54
-
55
- it("recovers: an unready verdict is replaced by a ready one after the TTL", async () => {
56
- const probe = vi
57
- .fn<() => Promise<ReadinessResult>>()
58
- .mockResolvedValueOnce(UNREADY)
59
- .mockResolvedValueOnce(READY);
60
- const check = createReadinessCheck("addr", probe);
61
- await expect(check()).resolves.toEqual(UNREADY);
62
- vi.advanceTimersByTime(READINESS_CACHE_TTL_MS + 1);
63
- await expect(check()).resolves.toEqual({ ready: true });
64
- });
65
-
66
- it("shares one in-flight RPC across concurrent callers", async () => {
67
- let resolveProbe!: (r: ReadinessResult) => void;
68
- const probe = vi.fn(
69
- () => new Promise<ReadinessResult>((resolve) => (resolveProbe = resolve)),
70
- );
71
- const check = createReadinessCheck("addr", probe);
72
-
73
- const first = check();
74
- const second = check();
75
- resolveProbe(READY);
76
-
77
- await expect(first).resolves.toEqual({ ready: true });
78
- await expect(second).resolves.toEqual({ ready: true });
79
- expect(probe).toHaveBeenCalledTimes(1);
80
- });
81
-
82
- it("passes the configured backend address to the probe", async () => {
83
- const probe = vi.fn(async (addr: string) => {
84
- expect(addr).toBe("backend:50051");
85
- return READY;
86
- });
87
- await createReadinessCheck("backend:50051", probe)();
88
- expect(probe).toHaveBeenCalledWith("backend:50051");
89
- });
90
- });
@@ -1,82 +0,0 @@
1
- // In-process integration test for the MCP server (the tester gate).
2
- //
3
- // Stands up a real in-process Connect server for AgentQueryController, points
4
- // the MCP server at it, drives it through an in-memory MCP client, and asserts:
5
- // - tools/list advertises get_agent
6
- // - get_agent returns the agent's protojson, byte-comparable (after parse)
7
- // with the canonical toJson — the parity contract (DD-005).
8
-
9
- import { create, toJson } from "@bufbuild/protobuf";
10
- import type { ConnectRouter } from "@connectrpc/connect";
11
- import { connectNodeAdapter } from "@connectrpc/connect-node";
12
- import {
13
- createServer as createHttp2Server,
14
- type Http2Server,
15
- type ServerHttp2Session,
16
- } from "node:http2";
17
- import type { AddressInfo } from "node:net";
18
-
19
- import { Client } from "@modelcontextprotocol/sdk/client/index.js";
20
- import { InMemoryTransport } from "@modelcontextprotocol/sdk/inMemory.js";
21
- import { AgentSchema } from "@stigmer/protos/ai/stigmer/agentic/agent/v1/api_pb";
22
- import { AgentQueryController } from "@stigmer/protos/ai/stigmer/agentic/agent/v1/query_pb";
23
- import { afterAll, beforeAll, describe, expect, it } from "vitest";
24
-
25
- import { configureLogger } from "./logger";
26
- import { createServer } from "./server";
27
-
28
- configureLogger({ level: "error", format: "text" });
29
-
30
- const knownAgent = create(AgentSchema, {
31
- apiVersion: "v1",
32
- kind: "agent",
33
- metadata: { name: "Code Reviewer", slug: "code-reviewer", org: "stigmer", id: "agt-123" },
34
- });
35
-
36
- let backend: Http2Server;
37
- let client: Client;
38
- const openSessions = new Set<ServerHttp2Session>();
39
-
40
- beforeAll(async () => {
41
- // Real in-process Connect (gRPC-web over h2c) backend serving the controller.
42
- const routes = (router: ConnectRouter) =>
43
- router.service(AgentQueryController, { getByReference: () => knownAgent });
44
- backend = createHttp2Server(connectNodeAdapter({ routes }));
45
- // Track keep-alive sessions so teardown can force them closed (otherwise
46
- // backend.close() blocks on the per-call transports' idle connections).
47
- backend.on("session", (session) => {
48
- openSessions.add(session);
49
- session.on("close", () => openSessions.delete(session));
50
- });
51
- await new Promise<void>((resolve) => backend.listen(0, "127.0.0.1", resolve));
52
- const port = (backend.address() as AddressInfo).port;
53
-
54
- const mcp = createServer({ serverAddress: `127.0.0.1:${port}`, apiKey: "" });
55
- const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
56
- client = new Client({ name: "integration", version: "test" });
57
- await Promise.all([mcp.connect(serverTransport), client.connect(clientTransport)]);
58
- });
59
-
60
- afterAll(async () => {
61
- await client?.close();
62
- for (const session of openSessions) session.destroy();
63
- await new Promise<void>((resolve) => backend.close(() => resolve()));
64
- });
65
-
66
- describe("MCP server integration", () => {
67
- it("advertises get_agent", async () => {
68
- const { tools } = await client.listTools();
69
- expect(tools.map((t) => t.name)).toContain("get_agent");
70
- });
71
-
72
- it("get_agent returns protojson matching the canonical toJson output", async () => {
73
- const result = (await client.callTool({
74
- name: "get_agent",
75
- arguments: { org: "stigmer", slug: "code-reviewer" },
76
- })) as { content: Array<{ type: string; text?: string }>; isError?: boolean };
77
-
78
- expect(result.isError).toBeFalsy();
79
- const parsed = JSON.parse(result.content[0]?.text ?? "{}");
80
- expect(parsed).toEqual(toJson(AgentSchema, knownAgent, { useProtoFieldName: true }));
81
- });
82
- });