@stigmer/mcp-server 3.0.8-dev.20260612122433

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 (205) hide show
  1. package/LICENSE +190 -0
  2. package/README.md +157 -0
  3. package/cli/mcp-server-stigmer.d.ts +3 -0
  4. package/cli/mcp-server-stigmer.d.ts.map +1 -0
  5. package/cli/mcp-server-stigmer.js +201 -0
  6. package/cli/mcp-server-stigmer.js.map +1 -0
  7. package/config.d.ts +44 -0
  8. package/config.d.ts.map +1 -0
  9. package/config.js +92 -0
  10. package/config.js.map +1 -0
  11. package/domains/agents/apply.d.ts +4 -0
  12. package/domains/agents/apply.d.ts.map +1 -0
  13. package/domains/agents/apply.js +25 -0
  14. package/domains/agents/apply.js.map +1 -0
  15. package/domains/agents/delete.d.ts +3 -0
  16. package/domains/agents/delete.d.ts.map +1 -0
  17. package/domains/agents/delete.js +35 -0
  18. package/domains/agents/delete.js.map +1 -0
  19. package/domains/agents/fetch.d.ts +6 -0
  20. package/domains/agents/fetch.d.ts.map +1 -0
  21. package/domains/agents/fetch.js +24 -0
  22. package/domains/agents/fetch.js.map +1 -0
  23. package/domains/agents/resources.d.ts +5 -0
  24. package/domains/agents/resources.d.ts.map +1 -0
  25. package/domains/agents/resources.js +16 -0
  26. package/domains/agents/resources.js.map +1 -0
  27. package/domains/agents/tools.d.ts +5 -0
  28. package/domains/agents/tools.d.ts.map +1 -0
  29. package/domains/agents/tools.js +41 -0
  30. package/domains/agents/tools.js.map +1 -0
  31. package/domains/client.d.ts +53 -0
  32. package/domains/client.d.ts.map +1 -0
  33. package/domains/client.js +62 -0
  34. package/domains/client.js.map +1 -0
  35. package/domains/marshal.d.ts +8 -0
  36. package/domains/marshal.d.ts.map +1 -0
  37. package/domains/marshal.js +17 -0
  38. package/domains/marshal.js.map +1 -0
  39. package/domains/mcpservers/apply.d.ts +4 -0
  40. package/domains/mcpservers/apply.d.ts.map +1 -0
  41. package/domains/mcpservers/apply.js +26 -0
  42. package/domains/mcpservers/apply.js.map +1 -0
  43. package/domains/mcpservers/delete.d.ts +6 -0
  44. package/domains/mcpservers/delete.d.ts.map +1 -0
  45. package/domains/mcpservers/delete.js +42 -0
  46. package/domains/mcpservers/delete.js.map +1 -0
  47. package/domains/mcpservers/fetch.d.ts +7 -0
  48. package/domains/mcpservers/fetch.d.ts.map +1 -0
  49. package/domains/mcpservers/fetch.js +26 -0
  50. package/domains/mcpservers/fetch.js.map +1 -0
  51. package/domains/mcpservers/resources.d.ts +5 -0
  52. package/domains/mcpservers/resources.d.ts.map +1 -0
  53. package/domains/mcpservers/resources.js +16 -0
  54. package/domains/mcpservers/resources.js.map +1 -0
  55. package/domains/mcpservers/tools.d.ts +5 -0
  56. package/domains/mcpservers/tools.d.ts.map +1 -0
  57. package/domains/mcpservers/tools.js +39 -0
  58. package/domains/mcpservers/tools.js.map +1 -0
  59. package/domains/resourcehandler.d.ts +27 -0
  60. package/domains/resourcehandler.d.ts.map +1 -0
  61. package/domains/resourcehandler.js +32 -0
  62. package/domains/resourcehandler.js.map +1 -0
  63. package/domains/resourceuri.d.ts +34 -0
  64. package/domains/resourceuri.d.ts.map +1 -0
  65. package/domains/resourceuri.js +100 -0
  66. package/domains/resourceuri.js.map +1 -0
  67. package/domains/rpcerr.d.ts +8 -0
  68. package/domains/rpcerr.d.ts.map +1 -0
  69. package/domains/rpcerr.js +42 -0
  70. package/domains/rpcerr.js.map +1 -0
  71. package/domains/search/tools.d.ts +5 -0
  72. package/domains/search/tools.d.ts.map +1 -0
  73. package/domains/search/tools.js +125 -0
  74. package/domains/search/tools.js.map +1 -0
  75. package/domains/skills/delete.d.ts +6 -0
  76. package/domains/skills/delete.d.ts.map +1 -0
  77. package/domains/skills/delete.js +38 -0
  78. package/domains/skills/delete.js.map +1 -0
  79. package/domains/skills/fetch.d.ts +6 -0
  80. package/domains/skills/fetch.d.ts.map +1 -0
  81. package/domains/skills/fetch.js +28 -0
  82. package/domains/skills/fetch.js.map +1 -0
  83. package/domains/skills/resources.d.ts +5 -0
  84. package/domains/skills/resources.d.ts.map +1 -0
  85. package/domains/skills/resources.js +25 -0
  86. package/domains/skills/resources.js.map +1 -0
  87. package/domains/skills/tools.d.ts +5 -0
  88. package/domains/skills/tools.d.ts.map +1 -0
  89. package/domains/skills/tools.js +39 -0
  90. package/domains/skills/tools.js.map +1 -0
  91. package/domains/toolresult.d.ts +12 -0
  92. package/domains/toolresult.d.ts.map +1 -0
  93. package/domains/toolresult.js +30 -0
  94. package/domains/toolresult.js.map +1 -0
  95. package/domains/workflowexecutions/tools.d.ts +5 -0
  96. package/domains/workflowexecutions/tools.d.ts.map +1 -0
  97. package/domains/workflowexecutions/tools.js +80 -0
  98. package/domains/workflowexecutions/tools.js.map +1 -0
  99. package/domains/workflows/apply.d.ts +4 -0
  100. package/domains/workflows/apply.d.ts.map +1 -0
  101. package/domains/workflows/apply.js +30 -0
  102. package/domains/workflows/apply.js.map +1 -0
  103. package/domains/workflows/delete.d.ts +3 -0
  104. package/domains/workflows/delete.d.ts.map +1 -0
  105. package/domains/workflows/delete.js +35 -0
  106. package/domains/workflows/delete.js.map +1 -0
  107. package/domains/workflows/fetch.d.ts +6 -0
  108. package/domains/workflows/fetch.d.ts.map +1 -0
  109. package/domains/workflows/fetch.js +25 -0
  110. package/domains/workflows/fetch.js.map +1 -0
  111. package/domains/workflows/resources.d.ts +5 -0
  112. package/domains/workflows/resources.d.ts.map +1 -0
  113. package/domains/workflows/resources.js +16 -0
  114. package/domains/workflows/resources.js.map +1 -0
  115. package/domains/workflows/taskkinds.d.ts +5 -0
  116. package/domains/workflows/taskkinds.d.ts.map +1 -0
  117. package/domains/workflows/taskkinds.js +66 -0
  118. package/domains/workflows/taskkinds.js.map +1 -0
  119. package/domains/workflows/tools.d.ts +5 -0
  120. package/domains/workflows/tools.d.ts.map +1 -0
  121. package/domains/workflows/tools.js +35 -0
  122. package/domains/workflows/tools.js.map +1 -0
  123. package/domains/workflows/validate.d.ts +5 -0
  124. package/domains/workflows/validate.d.ts.map +1 -0
  125. package/domains/workflows/validate.js +113 -0
  126. package/domains/workflows/validate.js.map +1 -0
  127. package/gen/agent.d.ts +385 -0
  128. package/gen/agent.d.ts.map +1 -0
  129. package/gen/agent.js +170 -0
  130. package/gen/agent.js.map +1 -0
  131. package/gen/apply-runtime.d.ts +18 -0
  132. package/gen/apply-runtime.d.ts.map +1 -0
  133. package/gen/apply-runtime.js +50 -0
  134. package/gen/apply-runtime.js.map +1 -0
  135. package/gen/mcpserver.d.ts +289 -0
  136. package/gen/mcpserver.d.ts.map +1 -0
  137. package/gen/mcpserver.js +166 -0
  138. package/gen/mcpserver.js.map +1 -0
  139. package/gen/workflow.d.ts +805 -0
  140. package/gen/workflow.d.ts.map +1 -0
  141. package/gen/workflow.js +842 -0
  142. package/gen/workflow.js.map +1 -0
  143. package/index.d.ts +20 -0
  144. package/index.d.ts.map +1 -0
  145. package/index.js +58 -0
  146. package/index.js.map +1 -0
  147. package/logger.d.ts +20 -0
  148. package/logger.d.ts.map +1 -0
  149. package/logger.js +41 -0
  150. package/logger.js.map +1 -0
  151. package/package.json +43 -0
  152. package/server.d.ts +60 -0
  153. package/server.d.ts.map +1 -0
  154. package/server.js +366 -0
  155. package/server.js.map +1 -0
  156. package/src/cli/mcp-server-stigmer.ts +42 -0
  157. package/src/config.test.ts +88 -0
  158. package/src/config.ts +151 -0
  159. package/src/domains/agents/apply.ts +30 -0
  160. package/src/domains/agents/delete.ts +41 -0
  161. package/src/domains/agents/fetch.ts +33 -0
  162. package/src/domains/agents/resources.ts +20 -0
  163. package/src/domains/agents/tools.ts +68 -0
  164. package/src/domains/apply.integration.test.ts +220 -0
  165. package/src/domains/client.ts +95 -0
  166. package/src/domains/deletes.integration.test.ts +124 -0
  167. package/src/domains/marshal.ts +21 -0
  168. package/src/domains/mcpservers/apply.ts +36 -0
  169. package/src/domains/mcpservers/delete.ts +51 -0
  170. package/src/domains/mcpservers/fetch.ts +35 -0
  171. package/src/domains/mcpservers/resources.ts +20 -0
  172. package/src/domains/mcpservers/tools.ts +74 -0
  173. package/src/domains/reads.integration.test.ts +134 -0
  174. package/src/domains/resourcehandler.ts +90 -0
  175. package/src/domains/resources.integration.test.ts +139 -0
  176. package/src/domains/resourceuri.test.ts +97 -0
  177. package/src/domains/resourceuri.ts +124 -0
  178. package/src/domains/rpcerr.test.ts +62 -0
  179. package/src/domains/rpcerr.ts +46 -0
  180. package/src/domains/search/search.integration.test.ts +127 -0
  181. package/src/domains/search/tools.ts +160 -0
  182. package/src/domains/skills/delete.ts +44 -0
  183. package/src/domains/skills/fetch.ts +38 -0
  184. package/src/domains/skills/resources.ts +33 -0
  185. package/src/domains/skills/tools.ts +67 -0
  186. package/src/domains/toolresult.ts +33 -0
  187. package/src/domains/workflowexecutions/tools.ts +133 -0
  188. package/src/domains/workflows/apply.ts +40 -0
  189. package/src/domains/workflows/delete.ts +44 -0
  190. package/src/domains/workflows/fetch.ts +34 -0
  191. package/src/domains/workflows/resources.ts +20 -0
  192. package/src/domains/workflows/taskkinds.ts +103 -0
  193. package/src/domains/workflows/tools.ts +68 -0
  194. package/src/domains/workflows/validate.integration.test.ts +117 -0
  195. package/src/domains/workflows/validate.ts +144 -0
  196. package/src/domains/workflows/workflow-tools.integration.test.ts +148 -0
  197. package/src/gen/agent.ts +173 -0
  198. package/src/gen/apply-runtime.ts +52 -0
  199. package/src/gen/mcpserver.ts +163 -0
  200. package/src/gen/workflow.ts +858 -0
  201. package/src/http.integration.test.ts +140 -0
  202. package/src/index.ts +66 -0
  203. package/src/logger.ts +49 -0
  204. package/src/server.integration.test.ts +82 -0
  205. package/src/server.ts +414 -0
