@stigmer/mcp-server 3.34.0 → 3.36.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/cli/mcp-server-stigmer.d.ts.map +1 -1
- package/cli/mcp-server-stigmer.js +2 -2
- package/cli/mcp-server-stigmer.js.map +1 -1
- package/config.d.ts.map +1 -1
- package/config.js.map +1 -1
- package/domains/agentexecutions/approve.d.ts.map +1 -1
- package/domains/agentexecutions/approve.js.map +1 -1
- package/domains/agentexecutions/fetch.d.ts.map +1 -1
- package/domains/agentexecutions/fetch.js.map +1 -1
- package/domains/agentexecutions/run.d.ts.map +1 -1
- package/domains/agentexecutions/run.js.map +1 -1
- package/domains/agentexecutions/tools.d.ts.map +1 -1
- package/domains/agentexecutions/tools.js.map +1 -1
- package/domains/agents/apply.d.ts.map +1 -1
- package/domains/agents/apply.js.map +1 -1
- package/domains/agents/delete.d.ts.map +1 -1
- package/domains/agents/delete.js.map +1 -1
- package/domains/agents/fetch.d.ts.map +1 -1
- package/domains/agents/fetch.js.map +1 -1
- package/domains/agents/resources.d.ts.map +1 -1
- package/domains/agents/resources.js.map +1 -1
- package/domains/agents/tools.d.ts.map +1 -1
- package/domains/agents/tools.js.map +1 -1
- package/domains/apply-visibility.d.ts.map +1 -1
- package/domains/apply-visibility.js.map +1 -1
- package/domains/channels/calls.d.ts.map +1 -1
- package/domains/channels/calls.js.map +1 -1
- package/domains/channels/errors.d.ts.map +1 -1
- package/domains/channels/errors.js.map +1 -1
- package/domains/channels/tools.d.ts.map +1 -1
- package/domains/channels/tools.js.map +1 -1
- package/domains/client.d.ts.map +1 -1
- package/domains/client.js.map +1 -1
- package/domains/conversation/calls.d.ts.map +1 -1
- package/domains/conversation/calls.js.map +1 -1
- package/domains/conversation/errors.d.ts.map +1 -1
- package/domains/conversation/errors.js.map +1 -1
- package/domains/conversation/tools.d.ts.map +1 -1
- package/domains/conversation/tools.js.map +1 -1
- package/domains/environments/apply.d.ts.map +1 -1
- package/domains/environments/apply.js.map +1 -1
- package/domains/environments/delete.d.ts.map +1 -1
- package/domains/environments/delete.js.map +1 -1
- package/domains/environments/fetch.d.ts.map +1 -1
- package/domains/environments/fetch.js.map +1 -1
- package/domains/environments/resources.d.ts.map +1 -1
- package/domains/environments/resources.js.map +1 -1
- package/domains/environments/tools.d.ts.map +1 -1
- package/domains/environments/tools.js.map +1 -1
- package/domains/executions/cancel.d.ts.map +1 -1
- package/domains/executions/cancel.js.map +1 -1
- package/domains/executions/tools.d.ts.map +1 -1
- package/domains/executions/tools.js.map +1 -1
- package/domains/marshal.d.ts.map +1 -1
- package/domains/marshal.js.map +1 -1
- package/domains/mcpservers/apply.d.ts.map +1 -1
- package/domains/mcpservers/apply.js.map +1 -1
- package/domains/mcpservers/delete.d.ts.map +1 -1
- package/domains/mcpservers/delete.js.map +1 -1
- package/domains/mcpservers/fetch.d.ts.map +1 -1
- package/domains/mcpservers/fetch.js.map +1 -1
- package/domains/mcpservers/resources.d.ts.map +1 -1
- package/domains/mcpservers/resources.js.map +1 -1
- package/domains/mcpservers/tools.d.ts.map +1 -1
- package/domains/mcpservers/tools.js.map +1 -1
- package/domains/memory/calls.d.ts.map +1 -1
- package/domains/memory/calls.js.map +1 -1
- package/domains/memory/context.d.ts.map +1 -1
- package/domains/memory/context.js.map +1 -1
- package/domains/memory/errors.d.ts.map +1 -1
- package/domains/memory/errors.js.map +1 -1
- package/domains/memory/tools.d.ts.map +1 -1
- package/domains/memory/tools.js.map +1 -1
- package/domains/resourcehandler.d.ts.map +1 -1
- package/domains/resourcehandler.js.map +1 -1
- package/domains/resourceuri.d.ts.map +1 -1
- package/domains/resourceuri.js.map +1 -1
- package/domains/rpcerr.d.ts.map +1 -1
- package/domains/rpcerr.js.map +1 -1
- package/domains/search/tools.d.ts.map +1 -1
- package/domains/search/tools.js.map +1 -1
- package/domains/skills/delete.d.ts.map +1 -1
- package/domains/skills/delete.js.map +1 -1
- package/domains/skills/fetch.d.ts.map +1 -1
- package/domains/skills/fetch.js.map +1 -1
- package/domains/skills/resources.d.ts.map +1 -1
- package/domains/skills/resources.js.map +1 -1
- package/domains/skills/tools.d.ts.map +1 -1
- package/domains/skills/tools.js.map +1 -1
- package/domains/skills/versions.d.ts.map +1 -1
- package/domains/skills/versions.js.map +1 -1
- package/domains/toolresult.d.ts.map +1 -1
- package/domains/toolresult.js.map +1 -1
- package/domains/workflowexecutions/approvals.d.ts.map +1 -1
- package/domains/workflowexecutions/approvals.js.map +1 -1
- package/domains/workflowexecutions/run.d.ts.map +1 -1
- package/domains/workflowexecutions/run.js.map +1 -1
- package/domains/workflowexecutions/tools.d.ts.map +1 -1
- package/domains/workflowexecutions/tools.js.map +1 -1
- package/domains/workflows/apply.d.ts.map +1 -1
- package/domains/workflows/apply.js.map +1 -1
- package/domains/workflows/delete.d.ts.map +1 -1
- package/domains/workflows/delete.js.map +1 -1
- package/domains/workflows/fetch.d.ts.map +1 -1
- package/domains/workflows/fetch.js.map +1 -1
- package/domains/workflows/resources.d.ts.map +1 -1
- package/domains/workflows/resources.js.map +1 -1
- package/domains/workflows/taskkinds.d.ts.map +1 -1
- package/domains/workflows/taskkinds.js.map +1 -1
- package/domains/workflows/tools.d.ts.map +1 -1
- package/domains/workflows/tools.js.map +1 -1
- package/domains/workflows/validate.d.ts.map +1 -1
- package/domains/workflows/validate.js.map +1 -1
- package/domains/workflows/versions.d.ts.map +1 -1
- package/domains/workflows/versions.js.map +1 -1
- package/gen/agent.d.ts.map +1 -1
- package/gen/agent.js.map +1 -1
- package/gen/apply-runtime.d.ts.map +1 -1
- package/gen/apply-runtime.js.map +1 -1
- package/gen/environment.d.ts.map +1 -1
- package/gen/environment.js.map +1 -1
- package/gen/mcpserver.d.ts.map +1 -1
- package/gen/mcpserver.js.map +1 -1
- package/gen/workflow.d.ts.map +1 -1
- package/gen/workflow.js +2 -2
- package/gen/workflow.js.map +1 -1
- package/index.d.ts.map +1 -1
- package/index.js.map +1 -1
- package/logger.d.ts.map +1 -1
- package/logger.js.map +1 -1
- package/package.json +3 -3
- package/readiness.d.ts.map +1 -1
- package/readiness.js.map +1 -1
- package/server.d.ts.map +1 -1
- package/server.js.map +1 -1
- package/src/gen/workflow.ts +2 -2
- package/src/config.test.ts +0 -88
- package/src/domains/apply.integration.test.ts +0 -313
- package/src/domains/channels/channels.integration.test.ts +0 -272
- package/src/domains/channels/errors.test.ts +0 -52
- package/src/domains/conversation/conversation.integration.test.ts +0 -233
- package/src/domains/deletes.integration.test.ts +0 -147
- package/src/domains/executions.integration.test.ts +0 -376
- package/src/domains/memory/context.test.ts +0 -95
- package/src/domains/memory/errors.test.ts +0 -54
- package/src/domains/memory/memory.integration.test.ts +0 -253
- package/src/domains/reads.integration.test.ts +0 -163
- package/src/domains/resources.integration.test.ts +0 -157
- package/src/domains/resourceuri.test.ts +0 -104
- package/src/domains/rpcerr.test.ts +0 -62
- package/src/domains/search/search.integration.test.ts +0 -141
- package/src/domains/versions.integration.test.ts +0 -212
- package/src/domains/workflows/validate.integration.test.ts +0 -117
- package/src/domains/workflows/workflow-tools.integration.test.ts +0 -148
- package/src/http.integration.test.ts +0 -235
- package/src/readiness.test.ts +0 -90
- 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
|
-
});
|