@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,30 @@
1
+ // Agent apply path: create-or-update via the AgentCommandController.apply RPC.
2
+ // The flat MCP input is projected into a fully-formed Agent proto by the
3
+ // generated agentInputToProto bridge (codegen, src/gen/agent.ts) before the call.
4
+ // Go parity: mcp-server/internal/domains/agents/apply.go.
5
+
6
+ import { AgentSchema } from "@stigmer/protos/ai/stigmer/agentic/agent/v1/api_pb";
7
+ import { AgentCommandController } from "@stigmer/protos/ai/stigmer/agentic/agent/v1/command_pb";
8
+
9
+ import { agentInputToProto, type AgentInput } from "../../gen/agent.js";
10
+ import { withClient } from "../client.js";
11
+ import { toProtoJson } from "../marshal.js";
12
+ import { rpcError } from "../rpcerr.js";
13
+
14
+ /** Create or update an agent, returning the persisted agent as protojson. */
15
+ export async function applyAgent(
16
+ serverAddress: string,
17
+ token: string,
18
+ input: AgentInput,
19
+ ): Promise<string> {
20
+ const agent = agentInputToProto(input);
21
+ const desc = `agent "${agent.metadata?.slug ?? ""}" in org "${agent.metadata?.org ?? ""}"`;
22
+ return withClient(AgentCommandController, serverAddress, token, async (client, callOptions) => {
23
+ try {
24
+ const result = await client.apply(agent, callOptions);
25
+ return toProtoJson(AgentSchema, result);
26
+ } catch (err) {
27
+ throw rpcError(err, desc);
28
+ }
29
+ });
30
+ }
@@ -0,0 +1,41 @@
1
+ // Agent delete path: resolve org/slug → id via the Query controller, then
2
+ // delete via the Command controller, both over a single shared transport.
3
+ // Go parity: mcp-server/internal/domains/agents/delete.go.
4
+
5
+ import { createClient } from "@connectrpc/connect";
6
+ import { AgentSchema } from "@stigmer/protos/ai/stigmer/agentic/agent/v1/api_pb";
7
+ import { AgentCommandController } from "@stigmer/protos/ai/stigmer/agentic/agent/v1/command_pb";
8
+ import { AgentQueryController } from "@stigmer/protos/ai/stigmer/agentic/agent/v1/query_pb";
9
+ import { ApiResourceKind } from "@stigmer/protos/ai/stigmer/commons/apiresource/apiresourcekind/api_resource_kind_pb";
10
+
11
+ import { withTransport } from "../client.js";
12
+ import { toProtoJson } from "../marshal.js";
13
+ import { rpcError } from "../rpcerr.js";
14
+
15
+ /** Delete an agent by org and slug, returning the deleted agent as protojson. */
16
+ export async function deleteAgent(
17
+ serverAddress: string,
18
+ token: string,
19
+ org: string,
20
+ slug: string,
21
+ ): Promise<string> {
22
+ const desc = `agent "${slug}" in org "${org}"`;
23
+ return withTransport(serverAddress, token, async (transport, callOptions) => {
24
+ const query = createClient(AgentQueryController, transport);
25
+ let id: string;
26
+ try {
27
+ const agent = await query.getByReference({ org, kind: ApiResourceKind.agent, slug }, callOptions);
28
+ id = agent.metadata?.id ?? "";
29
+ } catch (err) {
30
+ throw rpcError(err, desc);
31
+ }
32
+
33
+ const command = createClient(AgentCommandController, transport);
34
+ try {
35
+ const deleted = await command.delete({ value: id }, callOptions);
36
+ return toProtoJson(AgentSchema, deleted);
37
+ } catch (err) {
38
+ throw rpcError(err, desc);
39
+ }
40
+ });
41
+ }
@@ -0,0 +1,33 @@
1
+ // Agent read path: the single RPC both the get_agent tool and the agent
2
+ // resource template delegate to. Mirrors Go internal/domains/agents/fetch.go.
3
+
4
+ import { AgentSchema } from "@stigmer/protos/ai/stigmer/agentic/agent/v1/api_pb";
5
+ import { AgentQueryController } from "@stigmer/protos/ai/stigmer/agentic/agent/v1/query_pb";
6
+ import { ApiResourceKind } from "@stigmer/protos/ai/stigmer/commons/apiresource/apiresourcekind/api_resource_kind_pb";
7
+
8
+ import { withClient } from "../client.js";
9
+ import { toProtoJson } from "../marshal.js";
10
+ import { rpcError } from "../rpcerr.js";
11
+
12
+ /**
13
+ * Retrieve an agent by org and slug, returning its protojson representation.
14
+ * Errors are classified into user-facing messages via {@link rpcError}.
15
+ */
16
+ export async function fetchAgent(
17
+ serverAddress: string,
18
+ token: string,
19
+ org: string,
20
+ slug: string,
21
+ ): Promise<string> {
22
+ return withClient(AgentQueryController, serverAddress, token, async (client, callOptions) => {
23
+ try {
24
+ const agent = await client.getByReference(
25
+ { org, kind: ApiResourceKind.agent, slug },
26
+ callOptions,
27
+ );
28
+ return toProtoJson(AgentSchema, agent);
29
+ } catch (err) {
30
+ throw rpcError(err, `agent "${slug}" in org "${org}"`);
31
+ }
32
+ });
33
+ }
@@ -0,0 +1,20 @@
1
+ // Agent resource template (stigmer://agents/{org}/{slug}).
2
+ // Go parity: mcp-server/internal/domains/agents/resources.go.
3
+
4
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
5
+
6
+ import type { BackendTarget } from "../client.js";
7
+ import { registerResource } from "../resourcehandler.js";
8
+ import { fetchAgent } from "./fetch.js";
9
+
10
+ /** Register the agent resource template; returns the registered resource names. */
11
+ export function registerAgentResources(server: McpServer, target: BackendTarget): string[] {
12
+ registerResource(server, target, {
13
+ name: "stigmer_agent",
14
+ title: "Stigmer Agent",
15
+ description: "Full definition of a Stigmer agent, identified by organization and slug.",
16
+ template: "stigmer://agents/{org}/{slug}",
17
+ fetch: fetchAgent,
18
+ });
19
+ return ["stigmer_agent"];
20
+ }
@@ -0,0 +1,68 @@
1
+ // MCP tools for the Agent domain. Mirrors Go internal/domains/agents/tools.go.
2
+ //
3
+ // The tool name, description, and per-field input descriptions are part of the
4
+ // parity contract (MCP clients surface them to the model verbatim), so they are
5
+ // copied exactly from the Go definitions. This file is the canonical pattern the
6
+ // remaining domains follow in T02: define the tool, resolve the per-request
7
+ // credential, delegate to the domain fetch, and shape the result.
8
+
9
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
10
+ import { z } from "zod";
11
+
12
+ import { AgentInputShape } from "../../gen/agent.js";
13
+ import { resolveToken, type BackendTarget } from "../client.js";
14
+ import { textOrError } from "../toolresult.js";
15
+ import { applyAgent } from "./apply.js";
16
+ import { deleteAgent } from "./delete.js";
17
+ import { fetchAgent } from "./fetch.js";
18
+
19
+ /** Register every Agent-domain tool; returns the registered tool names. */
20
+ export function registerAgentTools(server: McpServer, target: BackendTarget): string[] {
21
+ server.registerTool(
22
+ "get_agent",
23
+ {
24
+ description:
25
+ "Get full details of a Stigmer agent by its org and slug (e.g. org=stigmer slug=code-reviewer).",
26
+ inputSchema: {
27
+ org: z.string().describe("Organization slug that owns the agent (e.g. stigmer)."),
28
+ slug: z
29
+ .string()
30
+ .describe("Agent slug — the unique identifier within the org (e.g. code-reviewer)."),
31
+ },
32
+ },
33
+ (args, extra) =>
34
+ textOrError(() =>
35
+ fetchAgent(target.serverAddress, resolveToken(extra, target.apiKey), args.org, args.slug),
36
+ ),
37
+ );
38
+
39
+ server.registerTool(
40
+ "apply_agent",
41
+ {
42
+ description:
43
+ "Create or update a Stigmer agent (idempotent). Provide identity fields (name, org) and agent configuration (instructions, skills, MCP servers, etc.).",
44
+ inputSchema: AgentInputShape,
45
+ },
46
+ (args, extra) =>
47
+ textOrError(() => applyAgent(target.serverAddress, resolveToken(extra, target.apiKey), args)),
48
+ );
49
+
50
+ server.registerTool(
51
+ "delete_agent",
52
+ {
53
+ description: "Delete a Stigmer agent by its org and slug. Returns the deleted agent.",
54
+ inputSchema: {
55
+ org: z.string().describe("Organization slug that owns the agent (e.g. stigmer)."),
56
+ slug: z
57
+ .string()
58
+ .describe("Agent slug — the unique identifier within the org (e.g. code-reviewer)."),
59
+ },
60
+ },
61
+ (args, extra) =>
62
+ textOrError(() =>
63
+ deleteAgent(target.serverAddress, resolveToken(extra, target.apiKey), args.org, args.slug),
64
+ ),
65
+ );
66
+
67
+ return ["get_agent", "apply_agent", "delete_agent"];
68
+ }
@@ -0,0 +1,220 @@
1
+ // In-process integration test for the apply tools. Drives apply_agent,
2
+ // apply_mcp_server, and apply_workflow through the full MCP boundary and asserts
3
+ // the codegen projection (src/gen/*) reconstitutes the proto correctly: metadata
4
+ // hoist + slug generation, enum-string conversion, ApiResourceReference kind
5
+ // injection, the stdio/http oneof, and the recursive workflow task_config
6
+ // expansion (http_call leaf, fork/for_each nesting).
7
+
8
+ import type { ConnectRouter } from "@connectrpc/connect";
9
+ import { connectNodeAdapter } from "@connectrpc/connect-node";
10
+ import {
11
+ createServer as createHttp2Server,
12
+ type Http2Server,
13
+ type ServerHttp2Session,
14
+ } from "node:http2";
15
+ import type { AddressInfo } from "node:net";
16
+
17
+ import { Client } from "@modelcontextprotocol/sdk/client/index.js";
18
+ import { InMemoryTransport } from "@modelcontextprotocol/sdk/inMemory.js";
19
+ import type { Agent } from "@stigmer/protos/ai/stigmer/agentic/agent/v1/api_pb";
20
+ import { AgentCommandController } from "@stigmer/protos/ai/stigmer/agentic/agent/v1/command_pb";
21
+ import type { McpServer as McpServerProto } from "@stigmer/protos/ai/stigmer/agentic/mcpserver/v1/api_pb";
22
+ import { McpServerCommandController } from "@stigmer/protos/ai/stigmer/agentic/mcpserver/v1/command_pb";
23
+ import type { Workflow } from "@stigmer/protos/ai/stigmer/agentic/workflow/v1/api_pb";
24
+ import { WorkflowCommandController } from "@stigmer/protos/ai/stigmer/agentic/workflow/v1/command_pb";
25
+ import { WorkflowTaskKind } from "@stigmer/protos/ai/stigmer/agentic/workflow/v1/enum_pb";
26
+ import { ApiResourceKind } from "@stigmer/protos/ai/stigmer/commons/apiresource/apiresourcekind/api_resource_kind_pb";
27
+ import { ApiResourceVisibility } from "@stigmer/protos/ai/stigmer/commons/apiresource/enum_pb";
28
+ import { afterAll, beforeAll, describe, expect, it } from "vitest";
29
+
30
+ import { configureLogger } from "../logger";
31
+ import { createServer } from "../server";
32
+
33
+ configureLogger({ level: "error", format: "text" });
34
+
35
+ let backend: Http2Server;
36
+ let client: Client;
37
+ let appliedAgent: Agent | undefined;
38
+ let appliedMcpServer: McpServerProto | undefined;
39
+ let appliedWorkflow: Workflow | undefined;
40
+ const openSessions = new Set<ServerHttp2Session>();
41
+
42
+ interface ToolResult {
43
+ content: Array<{ type: string; text?: string }>;
44
+ isError?: boolean;
45
+ }
46
+
47
+ async function callTool(name: string, args: Record<string, unknown>): Promise<ToolResult> {
48
+ return (await client.callTool({ name, arguments: args })) as ToolResult;
49
+ }
50
+
51
+ beforeAll(async () => {
52
+ const routes = (router: ConnectRouter) => {
53
+ router.service(AgentCommandController, {
54
+ apply: (req) => {
55
+ appliedAgent = req;
56
+ return req;
57
+ },
58
+ });
59
+ router.service(McpServerCommandController, {
60
+ apply: (req) => {
61
+ appliedMcpServer = req;
62
+ return req;
63
+ },
64
+ });
65
+ router.service(WorkflowCommandController, {
66
+ apply: (req) => {
67
+ appliedWorkflow = req;
68
+ return req;
69
+ },
70
+ });
71
+ };
72
+ backend = createHttp2Server(connectNodeAdapter({ routes }));
73
+ backend.on("session", (session) => {
74
+ openSessions.add(session);
75
+ session.on("close", () => openSessions.delete(session));
76
+ });
77
+ await new Promise<void>((resolve) => backend.listen(0, "127.0.0.1", resolve));
78
+ const port = (backend.address() as AddressInfo).port;
79
+
80
+ const mcp = createServer({ serverAddress: `127.0.0.1:${port}`, apiKey: "" });
81
+ const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
82
+ client = new Client({ name: "apply-integration", version: "test" });
83
+ await Promise.all([mcp.connect(serverTransport), client.connect(clientTransport)]);
84
+ });
85
+
86
+ afterAll(async () => {
87
+ await client?.close();
88
+ for (const session of openSessions) session.destroy();
89
+ await new Promise<void>((resolve) => backend.close(() => resolve()));
90
+ });
91
+
92
+ describe("apply tools integration", () => {
93
+ it("advertises every apply tool", async () => {
94
+ const { tools } = await client.listTools();
95
+ expect(tools.map((t) => t.name)).toEqual(
96
+ expect.arrayContaining(["apply_agent", "apply_mcp_server", "apply_workflow"]),
97
+ );
98
+ });
99
+
100
+ it("apply_agent hoists metadata, generates slug, and injects reference kinds", async () => {
101
+ const result = await callTool("apply_agent", {
102
+ name: "Code Reviewer",
103
+ org: "acme",
104
+ visibility: "PUBLIC",
105
+ instructions: "Review code carefully.",
106
+ skill_refs: [{ slug: "web-search", version: "stable" }],
107
+ mcp_server_usages: [{ mcp_server_ref: { slug: "github" }, enabled_tools: ["create_pr"] }],
108
+ });
109
+ expect(result.isError).toBeFalsy();
110
+
111
+ const agent = appliedAgent;
112
+ expect(agent?.apiVersion).toBe("agentic.stigmer.ai/v1");
113
+ expect(agent?.kind).toBe("Agent");
114
+ expect(agent?.metadata?.name).toBe("Code Reviewer");
115
+ expect(agent?.metadata?.slug).toBe("code-reviewer"); // auto-generated from name
116
+ expect(agent?.metadata?.org).toBe("acme");
117
+ expect(agent?.metadata?.visibility).toBe(ApiResourceVisibility.visibility_public);
118
+ expect(agent?.spec?.instructions).toBe("Review code carefully.");
119
+
120
+ const skillRef = agent?.spec?.skillRefs?.[0];
121
+ expect(skillRef?.slug).toBe("web-search");
122
+ expect(skillRef?.kind).toBe(ApiResourceKind.skill);
123
+ expect(skillRef?.version).toBe("stable"); // version kept for versioned kind
124
+
125
+ const usage = agent?.spec?.mcpServerUsages?.[0];
126
+ expect(usage?.mcpServerRef?.slug).toBe("github");
127
+ expect(usage?.mcpServerRef?.kind).toBe(ApiResourceKind.mcp_server);
128
+ expect(usage?.enabledTools).toEqual(["create_pr"]);
129
+ });
130
+
131
+ it("apply_mcp_server rebuilds the stdio oneof", async () => {
132
+ const result = await callTool("apply_mcp_server", {
133
+ name: "GitHub",
134
+ org: "acme",
135
+ stdio: { command: "npx", args: ["-y", "@modelcontextprotocol/server-github"] },
136
+ });
137
+ expect(result.isError).toBeFalsy();
138
+ expect(appliedMcpServer?.spec?.serverType?.case).toBe("stdio");
139
+ expect(appliedMcpServer?.spec?.serverType?.value).toMatchObject({ command: "npx" });
140
+ });
141
+
142
+ it("apply_mcp_server rebuilds the http oneof", async () => {
143
+ const result = await callTool("apply_mcp_server", {
144
+ name: "Remote",
145
+ org: "acme",
146
+ http: { url: "https://mcp.example.com/v1" },
147
+ });
148
+ expect(result.isError).toBeFalsy();
149
+ expect(appliedMcpServer?.spec?.serverType?.case).toBe("http");
150
+ expect(appliedMcpServer?.spec?.serverType?.value).toMatchObject({
151
+ url: "https://mcp.example.com/v1",
152
+ });
153
+ });
154
+
155
+ it("apply_workflow expands typed task_config and nests recursive tasks", async () => {
156
+ const result = await callTool("apply_workflow", {
157
+ name: "Triage",
158
+ org: "acme",
159
+ document: { namespace: "support", name: "triage", version: "1.0.0" },
160
+ tasks: [
161
+ {
162
+ name: "fetch",
163
+ kind: "http_call",
164
+ http_call: { method: "POST", endpoint: { uri: "https://api.example.com" } },
165
+ },
166
+ {
167
+ name: "parallel",
168
+ kind: "fork",
169
+ fork: {
170
+ branches: [
171
+ {
172
+ name: "b1",
173
+ do: [{ name: "set_x", kind: "set_vars", set_vars: { variables: { x: "1" } } }],
174
+ },
175
+ ],
176
+ },
177
+ },
178
+ {
179
+ name: "loop",
180
+ kind: "for_each",
181
+ for_each: {
182
+ each: "item",
183
+ in: "${ $data.items }",
184
+ do: [
185
+ {
186
+ name: "call",
187
+ kind: "http_call",
188
+ http_call: { method: "GET", endpoint: { uri: "https://api.example.com/item" } },
189
+ },
190
+ ],
191
+ },
192
+ },
193
+ ],
194
+ });
195
+ expect(result.isError).toBeFalsy();
196
+
197
+ const wf = appliedWorkflow;
198
+ expect(wf?.apiVersion).toBe("agentic.stigmer.ai/v1");
199
+ expect(wf?.metadata?.slug).toBe("triage");
200
+ expect(wf?.spec?.document?.namespace).toBe("support");
201
+
202
+ const tasks = wf?.spec?.tasks ?? [];
203
+ expect(tasks).toHaveLength(3);
204
+
205
+ // http_call leaf: task_config Struct carries the serialized config.
206
+ expect(tasks[0]?.kind).toBe(WorkflowTaskKind.http_call);
207
+ expect(tasks[0]?.taskConfig).toMatchObject({ method: "POST" });
208
+
209
+ // fork: recursive nesting — branches[].do[] are full tasks.
210
+ expect(tasks[1]?.kind).toBe(WorkflowTaskKind.fork);
211
+ const forkCfg = tasks[1]?.taskConfig as { branches?: Array<{ do?: unknown[] }> };
212
+ expect(forkCfg.branches?.[0]?.do).toHaveLength(1);
213
+
214
+ // for_each: recursive nesting via z.lazy.
215
+ expect(tasks[2]?.kind).toBe(WorkflowTaskKind.for_each);
216
+ const forCfg = tasks[2]?.taskConfig as { each?: string; do?: unknown[] };
217
+ expect(forCfg.each).toBe("item");
218
+ expect(forCfg.do).toHaveLength(1);
219
+ });
220
+ });
@@ -0,0 +1,95 @@
1
+ // Per-request gRPC client construction for tool handlers.
2
+ //
3
+ // Parity contract (mirrors Go internal/domains/conn.go WithConnection + the
4
+ // auth/grpc layer): each tool call resolves its credential, opens a short-lived
5
+ // authenticated client to stigmer-server with a fixed RPC timeout, runs the
6
+ // call, and discards the client. This is intentionally simple — the MCP server
7
+ // handles a low volume of calls and transport setup is cheap. Pooling can be
8
+ // introduced later without touching call sites.
9
+ //
10
+ // Credential source mirrors Go precisely:
11
+ // - stdio: the API key is captured once at startup (constant per process).
12
+ // - http: every request carries its own Bearer token via authInfo, which the
13
+ // transport injected from the Authorization header.
14
+
15
+ import { createClient, type Client, type CallOptions, type Transport } from "@connectrpc/connect";
16
+ import type { DescService } from "@bufbuild/protobuf";
17
+ import { createNodeTransport, normalizeEndpoint } from "@stigmer/sdk/node";
18
+
19
+ /**
20
+ * Per-call RPC timeout. Mirrors Go's DefaultRPCTimeout: generous for both
21
+ * localhost (milliseconds) and remote endpoints (low seconds), while still
22
+ * failing fast against a misconfigured or unreachable address.
23
+ */
24
+ export const DEFAULT_RPC_TIMEOUT_MS = 30_000;
25
+
26
+ /**
27
+ * The stigmer-server endpoint and startup credential captured at registration
28
+ * time and shared by every tool handler. Mirrors the `serverAddress` closure +
29
+ * startup API key that the Go handlers capture.
30
+ */
31
+ export interface BackendTarget {
32
+ readonly serverAddress: string;
33
+ /** stdio startup API key; "" when targeting an unauthenticated backend. */
34
+ readonly apiKey: string;
35
+ }
36
+
37
+ /** The subset of the MCP request `extra` this layer reads to resolve a token. */
38
+ export interface RequestAuth {
39
+ readonly authInfo?: { readonly token?: string };
40
+ }
41
+
42
+ /**
43
+ * Resolve the credential for an inbound tool call: the per-request Bearer token
44
+ * (http) when present, otherwise the startup API key (stdio). Returns "" when
45
+ * targeting an unauthenticated backend, in which case no credential is attached.
46
+ */
47
+ export function resolveToken(extra: RequestAuth | undefined, fallback: string): string {
48
+ const token = extra?.authInfo?.token;
49
+ return token !== undefined && token !== "" ? token : fallback;
50
+ }
51
+
52
+ /**
53
+ * Build an authenticated transport to stigmer-server for a single credential.
54
+ * The address is normalized (scheme/TLS rules) before the transport is built;
55
+ * an empty token attaches no Authorization header.
56
+ */
57
+ export function transportForToken(serverAddress: string, token: string): Transport {
58
+ return createNodeTransport({
59
+ baseUrl: normalizeEndpoint(serverAddress),
60
+ apiKey: token === "" ? undefined : token,
61
+ });
62
+ }
63
+
64
+ /**
65
+ * Open a short-lived authenticated transport and invoke `fn` with it and the
66
+ * standard call options (RPC timeout pre-applied). Mirrors Go's WithConnection:
67
+ * the single place that owns transport construction and the timeout. Use this
68
+ * when an operation needs more than one controller over the SAME connection —
69
+ * e.g. the two-step deletes that resolve via the Query controller and delete via
70
+ * the Command controller.
71
+ */
72
+ export async function withTransport<T>(
73
+ serverAddress: string,
74
+ token: string,
75
+ fn: (transport: Transport, callOptions: CallOptions) => Promise<T>,
76
+ ): Promise<T> {
77
+ return fn(transportForToken(serverAddress, token), { timeoutMs: DEFAULT_RPC_TIMEOUT_MS });
78
+ }
79
+
80
+ /**
81
+ * Open a short-lived raw controller client and invoke `fn` with it and the
82
+ * standard call options. The single-controller convenience over
83
+ * {@link withTransport}; call sites that only touch one service stay a
84
+ * one-liner.
85
+ */
86
+ export async function withClient<S extends DescService, T>(
87
+ service: S,
88
+ serverAddress: string,
89
+ token: string,
90
+ fn: (client: Client<S>, callOptions: CallOptions) => Promise<T>,
91
+ ): Promise<T> {
92
+ return withTransport(serverAddress, token, (transport, callOptions) =>
93
+ fn(createClient(service, transport), callOptions),
94
+ );
95
+ }
@@ -0,0 +1,124 @@
1
+ // In-process integration test for the delete tools. Verifies the two-step
2
+ // resolve→delete flow forwards the resolved id into the correct per-domain
3
+ // delete-input shape: typed {value} for agent/skill/workflow, and the
4
+ // ApiResourceDeleteInput {resource_id} outlier for mcp_server.
5
+
6
+ import { create, toJson } from "@bufbuild/protobuf";
7
+ import type { ConnectRouter } from "@connectrpc/connect";
8
+ import { connectNodeAdapter } from "@connectrpc/connect-node";
9
+ import {
10
+ createServer as createHttp2Server,
11
+ type Http2Server,
12
+ type ServerHttp2Session,
13
+ } from "node:http2";
14
+ import type { AddressInfo } from "node:net";
15
+
16
+ import { Client } from "@modelcontextprotocol/sdk/client/index.js";
17
+ import { InMemoryTransport } from "@modelcontextprotocol/sdk/inMemory.js";
18
+ import { AgentSchema } from "@stigmer/protos/ai/stigmer/agentic/agent/v1/api_pb";
19
+ import { AgentCommandController } from "@stigmer/protos/ai/stigmer/agentic/agent/v1/command_pb";
20
+ import { AgentQueryController } from "@stigmer/protos/ai/stigmer/agentic/agent/v1/query_pb";
21
+ import { McpServerSchema } from "@stigmer/protos/ai/stigmer/agentic/mcpserver/v1/api_pb";
22
+ import { McpServerCommandController } from "@stigmer/protos/ai/stigmer/agentic/mcpserver/v1/command_pb";
23
+ import { McpServerQueryController } from "@stigmer/protos/ai/stigmer/agentic/mcpserver/v1/query_pb";
24
+ import { afterAll, beforeAll, describe, expect, it } from "vitest";
25
+
26
+ import { configureLogger } from "../logger";
27
+ import { createServer } from "../server";
28
+
29
+ configureLogger({ level: "error", format: "text" });
30
+
31
+ const resolvedAgent = create(AgentSchema, {
32
+ apiVersion: "v1",
33
+ kind: "agent",
34
+ metadata: { name: "Code Reviewer", slug: "code-reviewer", org: "acme", id: "agt-123" },
35
+ });
36
+ const resolvedMcpServer = create(McpServerSchema, {
37
+ apiVersion: "v1",
38
+ kind: "mcp_server",
39
+ metadata: { name: "GitHub", slug: "github", org: "acme", id: "mcp-456" },
40
+ });
41
+
42
+ let backend: Http2Server;
43
+ let client: Client;
44
+ let deletedAgentId: string | undefined;
45
+ let deletedMcpResourceId: string | undefined;
46
+ const openSessions = new Set<ServerHttp2Session>();
47
+
48
+ interface ToolResult {
49
+ content: Array<{ type: string; text?: string }>;
50
+ isError?: boolean;
51
+ }
52
+
53
+ async function callTool(name: string, args: Record<string, unknown>): Promise<ToolResult> {
54
+ return (await client.callTool({ name, arguments: args })) as ToolResult;
55
+ }
56
+
57
+ beforeAll(async () => {
58
+ const routes = (router: ConnectRouter) => {
59
+ router.service(AgentQueryController, { getByReference: () => resolvedAgent });
60
+ router.service(AgentCommandController, {
61
+ delete: (req) => {
62
+ deletedAgentId = req.value;
63
+ return resolvedAgent;
64
+ },
65
+ });
66
+ router.service(McpServerQueryController, { getByReference: () => resolvedMcpServer });
67
+ router.service(McpServerCommandController, {
68
+ delete: (req) => {
69
+ deletedMcpResourceId = req.resourceId;
70
+ return resolvedMcpServer;
71
+ },
72
+ });
73
+ };
74
+ backend = createHttp2Server(connectNodeAdapter({ routes }));
75
+ backend.on("session", (session) => {
76
+ openSessions.add(session);
77
+ session.on("close", () => openSessions.delete(session));
78
+ });
79
+ await new Promise<void>((resolve) => backend.listen(0, "127.0.0.1", resolve));
80
+ const port = (backend.address() as AddressInfo).port;
81
+
82
+ const mcp = createServer({ serverAddress: `127.0.0.1:${port}`, apiKey: "" });
83
+ const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
84
+ client = new Client({ name: "deletes-integration", version: "test" });
85
+ await Promise.all([mcp.connect(serverTransport), client.connect(clientTransport)]);
86
+ });
87
+
88
+ afterAll(async () => {
89
+ await client?.close();
90
+ for (const session of openSessions) session.destroy();
91
+ await new Promise<void>((resolve) => backend.close(() => resolve()));
92
+ });
93
+
94
+ describe("delete tools integration", () => {
95
+ it("advertises every delete tool", async () => {
96
+ const { tools } = await client.listTools();
97
+ expect(tools.map((t) => t.name)).toEqual(
98
+ expect.arrayContaining([
99
+ "delete_agent",
100
+ "delete_skill",
101
+ "delete_workflow",
102
+ "delete_mcp_server",
103
+ ]),
104
+ );
105
+ });
106
+
107
+ it("delete_agent resolves the id then deletes via the typed AgentId", async () => {
108
+ const result = await callTool("delete_agent", { org: "acme", slug: "code-reviewer" });
109
+ expect(result.isError).toBeFalsy();
110
+ expect(deletedAgentId).toBe("agt-123");
111
+ expect(JSON.parse(result.content[0]?.text ?? "{}")).toEqual(
112
+ toJson(AgentSchema, resolvedAgent, { useProtoFieldName: true }),
113
+ );
114
+ });
115
+
116
+ it("delete_mcp_server resolves the id then deletes via ApiResourceDeleteInput", async () => {
117
+ const result = await callTool("delete_mcp_server", { org: "acme", slug: "github" });
118
+ expect(result.isError).toBeFalsy();
119
+ expect(deletedMcpResourceId).toBe("mcp-456");
120
+ expect(JSON.parse(result.content[0]?.text ?? "{}")).toEqual(
121
+ toJson(McpServerSchema, resolvedMcpServer, { useProtoFieldName: true }),
122
+ );
123
+ });
124
+ });
@@ -0,0 +1,21 @@
1
+ // Canonical protobuf → JSON serialization shared by every tool response.
2
+ //
3
+ // Parity contract (mirrors Go internal/domains/marshal.go): the Go server emits
4
+ // protojson with UseProtoNames=true and EmitUnpopulated=false, 2-space indented.
5
+ // protobuf-es `toJson` with { useProtoFieldName: true } reproduces this exactly —
6
+ // snake_case field names, omitted defaults, base64 bytes, string enums, and
7
+ // RFC-3339 timestamps — and JSON.stringify(..., 2) applies the same indentation.
8
+
9
+ import { toJson, type DescMessage, type MessageShape } from "@bufbuild/protobuf";
10
+
11
+ /**
12
+ * Serialize a protobuf message to the human-friendly JSON string returned in
13
+ * MCP tool output. Field names use the proto (snake_case) names so the payload
14
+ * is byte-comparable, after parsing, with the Go server's protojson.
15
+ */
16
+ export function toProtoJson<Desc extends DescMessage>(
17
+ schema: Desc,
18
+ message: MessageShape<Desc>,
19
+ ): string {
20
+ return JSON.stringify(toJson(schema, message, { useProtoFieldName: true }), null, 2);
21
+ }