@stigmer/mcp-server 3.35.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 (155) hide show
  1. package/cli/mcp-server-stigmer.d.ts.map +1 -1
  2. package/cli/mcp-server-stigmer.js +1 -1
  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.map +1 -1
  126. package/index.d.ts.map +1 -1
  127. package/index.js.map +1 -1
  128. package/logger.d.ts.map +1 -1
  129. package/logger.js.map +1 -1
  130. package/package.json +3 -3
  131. package/readiness.d.ts.map +1 -1
  132. package/readiness.js.map +1 -1
  133. package/server.d.ts.map +1 -1
  134. package/server.js.map +1 -1
  135. package/src/config.test.ts +0 -88
  136. package/src/domains/apply.integration.test.ts +0 -313
  137. package/src/domains/channels/channels.integration.test.ts +0 -272
  138. package/src/domains/channels/errors.test.ts +0 -52
  139. package/src/domains/conversation/conversation.integration.test.ts +0 -233
  140. package/src/domains/deletes.integration.test.ts +0 -147
  141. package/src/domains/executions.integration.test.ts +0 -376
  142. package/src/domains/memory/context.test.ts +0 -95
  143. package/src/domains/memory/errors.test.ts +0 -54
  144. package/src/domains/memory/memory.integration.test.ts +0 -253
  145. package/src/domains/reads.integration.test.ts +0 -163
  146. package/src/domains/resources.integration.test.ts +0 -157
  147. package/src/domains/resourceuri.test.ts +0 -104
  148. package/src/domains/rpcerr.test.ts +0 -62
  149. package/src/domains/search/search.integration.test.ts +0 -141
  150. package/src/domains/versions.integration.test.ts +0 -212
  151. package/src/domains/workflows/validate.integration.test.ts +0 -117
  152. package/src/domains/workflows/workflow-tools.integration.test.ts +0 -148
  153. package/src/http.integration.test.ts +0 -235
  154. package/src/readiness.test.ts +0 -90
  155. package/src/server.integration.test.ts +0 -82