@@ -0,0 +1,140 @@
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 { createServer, 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
+ httpPort: String(port),
40
+ httpAuthEnabled: true,
41
+ oauth: {
42
+ enabled: true,
43
+ resource: "https://mcp.stigmer.ai",
44
+ authorizationServers: ["https://auth.example.com"],
45
+ scopesSupported: ["read", "write"],
46
+ },
47
+ logFormat: "text",
48
+ logLevel: "error",
49
+ };
50
+ controller = new AbortController();
51
+ serving = serveHttp(() => createServer({ serverAddress: cfg.stigmerServerAddress, apiKey: "" }), cfg, controller.signal);
52
+ // Give the listener a tick to bind.
53
+ await new Promise((r) => setTimeout(r, 100));
54
+ });
55
+
56
+ afterAll(async () => {
57
+ controller.abort();
58
+ await serving;
59
+ });
60
+
61
+ const base = () => `http://127.0.0.1:${port}`;
62
+
63
+ describe("HTTP transport hardening + OAuth discovery", () => {
64
+ it("answers the /health probe without auth", async () => {
65
+ const res = await fetch(`${base()}/health`);
66
+ expect(res.status).toBe(200);
67
+ expect(await res.json()).toEqual({ status: "ok" });
68
+ });
69
+
70
+ it("serves RFC 9728 protected resource metadata", async () => {
71
+ const res = await fetch(`${base()}/.well-known/oauth-protected-resource`);
72
+ expect(res.status).toBe(200);
73
+ expect(res.headers.get("access-control-allow-origin")).toBe("*");
74
+ expect(await res.json()).toEqual({
75
+ resource: "https://mcp.stigmer.ai",
76
+ authorization_servers: ["https://auth.example.com"],
77
+ bearer_methods_supported: ["header"],
78
+ resource_name: "Stigmer MCP Server",
79
+ scopes_supported: ["read", "write"],
80
+ });
81
+ });
82
+
83
+ it("answers the CORS preflight for the metadata document", async () => {
84
+ const res = await fetch(`${base()}/.well-known/oauth-protected-resource`, { method: "OPTIONS" });
85
+ expect(res.status).toBe(204);
86
+ expect(res.headers.get("access-control-allow-origin")).toBe("*");
87
+ expect(res.headers.get("access-control-allow-methods")).toContain("GET");
88
+ });
89
+
90
+ it("challenges token-less requests with WWW-Authenticate", async () => {
91
+ const res = await fetch(`${base()}/`, { method: "POST", body: "{}" });
92
+ expect(res.status).toBe(401);
93
+ const challenge = res.headers.get("www-authenticate") ?? "";
94
+ expect(challenge).toContain('realm="stigmer"');
95
+ expect(challenge).toContain(
96
+ 'resource_metadata="https://mcp.stigmer.ai/.well-known/oauth-protected-resource"',
97
+ );
98
+ expect(challenge).toContain('scope="read write"');
99
+ });
100
+
101
+ it("rejects a malformed Authorization header as token-less", async () => {
102
+ // A non-"Bearer " scheme yields an empty token and is treated as missing.
103
+ const res = await fetch(`${base()}/`, {
104
+ method: "POST",
105
+ headers: { authorization: "Basic Zm9vOmJhcg==" },
106
+ body: "{}",
107
+ });
108
+ expect(res.status).toBe(401);
109
+ expect(res.headers.get("www-authenticate") ?? "").toContain('realm="stigmer"');
110
+ });
111
+
112
+ it("returns 404 for an unknown MCP session even with a valid token", async () => {
113
+ const res = await fetch(`${base()}/`, {
114
+ method: "POST",
115
+ headers: { authorization: "Bearer test-token", "mcp-session-id": "does-not-exist" },
116
+ body: "{}",
117
+ });
118
+ expect(res.status).toBe(404);
119
+ expect(await res.text()).toContain("unknown or expired MCP session");
120
+ });
121
+
122
+ it("rejects a sessionless non-initialize POST", async () => {
123
+ const res = await fetch(`${base()}/`, {
124
+ method: "POST",
125
+ headers: { authorization: "Bearer test-token", "content-type": "application/json" },
126
+ body: JSON.stringify({ jsonrpc: "2.0", id: 1, method: "tools/list" }),
127
+ });
128
+ expect(res.status).toBe(400);
129
+ expect(await res.text()).toContain("an initialize request is required");
130
+ });
131
+
132
+ it("rejects a sessionless GET (no Mcp-Session-Id)", async () => {
133
+ const res = await fetch(`${base()}/`, {
134
+ method: "GET",
135
+ headers: { authorization: "Bearer test-token" },
136
+ });
137
+ expect(res.status).toBe(400);
138
+ expect(await res.text()).toContain("missing Mcp-Session-Id header");
139
+ });
140
+ });
package/src/index.ts ADDED
@@ -0,0 +1,66 @@
1
+ // Public, embeddable surface for the Stigmer MCP server.
2
+ //
3
+ // Mirrors Go pkg/mcpserver: a minimal API — load a Config from the environment,
4
+ // then run() the server until the provided AbortSignal fires. The Stigmer CLI
5
+ // (and any other host) embeds the server through this module.
6
+
7
+ import { loadConfigFromEnv, validateConfig, type Config } from "./config.js";
8
+ import { configureLogger, log } from "./logger.js";
9
+ import { createServer, serveBoth, serveHttp, serveStdio, isNormalShutdown } from "./server.js";
10
+ import type { BackendTarget } from "./domains/client.js";
11
+
12
+ export type { Config, OAuthConfig, Transport } from "./config.js";
13
+ export { loadConfigFromEnv, validateConfig } from "./config.js";
14
+ export { createServer, SERVER_VERSION } from "./server.js";
15
+
16
+ /** Returns a Config populated from environment variables (no validation). */
17
+ export function defaultConfig(): Config {
18
+ return loadConfigFromEnv();
19
+ }
20
+
21
+ /**
22
+ * Start the MCP server with the given configuration and run until `signal`
23
+ * aborts or a fatal error occurs. The caller owns signal handling:
24
+ *
25
+ * ```ts
26
+ * const ac = new AbortController();
27
+ * process.on("SIGINT", () => ac.abort());
28
+ * await run(defaultConfig(), ac.signal);
29
+ * ```
30
+ *
31
+ * A clean client disconnect resolves normally (see {@link isNormalShutdown}).
32
+ */
33
+ export async function run(cfg: Config, signal: AbortSignal): Promise<void> {
34
+ configureLogger({ level: cfg.logLevel, format: cfg.logFormat });
35
+ validateConfig(cfg);
36
+
37
+ log.info("mcp-server-stigmer starting", { transport: cfg.transport });
38
+
39
+ const target: BackendTarget = {
40
+ serverAddress: cfg.stigmerServerAddress,
41
+ apiKey: cfg.apiKey,
42
+ };
43
+
44
+ try {
45
+ switch (cfg.transport) {
46
+ case "stdio":
47
+ await serveStdio(createServer(target), signal);
48
+ break;
49
+ case "http":
50
+ await serveHttp(() => createServer(target), cfg, signal);
51
+ break;
52
+ case "both":
53
+ await serveBoth(target, cfg, signal);
54
+ break;
55
+ }
56
+ } catch (err) {
57
+ if (!isNormalShutdown(err)) {
58
+ log.error("mcp-server-stigmer stopped", {
59
+ error: err instanceof Error ? err.message : String(err),
60
+ });
61
+ throw err;
62
+ }
63
+ }
64
+
65
+ log.info("mcp-server-stigmer stopped");
66
+ }
package/src/logger.ts ADDED
@@ -0,0 +1,49 @@
1
+ // Process-wide structured logger for mcp-server-stigmer.
2
+ //
3
+ // All output goes to stderr: in stdio transport mode, stdout is reserved for
4
+ // the MCP JSON-RPC stream, so any stray stdout write would corrupt the
5
+ // protocol. This mirrors the Go server's `initLogger` (slog → stderr).
6
+
7
+ export type LogLevel = "debug" | "info" | "warn" | "error";
8
+ export type LogFormat = "text" | "json";
9
+
10
+ const LEVEL_ORDER: Record<LogLevel, number> = { debug: 0, info: 1, warn: 2, error: 3 };
11
+
12
+ let minLevel: LogLevel = "info";
13
+ let format: LogFormat = "text";
14
+
15
+ /**
16
+ * Configure the default logger. Called once at startup from the resolved config.
17
+ *
18
+ * Tolerant of invalid inputs (falls back to info/text) so that configuration
19
+ * validation can still emit a precise error about the bad value through a
20
+ * working logger, rather than the logger itself crashing first.
21
+ */
22
+ export function configureLogger(opts: { level: string; format: string }): void {
23
+ minLevel = opts.level in LEVEL_ORDER ? (opts.level as LogLevel) : "info";
24
+ format = opts.format === "json" ? "json" : "text";
25
+ }
26
+
27
+ function emit(level: LogLevel, msg: string, fields?: Record<string, unknown>): void {
28
+ if (LEVEL_ORDER[level] < LEVEL_ORDER[minLevel]) return;
29
+
30
+ if (format === "json") {
31
+ process.stderr.write(JSON.stringify({ level, msg, ...fields }) + "\n");
32
+ return;
33
+ }
34
+
35
+ const suffix = fields
36
+ ? " " +
37
+ Object.entries(fields)
38
+ .map(([k, v]) => `${k}=${typeof v === "string" ? v : JSON.stringify(v)}`)
39
+ .join(" ")
40
+ : "";
41
+ process.stderr.write(`${level.toUpperCase()} ${msg}${suffix}\n`);
42
+ }
43
+
44
+ export const log = {
45
+ debug: (msg: string, fields?: Record<string, unknown>) => emit("debug", msg, fields),
46
+ info: (msg: string, fields?: Record<string, unknown>) => emit("info", msg, fields),
47
+ warn: (msg: string, fields?: Record<string, unknown>) => emit("warn", msg, fields),
48
+ error: (msg: string, fields?: Record<string, unknown>) => emit("error", msg, fields),
49
+ };
@@ -0,0 +1,82 @@
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
+ });