@@ -1,253 +0,0 @@
1
- // In-process integration test for the memory roster (the
2
- // channels.integration.test.ts pattern: real Connect backend with a
3
- // stubbed memory service, real MCP client over an in-memory transport).
4
- //
5
- // Verifies the DD-005 D1/D2 contract surface:
6
- // - the memory-only roster is exactly remember with ONLY a fact
7
- // argument (agent audience; org, subject, and provenance are never
8
- // the model's to supply);
9
- // - argument + capture-context → request mapping: fact → spec.content,
10
- // context org → metadata.org, context triple → spec.provenance,
11
- // subject never travels, tool_call_id stays empty (v1);
12
- // - the answer is the chip contract: { outcome, memory } with the
13
- // created record as verbatim proto JSON and the honest
14
- // proposed-not-remembered outcome line;
15
- // - the memory-own error mapper passes domain messages verbatim as
16
- // {error, code, reason} JSON; transport errors delegate to the
17
- // shared classifier.
18
-
19
- import { create } from "@bufbuild/protobuf";
20
- import { Code, ConnectError, type ConnectRouter } from "@connectrpc/connect";
21
- import { connectNodeAdapter } from "@connectrpc/connect-node";
22
- import {
23
- createServer as createHttp2Server,
24
- type Http2Server,
25
- type ServerHttp2Session,
26
- } from "node:http2";
27
- import type { AddressInfo } from "node:net";
28
-
29
- import { Client } from "@modelcontextprotocol/sdk/client/index.js";
30
- import { InMemoryTransport } from "@modelcontextprotocol/sdk/inMemory.js";
31
- import { MemorySchema, type Memory } from "@stigmer/protos/ai/stigmer/agentic/memory/v1/api_pb";
32
- import { MemoryCommandController } from "@stigmer/protos/ai/stigmer/agentic/memory/v1/command_pb";
33
- import { MemoryLifecycleState } from "@stigmer/protos/ai/stigmer/agentic/memory/v1/enum_pb";
34
- import { MemorySpecSchema } from "@stigmer/protos/ai/stigmer/agentic/memory/v1/spec_pb";
35
- import { MemoryStatusSchema } from "@stigmer/protos/ai/stigmer/agentic/memory/v1/status_pb";
36
- import { ApiResourceMetadataSchema } from "@stigmer/protos/ai/stigmer/commons/apiresource/metadata_pb";
37
- import { afterAll, beforeAll, describe, expect, it } from "vitest";
38
-
39
- import { configureLogger } from "../../logger";
40
- import { MEMORY_ROUTE, createMemoryServer } from "../../server";
41
- import { PROPOSED_OUTCOME } from "./calls";
42
- import {
43
- MEMORY_AGENT_ID_ENV,
44
- MEMORY_AGENT_ID_HEADER,
45
- MEMORY_EXECUTION_ID_ENV,
46
- MEMORY_EXECUTION_ID_HEADER,
47
- MEMORY_ORG_ENV,
48
- MEMORY_ORG_HEADER,
49
- MEMORY_SESSION_ID_ENV,
50
- MEMORY_SESSION_ID_HEADER,
51
- } from "./context";
52
-
53
- configureLogger({ level: "error", format: "text" });
54
-
55
- let backend: Http2Server;
56
- let client: Client;
57
- const openSessions = new Set<ServerHttp2Session>();
58
-
59
- /** The next stubbed created record; tests set this. */
60
- let createResponse: () => Memory;
61
-
62
- // Requests the stub captured. An array, deliberately: resetting an optional
63
- // property to undefined narrows its type to `undefined` for the rest of the
64
- // flow (tsc does not un-narrow across the intervening callTool call), which
65
- // makes every later `req?.x` a property access on `never` under
66
- // `npm run typecheck` — the job that, unlike tsconfig.build.json, includes
67
- // tests. Clearing and reading an array never narrows.
68
- const capturedCreates: Memory[] = [];
69
-
70
- interface ToolResult {
71
- content: Array<{ type: string; text?: string }>;
72
- isError?: boolean;
73
- }
74
-
75
- async function remember(args: Record<string, unknown>): Promise<ToolResult> {
76
- return (await client.callTool({ name: "remember", arguments: args })) as ToolResult;
77
- }
78
-
79
- /** A plausible created record echoing the request's content. */
80
- function proposedRecord(content: string): Memory {
81
- return create(MemorySchema, {
82
- apiVersion: "agentic.stigmer.ai/v1",
83
- kind: "Memory",
84
- metadata: create(ApiResourceMetadataSchema, { id: "mem_01test", org: "acme" }),
85
- spec: create(MemorySpecSchema, {
86
- content,
87
- subjectIdentityAccountId: "ida_subject",
88
- }),
89
- status: create(MemoryStatusSchema, {
90
- lifecycleState: MemoryLifecycleState.lifecycle_state_proposed,
91
- }),
92
- });
93
- }
94
-
95
- beforeAll(async () => {
96
- const routes = (router: ConnectRouter) => {
97
- router.service(MemoryCommandController, {
98
- create: (req) => {
99
- capturedCreates.push(req);
100
- return createResponse();
101
- },
102
- });
103
- };
104
- backend = createHttp2Server(connectNodeAdapter({ routes }));
105
- backend.on("session", (session) => {
106
- openSessions.add(session);
107
- session.on("close", () => openSessions.delete(session));
108
- });
109
- await new Promise<void>((resolve) => backend.listen(0, "127.0.0.1", resolve));
110
- const port = (backend.address() as AddressInfo).port;
111
-
112
- // The stdio shape: the startup capture context stands in for the
113
- // runner-set STIGMER_MEMORY_* environment.
114
- const mcp = createMemoryServer(
115
- { serverAddress: `127.0.0.1:${port}`, apiKey: "" },
116
- { org: "acme", agentId: "agt_1", sessionId: "ses_1", agentExecutionId: "aex_1" },
117
- );
118
- const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
119
- client = new Client({ name: "memory-integration", version: "test" });
120
- await Promise.all([mcp.connect(serverTransport), client.connect(clientTransport)]);
121
- });
122
-
123
- afterAll(async () => {
124
- await client?.close();
125
- for (const session of openSessions) session.destroy();
126
- await new Promise<void>((resolve) => backend.close(() => resolve()));
127
- });
128
-
129
- describe("memory roster (DD-005 D1)", () => {
130
- it("exposes exactly remember with only a fact argument", async () => {
131
- const { tools } = await client.listTools();
132
- expect(tools.map((t) => t.name)).toEqual(["remember"]);
133
-
134
- // The agent audience supplies the fact and NOTHING else — org,
135
- // subject, and provenance derive from the credential and the
136
- // runner-threaded capture context, so no argument exists to forge
137
- // them (the channels no-org rule, applied to attribution).
138
- const properties = (tools[0].inputSchema as { properties?: Record<string, unknown> })
139
- .properties;
140
- expect(Object.keys(properties ?? {})).toEqual(["fact"]);
141
- });
142
-
143
- it("pins the cross-repo attachment strings (the TOOL_CALL_LIMIT precedent)", () => {
144
- // The runner's memory-attachment.ts builds these independently; a
145
- // drift strands every synthesized attachment on a 404 (route) or an
146
- // attribution-less record (context keys).
147
- expect(MEMORY_ROUTE).toBe("/memory");
148
- expect(MEMORY_ORG_ENV).toBe("STIGMER_MEMORY_ORG");
149
- expect(MEMORY_AGENT_ID_ENV).toBe("STIGMER_MEMORY_AGENT_ID");
150
- expect(MEMORY_SESSION_ID_ENV).toBe("STIGMER_MEMORY_SESSION_ID");
151
- expect(MEMORY_EXECUTION_ID_ENV).toBe("STIGMER_MEMORY_EXECUTION_ID");
152
- expect(MEMORY_ORG_HEADER).toBe("x-stigmer-memory-org");
153
- expect(MEMORY_AGENT_ID_HEADER).toBe("x-stigmer-memory-agent-id");
154
- expect(MEMORY_SESSION_ID_HEADER).toBe("x-stigmer-memory-session-id");
155
- expect(MEMORY_EXECUTION_ID_HEADER).toBe("x-stigmer-memory-execution-id");
156
- });
157
- });
158
-
159
- describe("argument + capture context → request mapping (DD-005 D2)", () => {
160
- it("maps the fact and the startup context; subject never travels", async () => {
161
- createResponse = () => proposedRecord("Prefers concise answers.");
162
- capturedCreates.length = 0;
163
-
164
- const result = await remember({ fact: "Prefers concise answers." });
165
-
166
- expect(result.isError).toBeFalsy();
167
- const req = capturedCreates.at(-1);
168
- expect(req?.metadata?.org).toBe("acme");
169
- expect(req?.metadata?.name).toBe(""); // id-addressed; the server defaults the name
170
- expect(req?.spec?.content).toBe("Prefers concise answers.");
171
- // Subject is the server's to derive from the credential — the tool
172
- // never supplies one, so forgery is structurally impossible.
173
- expect(req?.spec?.subjectIdentityAccountId).toBe("");
174
- expect(req?.spec?.provenance?.agentId).toBe("agt_1");
175
- expect(req?.spec?.provenance?.sessionId).toBe("ses_1");
176
- expect(req?.spec?.provenance?.agentExecutionId).toBe("aex_1");
177
- // v1: MCP does not carry the harness's tool-call identity.
178
- expect(req?.spec?.provenance?.toolCallId).toBe("");
179
- });
180
-
181
- it("answers with the chip contract: outcome line + created record as proto JSON", async () => {
182
- createResponse = () => proposedRecord("Works primarily in Go.");
183
-
184
- const result = await remember({ fact: "Works primarily in Go." });
185
-
186
- expect(result.isError).toBeFalsy();
187
- const body = JSON.parse(result.content[0]?.text ?? "{}") as {
188
- outcome?: string;
189
- memory?: Record<string, unknown>;
190
- };
191
- // The honest relay (DD-005 D2): proposed, the user decides.
192
- expect(body.outcome).toBe(PROPOSED_OUTCOME);
193
- // The record rides verbatim with proto (snake_case) field names —
194
- // what the SDK's normalizeToolResult parses to render the chip.
195
- const memory = body.memory as {
196
- metadata?: { id?: string };
197
- spec?: { content?: string };
198
- status?: { lifecycle_state?: string };
199
- };
200
- expect(memory?.metadata?.id).toBe("mem_01test");
201
- expect(memory?.spec?.content).toBe("Works primarily in Go.");
202
- expect(memory?.status?.lifecycle_state).toBe("lifecycle_state_proposed");
203
- });
204
- });
205
-
206
- describe("memory error mapper", () => {
207
- it("passes the visible-full refusal through verbatim — never the shared rewrite", async () => {
208
- createResponse = () => {
209
- throw new ConnectError(
210
- "memory is full — review and delete existing memories",
211
- Code.FailedPrecondition,
212
- );
213
- };
214
-
215
- const result = await remember({ fact: "One fact too many." });
216
-
217
- expect(result.isError).toBe(true);
218
- expect(JSON.parse(result.content[0]?.text ?? "{}")).toEqual({
219
- error: "memory is full — review and delete existing memories",
220
- code: "FAILED_PRECONDITION",
221
- });
222
- });
223
-
224
- it("passes the caller-gate refusal through verbatim", async () => {
225
- createResponse = () => {
226
- throw new ConnectError(
227
- "memory capture is limited to first-party sessions",
228
- Code.PermissionDenied,
229
- );
230
- };
231
-
232
- const result = await remember({ fact: "A fact from the wrong caller." });
233
-
234
- expect(result.isError).toBe(true);
235
- expect(JSON.parse(result.content[0]?.text ?? "{}")).toEqual({
236
- error: "memory capture is limited to first-party sessions",
237
- code: "PERMISSION_DENIED",
238
- });
239
- });
240
-
241
- it("delegates transport codes to the shared classifier", async () => {
242
- createResponse = () => {
243
- throw new ConnectError("upstream down", Code.Unavailable);
244
- };
245
-
246
- const result = await remember({ fact: "Any fact." });
247
-
248
- expect(result.isError).toBe(true);
249
- expect(result.content[0]?.text).toBe(
250
- "Stigmer server is unavailable. Ensure it is running and reachable.",
251
- );
252
- });
253
- });
@@ -1,163 +0,0 @@
1
- // In-process integration test for the read tools (get_mcp_server, get_skill,
2
- // get_workflow, get_environment). Stands up a real Connect
3
- // backend serving the query controllers, drives the MCP server through an
4
- // in-memory client, and asserts each tool returns the backend's protojson
5
- // verbatim (the parity contract) and that get_skill forwards its optional
6
- // version to the backend.
7
-
8
- import { create, toJson } from "@bufbuild/protobuf";
9
- import type { ConnectRouter } from "@connectrpc/connect";
10
- import { connectNodeAdapter } from "@connectrpc/connect-node";
11
- import {
12
- createServer as createHttp2Server,
13
- type Http2Server,
14
- type ServerHttp2Session,
15
- } from "node:http2";
16
- import type { AddressInfo } from "node:net";
17
-
18
- import { Client } from "@modelcontextprotocol/sdk/client/index.js";
19
- import { InMemoryTransport } from "@modelcontextprotocol/sdk/inMemory.js";
20
- import { EnvironmentSchema } from "@stigmer/protos/ai/stigmer/agentic/environment/v1/api_pb";
21
- import { EnvironmentQueryController } from "@stigmer/protos/ai/stigmer/agentic/environment/v1/query_pb";
22
- import { McpServerSchema } from "@stigmer/protos/ai/stigmer/agentic/mcpserver/v1/api_pb";
23
- import { McpServerQueryController } from "@stigmer/protos/ai/stigmer/agentic/mcpserver/v1/query_pb";
24
- import { SkillSchema } from "@stigmer/protos/ai/stigmer/agentic/skill/v1/api_pb";
25
- import { SkillQueryController } from "@stigmer/protos/ai/stigmer/agentic/skill/v1/query_pb";
26
- import { WorkflowSchema } from "@stigmer/protos/ai/stigmer/agentic/workflow/v1/api_pb";
27
- import { WorkflowQueryController } from "@stigmer/protos/ai/stigmer/agentic/workflow/v1/query_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
- const knownMcpServer = create(McpServerSchema, {
36
- apiVersion: "v1",
37
- kind: "mcp_server",
38
- metadata: { name: "GitHub", slug: "github", org: "acme", id: "mcp-1" },
39
- });
40
-
41
- const knownSkill = create(SkillSchema, {
42
- apiVersion: "v1",
43
- kind: "skill",
44
- metadata: { name: "Code Review", slug: "code-review", org: "acme", id: "skl-1" },
45
- });
46
-
47
- const knownWorkflow = create(WorkflowSchema, {
48
- apiVersion: "v1",
49
- kind: "workflow",
50
- metadata: { name: "Release", slug: "release", org: "acme", id: "wkf-1" },
51
- });
52
-
53
- // The backend redacts secret values before they leave the server; the tool
54
- // must pass that redaction through verbatim.
55
- const knownEnvironment = create(EnvironmentSchema, {
56
- apiVersion: "v1",
57
- kind: "environment",
58
- metadata: { name: "GitHub Creds", slug: "github-creds", org: "acme", id: "env-1" },
59
- spec: { data: { API_KEY: { value: "***REDACTED***", isSecret: true } } },
60
- });
61
-
62
-
63
- let backend: Http2Server;
64
- let client: Client;
65
- let lastSkillVersion: string | undefined;
66
- const openSessions = new Set<ServerHttp2Session>();
67
-
68
- interface ToolResult {
69
- content: Array<{ type: string; text?: string }>;
70
- isError?: boolean;
71
- }
72
-
73
- async function callTool(name: string, args: Record<string, unknown>): Promise<ToolResult> {
74
- return (await client.callTool({ name, arguments: args })) as ToolResult;
75
- }
76
-
77
- beforeAll(async () => {
78
- const routes = (router: ConnectRouter) => {
79
- router.service(McpServerQueryController, { getByReference: () => knownMcpServer });
80
- router.service(SkillQueryController, {
81
- getByReference: (req) => {
82
- lastSkillVersion = req.version;
83
- return knownSkill;
84
- },
85
- });
86
- router.service(WorkflowQueryController, { getByReference: () => knownWorkflow });
87
- router.service(EnvironmentQueryController, { getByReference: () => knownEnvironment });
88
- };
89
- backend = createHttp2Server(connectNodeAdapter({ routes }));
90
- // Force keep-alive sessions closed on teardown, else backend.close() blocks.
91
- backend.on("session", (session) => {
92
- openSessions.add(session);
93
- session.on("close", () => openSessions.delete(session));
94
- });
95
- await new Promise<void>((resolve) => backend.listen(0, "127.0.0.1", resolve));
96
- const port = (backend.address() as AddressInfo).port;
97
-
98
- const mcp = createServer({ serverAddress: `127.0.0.1:${port}`, apiKey: "" });
99
- const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
100
- client = new Client({ name: "reads-integration", version: "test" });
101
- await Promise.all([mcp.connect(serverTransport), client.connect(clientTransport)]);
102
- });
103
-
104
- afterAll(async () => {
105
- await client?.close();
106
- for (const session of openSessions) session.destroy();
107
- await new Promise<void>((resolve) => backend.close(() => resolve()));
108
- });
109
-
110
- describe("read tools integration", () => {
111
- it("advertises all read tools", async () => {
112
- const { tools } = await client.listTools();
113
- expect(tools.map((t) => t.name)).toEqual(
114
- expect.arrayContaining([
115
- "get_agent",
116
- "get_mcp_server",
117
- "get_skill",
118
- "get_workflow",
119
- "get_environment",
120
- ]),
121
- );
122
- });
123
-
124
- it("get_mcp_server returns the backend protojson", async () => {
125
- const result = await callTool("get_mcp_server", { org: "acme", slug: "github" });
126
- expect(result.isError).toBeFalsy();
127
- expect(JSON.parse(result.content[0]?.text ?? "{}")).toEqual(
128
- toJson(McpServerSchema, knownMcpServer, { useProtoFieldName: true }),
129
- );
130
- });
131
-
132
- it("get_workflow returns the backend protojson", async () => {
133
- const result = await callTool("get_workflow", { org: "acme", slug: "release" });
134
- expect(result.isError).toBeFalsy();
135
- expect(JSON.parse(result.content[0]?.text ?? "{}")).toEqual(
136
- toJson(WorkflowSchema, knownWorkflow, { useProtoFieldName: true }),
137
- );
138
- });
139
-
140
- it("get_skill returns the backend protojson and defaults version to latest", async () => {
141
- const result = await callTool("get_skill", { org: "acme", slug: "code-review" });
142
- expect(result.isError).toBeFalsy();
143
- expect(JSON.parse(result.content[0]?.text ?? "{}")).toEqual(
144
- toJson(SkillSchema, knownSkill, { useProtoFieldName: true }),
145
- );
146
- // Omitted version is forwarded as the empty string ("latest").
147
- expect(lastSkillVersion).toBe("");
148
- });
149
-
150
- it("get_skill forwards an explicit version to the backend", async () => {
151
- await callTool("get_skill", { org: "acme", slug: "code-review", version: "stable" });
152
- expect(lastSkillVersion).toBe("stable");
153
- });
154
-
155
- it("get_environment passes the server's secret redaction through verbatim", async () => {
156
- const result = await callTool("get_environment", { org: "acme", slug: "github-creds" });
157
- expect(result.isError).toBeFalsy();
158
- const body = JSON.parse(result.content[0]?.text ?? "{}") as Record<string, unknown>;
159
- expect(body).toEqual(toJson(EnvironmentSchema, knownEnvironment, { useProtoFieldName: true }));
160
- const data = (body.spec as { data: Record<string, { value: string }> }).data;
161
- expect(data.API_KEY?.value).toBe("***REDACTED***");
162
- });
163
- });
@@ -1,157 +0,0 @@
1
- // In-process integration test for the resource templates. Verifies template
2
- // discovery, that each read returns the backend protojson as a single
3
- // application/json entry, and that the skill templates resolve latest (empty
4
- // version) vs a pinned version.
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 { AgentQueryController } from "@stigmer/protos/ai/stigmer/agentic/agent/v1/query_pb";
20
- import { EnvironmentSchema } from "@stigmer/protos/ai/stigmer/agentic/environment/v1/api_pb";
21
- import { EnvironmentQueryController } from "@stigmer/protos/ai/stigmer/agentic/environment/v1/query_pb";
22
- import { McpServerSchema } from "@stigmer/protos/ai/stigmer/agentic/mcpserver/v1/api_pb";
23
- import { McpServerQueryController } from "@stigmer/protos/ai/stigmer/agentic/mcpserver/v1/query_pb";
24
- import { SkillSchema } from "@stigmer/protos/ai/stigmer/agentic/skill/v1/api_pb";
25
- import { SkillQueryController } from "@stigmer/protos/ai/stigmer/agentic/skill/v1/query_pb";
26
- import { WorkflowSchema } from "@stigmer/protos/ai/stigmer/agentic/workflow/v1/api_pb";
27
- import { WorkflowQueryController } from "@stigmer/protos/ai/stigmer/agentic/workflow/v1/query_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
- const agent = create(AgentSchema, { apiVersion: "v1", kind: "agent", metadata: { slug: "a", org: "acme" } });
36
- const mcpServer = create(McpServerSchema, {
37
- apiVersion: "v1",
38
- kind: "mcp_server",
39
- metadata: { slug: "m", org: "acme" },
40
- });
41
- const skill = create(SkillSchema, { apiVersion: "v1", kind: "skill", metadata: { slug: "s", org: "acme" } });
42
- const workflow = create(WorkflowSchema, {
43
- apiVersion: "v1",
44
- kind: "workflow",
45
- metadata: { slug: "w", org: "acme" },
46
- });
47
- const environment = create(EnvironmentSchema, {
48
- apiVersion: "v1",
49
- kind: "environment",
50
- metadata: { slug: "e", org: "acme" },
51
- });
52
-
53
- let backend: Http2Server;
54
- let client: Client;
55
- let lastSkillVersion: string | undefined;
56
- const openSessions = new Set<ServerHttp2Session>();
57
-
58
- interface ResourceResult {
59
- contents: Array<{ uri: string; mimeType?: string; text?: string }>;
60
- }
61
-
62
- beforeAll(async () => {
63
- const routes = (router: ConnectRouter) => {
64
- router.service(AgentQueryController, { getByReference: () => agent });
65
- router.service(McpServerQueryController, { getByReference: () => mcpServer });
66
- router.service(SkillQueryController, {
67
- getByReference: (req) => {
68
- lastSkillVersion = req.version;
69
- return skill;
70
- },
71
- });
72
- router.service(WorkflowQueryController, { getByReference: () => workflow });
73
- router.service(EnvironmentQueryController, { getByReference: () => environment });
74
- };
75
- backend = createHttp2Server(connectNodeAdapter({ routes }));
76
- backend.on("session", (session) => {
77
- openSessions.add(session);
78
- session.on("close", () => openSessions.delete(session));
79
- });
80
- await new Promise<void>((resolve) => backend.listen(0, "127.0.0.1", resolve));
81
- const port = (backend.address() as AddressInfo).port;
82
-
83
- const mcp = createServer({ serverAddress: `127.0.0.1:${port}`, apiKey: "" });
84
- const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
85
- client = new Client({ name: "resources-integration", version: "test" });
86
- await Promise.all([mcp.connect(serverTransport), client.connect(clientTransport)]);
87
- });
88
-
89
- afterAll(async () => {
90
- await client?.close();
91
- for (const session of openSessions) session.destroy();
92
- await new Promise<void>((resolve) => backend.close(() => resolve()));
93
- });
94
-
95
- describe("resource templates integration", () => {
96
- it("advertises every template", async () => {
97
- const { resourceTemplates } = await client.listResourceTemplates();
98
- expect(resourceTemplates.map((t) => t.name)).toEqual(
99
- expect.arrayContaining([
100
- "stigmer_agent",
101
- "stigmer_mcp_server",
102
- "stigmer_skill",
103
- "stigmer_skill_version",
104
- "stigmer_workflow",
105
- "stigmer_environment",
106
- ]),
107
- );
108
- });
109
-
110
- it("reads an agent resource", async () => {
111
- const result = (await client.readResource({ uri: "stigmer://agents/acme/a" })) as ResourceResult;
112
- expect(result.contents[0]?.mimeType).toBe("application/json");
113
- expect(JSON.parse(result.contents[0]?.text ?? "{}")).toEqual(
114
- toJson(AgentSchema, agent, { useProtoFieldName: true }),
115
- );
116
- });
117
-
118
- it("reads an mcp-server resource", async () => {
119
- const result = (await client.readResource({
120
- uri: "stigmer://mcp-servers/acme/m",
121
- })) as ResourceResult;
122
- expect(JSON.parse(result.contents[0]?.text ?? "{}")).toEqual(
123
- toJson(McpServerSchema, mcpServer, { useProtoFieldName: true }),
124
- );
125
- });
126
-
127
- it("reads a workflow resource", async () => {
128
- const result = (await client.readResource({
129
- uri: "stigmer://workflows/acme/w",
130
- })) as ResourceResult;
131
- expect(JSON.parse(result.contents[0]?.text ?? "{}")).toEqual(
132
- toJson(WorkflowSchema, workflow, { useProtoFieldName: true }),
133
- );
134
- });
135
-
136
- it("reads the latest skill (empty version)", async () => {
137
- const result = (await client.readResource({ uri: "stigmer://skills/acme/s" })) as ResourceResult;
138
- expect(JSON.parse(result.contents[0]?.text ?? "{}")).toEqual(
139
- toJson(SkillSchema, skill, { useProtoFieldName: true }),
140
- );
141
- expect(lastSkillVersion).toBe("");
142
- });
143
-
144
- it("reads a pinned skill version", async () => {
145
- await client.readResource({ uri: "stigmer://skills/acme/s/v2.0.0" });
146
- expect(lastSkillVersion).toBe("v2.0.0");
147
- });
148
-
149
- it("reads an environment resource", async () => {
150
- const result = (await client.readResource({
151
- uri: "stigmer://environments/acme/e",
152
- })) as ResourceResult;
153
- expect(JSON.parse(result.contents[0]?.text ?? "{}")).toEqual(
154
- toJson(EnvironmentSchema, environment, { useProtoFieldName: true }),
155
- );
156
- });
157
- });
@@ -1,104 +0,0 @@
1
- // Unit tests for stigmer:// resource URI parsing and construction.
2
- // Asserts parity with the Go server's behavior (scheme check, segment counts,
3
- // version handling, and the kind→authority round-trip).
4
-
5
- import { describe, expect, it } from "vitest";
6
-
7
- import {
8
- buildResourceURI,
9
- kindToAuthority,
10
- parseResourceURI,
11
- parseVersionedResourceURI,
12
- } from "./resourceuri";
13
-
14
- describe("parseResourceURI", () => {
15
- it("extracts org and slug from a two-segment URI", () => {
16
- expect(parseResourceURI("stigmer://agents/acme/code-reviewer")).toEqual({
17
- org: "acme",
18
- slug: "code-reviewer",
19
- });
20
- });
21
-
22
- it("ignores the authority (kind) segment", () => {
23
- // The authority differs but the org/slug are taken from the path only.
24
- expect(parseResourceURI("stigmer://mcp-servers/acme/github")).toEqual({
25
- org: "acme",
26
- slug: "github",
27
- });
28
- });
29
-
30
- it("rejects a non-stigmer scheme", () => {
31
- expect(() => parseResourceURI("https://agents/acme/code-reviewer")).toThrow(/expected "stigmer"/);
32
- });
33
-
34
- it("rejects the wrong number of segments", () => {
35
- expect(() => parseResourceURI("stigmer://agents/acme")).toThrow(/got 1/);
36
- expect(() => parseResourceURI("stigmer://agents/acme/code-reviewer/v1")).toThrow(/got 3/);
37
- });
38
-
39
- it("rejects a malformed URI", () => {
40
- expect(() => parseResourceURI("not a uri")).toThrow(/malformed resource URI/);
41
- });
42
- });
43
-
44
- describe("parseVersionedResourceURI", () => {
45
- it("treats two segments as latest (empty version)", () => {
46
- expect(parseVersionedResourceURI("stigmer://skills/acme/my-skill")).toEqual({
47
- org: "acme",
48
- slug: "my-skill",
49
- version: "",
50
- });
51
- });
52
-
53
- it("extracts an explicit version from three segments", () => {
54
- expect(parseVersionedResourceURI("stigmer://skills/acme/my-skill/v1.2.0")).toEqual({
55
- org: "acme",
56
- slug: "my-skill",
57
- version: "v1.2.0",
58
- });
59
- });
60
-
61
- it("rejects more than three segments", () => {
62
- expect(() => parseVersionedResourceURI("stigmer://skills/acme/my-skill/v1/extra")).toThrow(
63
- /got 4/,
64
- );
65
- });
66
-
67
- it("rejects a single segment", () => {
68
- expect(() => parseVersionedResourceURI("stigmer://skills/acme")).toThrow(/got 1/);
69
- });
70
- });
71
-
72
- describe("buildResourceURI", () => {
73
- it("builds a URI for every templated kind", () => {
74
- expect(buildResourceURI("agent", "acme", "a")).toBe("stigmer://agents/acme/a");
75
- expect(buildResourceURI("mcp_server", "acme", "m")).toBe("stigmer://mcp-servers/acme/m");
76
- expect(buildResourceURI("skill", "acme", "s")).toBe("stigmer://skills/acme/s");
77
- expect(buildResourceURI("workflow", "acme", "w")).toBe("stigmer://workflows/acme/w");
78
- expect(buildResourceURI("environment", "acme", "e")).toBe("stigmer://environments/acme/e");
79
- });
80
-
81
- it("round-trips with parseResourceURI", () => {
82
- const uri = buildResourceURI("agent", "acme", "code-reviewer");
83
- expect(parseResourceURI(uri)).toEqual({ org: "acme", slug: "code-reviewer" });
84
- });
85
-
86
- it("returns empty string for an unknown kind", () => {
87
- expect(buildResourceURI("organization", "acme", "x")).toBe("");
88
- });
89
-
90
- it("returns empty string when org or slug is empty", () => {
91
- expect(buildResourceURI("agent", "", "x")).toBe("");
92
- expect(buildResourceURI("agent", "acme", "")).toBe("");
93
- });
94
-
95
- it("covers exactly the templated kinds", () => {
96
- expect(Object.keys(kindToAuthority).sort()).toEqual([
97
- "agent",
98
- "environment",
99
- "mcp_server",
100
- "skill",
101
- "workflow",
102
- ]);
103
- });
104
- });