@stigmer/mcp-server 3.12.6 → 3.12.8
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.js +4 -4
- package/config.d.ts +4 -1
- package/config.d.ts.map +1 -1
- package/config.js +2 -2
- package/config.js.map +1 -1
- package/domains/memory/calls.d.ts +8 -0
- package/domains/memory/calls.d.ts.map +1 -0
- package/domains/memory/calls.js +60 -0
- package/domains/memory/calls.js.map +1 -0
- package/domains/memory/context.d.ts +56 -0
- package/domains/memory/context.d.ts.map +1 -0
- package/domains/memory/context.js +92 -0
- package/domains/memory/context.js.map +1 -0
- package/domains/memory/errors.d.ts +21 -0
- package/domains/memory/errors.d.ts.map +1 -0
- package/domains/memory/errors.js +72 -0
- package/domains/memory/errors.js.map +1 -0
- package/domains/memory/tools.d.ts +12 -0
- package/domains/memory/tools.d.ts.map +1 -0
- package/domains/memory/tools.js +48 -0
- package/domains/memory/tools.js.map +1 -0
- package/index.d.ts +1 -1
- package/index.d.ts.map +1 -1
- package/index.js +1 -1
- package/index.js.map +1 -1
- package/package.json +3 -3
- package/server.d.ts +22 -4
- package/server.d.ts.map +1 -1
- package/server.js +32 -4
- package/server.js.map +1 -1
- package/src/config.ts +6 -3
- package/src/domains/memory/calls.ts +74 -0
- package/src/domains/memory/context.test.ts +95 -0
- package/src/domains/memory/context.ts +125 -0
- package/src/domains/memory/errors.ts +89 -0
- package/src/domains/memory/memory.integration.test.ts +248 -0
- package/src/domains/memory/tools.ts +75 -0
- package/src/http.integration.test.ts +1 -0
- package/src/index.ts +2 -0
- package/src/server.ts +38 -4
|
@@ -0,0 +1,248 @@
|
|
|
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 (object property for closure narrowing). */
|
|
63
|
+
const captured: { create?: Memory } = {};
|
|
64
|
+
|
|
65
|
+
interface ToolResult {
|
|
66
|
+
content: Array<{ type: string; text?: string }>;
|
|
67
|
+
isError?: boolean;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async function remember(args: Record<string, unknown>): Promise<ToolResult> {
|
|
71
|
+
return (await client.callTool({ name: "remember", arguments: args })) as ToolResult;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** A plausible created record echoing the request's content. */
|
|
75
|
+
function proposedRecord(content: string): Memory {
|
|
76
|
+
return create(MemorySchema, {
|
|
77
|
+
apiVersion: "agentic.stigmer.ai/v1",
|
|
78
|
+
kind: "Memory",
|
|
79
|
+
metadata: create(ApiResourceMetadataSchema, { id: "mem_01test", org: "acme" }),
|
|
80
|
+
spec: create(MemorySpecSchema, {
|
|
81
|
+
content,
|
|
82
|
+
subjectIdentityAccountId: "ida_subject",
|
|
83
|
+
}),
|
|
84
|
+
status: create(MemoryStatusSchema, {
|
|
85
|
+
lifecycleState: MemoryLifecycleState.lifecycle_state_proposed,
|
|
86
|
+
}),
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
beforeAll(async () => {
|
|
91
|
+
const routes = (router: ConnectRouter) => {
|
|
92
|
+
router.service(MemoryCommandController, {
|
|
93
|
+
create: (req) => {
|
|
94
|
+
captured.create = req;
|
|
95
|
+
return createResponse();
|
|
96
|
+
},
|
|
97
|
+
});
|
|
98
|
+
};
|
|
99
|
+
backend = createHttp2Server(connectNodeAdapter({ routes }));
|
|
100
|
+
backend.on("session", (session) => {
|
|
101
|
+
openSessions.add(session);
|
|
102
|
+
session.on("close", () => openSessions.delete(session));
|
|
103
|
+
});
|
|
104
|
+
await new Promise<void>((resolve) => backend.listen(0, "127.0.0.1", resolve));
|
|
105
|
+
const port = (backend.address() as AddressInfo).port;
|
|
106
|
+
|
|
107
|
+
// The stdio shape: the startup capture context stands in for the
|
|
108
|
+
// runner-set STIGMER_MEMORY_* environment.
|
|
109
|
+
const mcp = createMemoryServer(
|
|
110
|
+
{ serverAddress: `127.0.0.1:${port}`, apiKey: "" },
|
|
111
|
+
{ org: "acme", agentId: "agt_1", sessionId: "ses_1", agentExecutionId: "aex_1" },
|
|
112
|
+
);
|
|
113
|
+
const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
|
|
114
|
+
client = new Client({ name: "memory-integration", version: "test" });
|
|
115
|
+
await Promise.all([mcp.connect(serverTransport), client.connect(clientTransport)]);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
afterAll(async () => {
|
|
119
|
+
await client?.close();
|
|
120
|
+
for (const session of openSessions) session.destroy();
|
|
121
|
+
await new Promise<void>((resolve) => backend.close(() => resolve()));
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
describe("memory roster (DD-005 D1)", () => {
|
|
125
|
+
it("exposes exactly remember with only a fact argument", async () => {
|
|
126
|
+
const { tools } = await client.listTools();
|
|
127
|
+
expect(tools.map((t) => t.name)).toEqual(["remember"]);
|
|
128
|
+
|
|
129
|
+
// The agent audience supplies the fact and NOTHING else — org,
|
|
130
|
+
// subject, and provenance derive from the credential and the
|
|
131
|
+
// runner-threaded capture context, so no argument exists to forge
|
|
132
|
+
// them (the channels no-org rule, applied to attribution).
|
|
133
|
+
const properties = (tools[0].inputSchema as { properties?: Record<string, unknown> })
|
|
134
|
+
.properties;
|
|
135
|
+
expect(Object.keys(properties ?? {})).toEqual(["fact"]);
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it("pins the cross-repo attachment strings (the TOOL_CALL_LIMIT precedent)", () => {
|
|
139
|
+
// The runner's memory-attachment.ts builds these independently; a
|
|
140
|
+
// drift strands every synthesized attachment on a 404 (route) or an
|
|
141
|
+
// attribution-less record (context keys).
|
|
142
|
+
expect(MEMORY_ROUTE).toBe("/memory");
|
|
143
|
+
expect(MEMORY_ORG_ENV).toBe("STIGMER_MEMORY_ORG");
|
|
144
|
+
expect(MEMORY_AGENT_ID_ENV).toBe("STIGMER_MEMORY_AGENT_ID");
|
|
145
|
+
expect(MEMORY_SESSION_ID_ENV).toBe("STIGMER_MEMORY_SESSION_ID");
|
|
146
|
+
expect(MEMORY_EXECUTION_ID_ENV).toBe("STIGMER_MEMORY_EXECUTION_ID");
|
|
147
|
+
expect(MEMORY_ORG_HEADER).toBe("x-stigmer-memory-org");
|
|
148
|
+
expect(MEMORY_AGENT_ID_HEADER).toBe("x-stigmer-memory-agent-id");
|
|
149
|
+
expect(MEMORY_SESSION_ID_HEADER).toBe("x-stigmer-memory-session-id");
|
|
150
|
+
expect(MEMORY_EXECUTION_ID_HEADER).toBe("x-stigmer-memory-execution-id");
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
describe("argument + capture context → request mapping (DD-005 D2)", () => {
|
|
155
|
+
it("maps the fact and the startup context; subject never travels", async () => {
|
|
156
|
+
createResponse = () => proposedRecord("Prefers concise answers.");
|
|
157
|
+
captured.create = undefined;
|
|
158
|
+
|
|
159
|
+
const result = await remember({ fact: "Prefers concise answers." });
|
|
160
|
+
|
|
161
|
+
expect(result.isError).toBeFalsy();
|
|
162
|
+
const req = captured.create;
|
|
163
|
+
expect(req?.metadata?.org).toBe("acme");
|
|
164
|
+
expect(req?.metadata?.name).toBe(""); // id-addressed; the server defaults the name
|
|
165
|
+
expect(req?.spec?.content).toBe("Prefers concise answers.");
|
|
166
|
+
// Subject is the server's to derive from the credential — the tool
|
|
167
|
+
// never supplies one, so forgery is structurally impossible.
|
|
168
|
+
expect(req?.spec?.subjectIdentityAccountId).toBe("");
|
|
169
|
+
expect(req?.spec?.provenance?.agentId).toBe("agt_1");
|
|
170
|
+
expect(req?.spec?.provenance?.sessionId).toBe("ses_1");
|
|
171
|
+
expect(req?.spec?.provenance?.agentExecutionId).toBe("aex_1");
|
|
172
|
+
// v1: MCP does not carry the harness's tool-call identity.
|
|
173
|
+
expect(req?.spec?.provenance?.toolCallId).toBe("");
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
it("answers with the chip contract: outcome line + created record as proto JSON", async () => {
|
|
177
|
+
createResponse = () => proposedRecord("Works primarily in Go.");
|
|
178
|
+
|
|
179
|
+
const result = await remember({ fact: "Works primarily in Go." });
|
|
180
|
+
|
|
181
|
+
expect(result.isError).toBeFalsy();
|
|
182
|
+
const body = JSON.parse(result.content[0]?.text ?? "{}") as {
|
|
183
|
+
outcome?: string;
|
|
184
|
+
memory?: Record<string, unknown>;
|
|
185
|
+
};
|
|
186
|
+
// The honest relay (DD-005 D2): proposed, the user decides.
|
|
187
|
+
expect(body.outcome).toBe(PROPOSED_OUTCOME);
|
|
188
|
+
// The record rides verbatim with proto (snake_case) field names —
|
|
189
|
+
// what the SDK's normalizeToolResult parses to render the chip.
|
|
190
|
+
const memory = body.memory as {
|
|
191
|
+
metadata?: { id?: string };
|
|
192
|
+
spec?: { content?: string };
|
|
193
|
+
status?: { lifecycle_state?: string };
|
|
194
|
+
};
|
|
195
|
+
expect(memory?.metadata?.id).toBe("mem_01test");
|
|
196
|
+
expect(memory?.spec?.content).toBe("Works primarily in Go.");
|
|
197
|
+
expect(memory?.status?.lifecycle_state).toBe("lifecycle_state_proposed");
|
|
198
|
+
});
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
describe("memory error mapper", () => {
|
|
202
|
+
it("passes the visible-full refusal through verbatim — never the shared rewrite", async () => {
|
|
203
|
+
createResponse = () => {
|
|
204
|
+
throw new ConnectError(
|
|
205
|
+
"memory is full — review and delete existing memories",
|
|
206
|
+
Code.FailedPrecondition,
|
|
207
|
+
);
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
const result = await remember({ fact: "One fact too many." });
|
|
211
|
+
|
|
212
|
+
expect(result.isError).toBe(true);
|
|
213
|
+
expect(JSON.parse(result.content[0]?.text ?? "{}")).toEqual({
|
|
214
|
+
error: "memory is full — review and delete existing memories",
|
|
215
|
+
code: "FAILED_PRECONDITION",
|
|
216
|
+
});
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
it("passes the caller-gate refusal through verbatim", async () => {
|
|
220
|
+
createResponse = () => {
|
|
221
|
+
throw new ConnectError(
|
|
222
|
+
"memory capture is limited to first-party sessions",
|
|
223
|
+
Code.PermissionDenied,
|
|
224
|
+
);
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
const result = await remember({ fact: "A fact from the wrong caller." });
|
|
228
|
+
|
|
229
|
+
expect(result.isError).toBe(true);
|
|
230
|
+
expect(JSON.parse(result.content[0]?.text ?? "{}")).toEqual({
|
|
231
|
+
error: "memory capture is limited to first-party sessions",
|
|
232
|
+
code: "PERMISSION_DENIED",
|
|
233
|
+
});
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
it("delegates transport codes to the shared classifier", async () => {
|
|
237
|
+
createResponse = () => {
|
|
238
|
+
throw new ConnectError("upstream down", Code.Unavailable);
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
const result = await remember({ fact: "Any fact." });
|
|
242
|
+
|
|
243
|
+
expect(result.isError).toBe(true);
|
|
244
|
+
expect(result.content[0]?.text).toBe(
|
|
245
|
+
"Stigmer server is unavailable. Ensure it is running and reachable.",
|
|
246
|
+
);
|
|
247
|
+
});
|
|
248
|
+
});
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
// The remember tool — the ONE tool of the memory roster (DD-005 D1: the
|
|
2
|
+
// first-party capture verb both harnesses receive through the
|
|
3
|
+
// runner-synthesized memory attachment).
|
|
4
|
+
//
|
|
5
|
+
// Agent audience only, by construction: this roster is what the memory
|
|
6
|
+
// attachment connects to, and the record's org, subject, and provenance
|
|
7
|
+
// all derive from the credential and the runner-threaded capture context
|
|
8
|
+
// (context.ts) — no org argument exists to invite rejected calls, and no
|
|
9
|
+
// argument can aim the record at another person. The tool is deliberately
|
|
10
|
+
// NOT on the full roster: a human operator manages memories through the
|
|
11
|
+
// console and SDK, which carry the addressing this path derives.
|
|
12
|
+
//
|
|
13
|
+
// The tool creates a PROPOSAL and nothing more (DD-005 D2/D3): the record
|
|
14
|
+
// lands lifecycle_state=proposed, and only the user's confirm — a
|
|
15
|
+
// control-plane command the model cannot reach — makes it recallable. The
|
|
16
|
+
// answer's `outcome` line states this so the model relays honestly.
|
|
17
|
+
|
|
18
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
19
|
+
import { z } from "zod";
|
|
20
|
+
|
|
21
|
+
import { resolveToken, type BackendTarget } from "../client.js";
|
|
22
|
+
import { proposeMemory } from "./calls.js";
|
|
23
|
+
import {
|
|
24
|
+
resolveCaptureContext,
|
|
25
|
+
type CaptureContext,
|
|
26
|
+
type RequestHeaders,
|
|
27
|
+
} from "./context.js";
|
|
28
|
+
import { memoryResult } from "./errors.js";
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Register the memory capture tool; returns the tool names.
|
|
32
|
+
*
|
|
33
|
+
* `startupContext` is the stdio-shape capture context (env-loaded at
|
|
34
|
+
* server construction); over HTTP each request's headers supersede it —
|
|
35
|
+
* the resolveToken fallback shape, applied to attribution.
|
|
36
|
+
*/
|
|
37
|
+
export function registerMemoryTools(
|
|
38
|
+
server: McpServer,
|
|
39
|
+
target: BackendTarget,
|
|
40
|
+
startupContext: CaptureContext,
|
|
41
|
+
): string[] {
|
|
42
|
+
server.registerTool(
|
|
43
|
+
"remember",
|
|
44
|
+
{
|
|
45
|
+
description:
|
|
46
|
+
"Propose one durable fact about the person you are assisting, to be recalled in " +
|
|
47
|
+
"their future sessions — a stable preference, situation, or way of working " +
|
|
48
|
+
"(e.g. \"Prefers concise answers with code examples\"). Not task state, not " +
|
|
49
|
+
"secrets or credentials, not one-off details. The fact is only PROPOSED: the " +
|
|
50
|
+
"user reviews the exact text and decides — tell them you have suggested it, " +
|
|
51
|
+
"never that you have remembered it. Keep each fact self-contained and under " +
|
|
52
|
+
"500 characters; call once per fact.",
|
|
53
|
+
inputSchema: {
|
|
54
|
+
fact: z
|
|
55
|
+
.string()
|
|
56
|
+
.describe(
|
|
57
|
+
"The fact to remember, stated in the third person and self-contained " +
|
|
58
|
+
'(e.g. "Works primarily in Go and prefers table-driven tests"). ' +
|
|
59
|
+
"1–500 characters; stored and shown to the user verbatim.",
|
|
60
|
+
),
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
(args, extra) =>
|
|
64
|
+
memoryResult("memory proposal", () =>
|
|
65
|
+
proposeMemory(
|
|
66
|
+
target.serverAddress,
|
|
67
|
+
resolveToken(extra, target.apiKey),
|
|
68
|
+
args.fact,
|
|
69
|
+
resolveCaptureContext(extra as RequestHeaders, startupContext),
|
|
70
|
+
),
|
|
71
|
+
),
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
return ["remember"];
|
|
75
|
+
}
|
|
@@ -212,6 +212,7 @@ describe("HTTP route dispatch (the closed route table)", () => {
|
|
|
212
212
|
["/", "mcp-server-stigmer"],
|
|
213
213
|
["/channels", "mcp-server-stigmer-channels"],
|
|
214
214
|
["/conversation", "mcp-server-stigmer-conversation"],
|
|
215
|
+
["/memory", "mcp-server-stigmer-memory"],
|
|
215
216
|
])("serves the %s roster as %s", async (path, serverName) => {
|
|
216
217
|
const res = await initialize(path);
|
|
217
218
|
expect(res.status).toBe(200);
|
package/src/index.ts
CHANGED
package/src/server.ts
CHANGED
|
@@ -31,6 +31,11 @@ import { registerEnvironmentTools } from "./domains/environments/tools.js";
|
|
|
31
31
|
import { registerExecutionControlTools } from "./domains/executions/tools.js";
|
|
32
32
|
import { registerMcpServerResources } from "./domains/mcpservers/resources.js";
|
|
33
33
|
import { registerMcpServerTools } from "./domains/mcpservers/tools.js";
|
|
34
|
+
import {
|
|
35
|
+
loadCaptureContextFromEnv,
|
|
36
|
+
type CaptureContext,
|
|
37
|
+
} from "./domains/memory/context.js";
|
|
38
|
+
import { registerMemoryTools } from "./domains/memory/tools.js";
|
|
34
39
|
import { registerSearchTools } from "./domains/search/tools.js";
|
|
35
40
|
import { registerSkillResources } from "./domains/skills/resources.js";
|
|
36
41
|
import { registerSkillTools } from "./domains/skills/tools.js";
|
|
@@ -107,6 +112,29 @@ export function createConversationServer(target: BackendTarget): McpServer {
|
|
|
107
112
|
return server;
|
|
108
113
|
}
|
|
109
114
|
|
|
115
|
+
/**
|
|
116
|
+
* Build a memory-only MCP server: remember with the agent-facing argument
|
|
117
|
+
* surface, and nothing else (DD-005 D1 — the channels-roster pattern).
|
|
118
|
+
* This is the roster the runner-synthesized memory attachment connects
|
|
119
|
+
* to; the structural guarantee mirrors the channels roster's. Served on
|
|
120
|
+
* the /memory HTTP route and as the stdio roster when
|
|
121
|
+
* STIGMER_MCP_ROSTER=memory.
|
|
122
|
+
*
|
|
123
|
+
* `startupContext` is the stdio-shape capture context, read from the
|
|
124
|
+
* runner-set STIGMER_MEMORY_* environment at construction (the startup
|
|
125
|
+
* API key pattern). Over HTTP each request's provenance headers
|
|
126
|
+
* supersede it, so the http factory's env read is a harmless no-op.
|
|
127
|
+
*/
|
|
128
|
+
export function createMemoryServer(
|
|
129
|
+
target: BackendTarget,
|
|
130
|
+
startupContext: CaptureContext = loadCaptureContextFromEnv(),
|
|
131
|
+
): McpServer {
|
|
132
|
+
const server = new McpServer({ name: "mcp-server-stigmer-memory", version: SERVER_VERSION });
|
|
133
|
+
const tools = registerMemoryTools(server, target, startupContext);
|
|
134
|
+
log.info("tools registered (memory roster)", { count: tools.length, tools });
|
|
135
|
+
return server;
|
|
136
|
+
}
|
|
137
|
+
|
|
110
138
|
/**
|
|
111
139
|
* Wire up every domain's tools. Each domain returns the names it registered so
|
|
112
140
|
* the startup log's count and roster cannot drift from what is actually wired,
|
|
@@ -193,10 +221,14 @@ export const CHANNELS_ROUTE = "/channels";
|
|
|
193
221
|
/** HTTP route serving the conversation-only roster (channel-conversations A14). */
|
|
194
222
|
export const CONVERSATION_ROUTE = "/conversation";
|
|
195
223
|
|
|
224
|
+
/** HTTP route serving the memory-only roster (memory capture, DD-005 D1). */
|
|
225
|
+
export const MEMORY_ROUTE = "/memory";
|
|
226
|
+
|
|
196
227
|
/**
|
|
197
228
|
* The standard HTTP route dispatch: the full roster on {@link FULL_ROUTE},
|
|
198
229
|
* the channels-only roster on {@link CHANNELS_ROUTE}, the
|
|
199
|
-
* conversation-only roster on {@link CONVERSATION_ROUTE}
|
|
230
|
+
* conversation-only roster on {@link CONVERSATION_ROUTE}, the
|
|
231
|
+
* memory-only roster on {@link MEMORY_ROUTE} — and NOTHING
|
|
200
232
|
* anywhere else.
|
|
201
233
|
*
|
|
202
234
|
* The closed route table is load-bearing, not tidiness. This dispatch
|
|
@@ -214,6 +246,7 @@ export function routedServerFactory(target: BackendTarget): RouteServerFactory {
|
|
|
214
246
|
if (path === FULL_ROUTE) return createServer(target);
|
|
215
247
|
if (path === CHANNELS_ROUTE) return createChannelsServer(target);
|
|
216
248
|
if (path === CONVERSATION_ROUTE) return createConversationServer(target);
|
|
249
|
+
if (path === MEMORY_ROUTE) return createMemoryServer(target);
|
|
217
250
|
return undefined;
|
|
218
251
|
};
|
|
219
252
|
}
|
|
@@ -425,12 +458,13 @@ async function routeRequest(
|
|
|
425
458
|
}
|
|
426
459
|
|
|
427
460
|
/**
|
|
428
|
-
* The stdio server for the configured roster: the channels-only
|
|
429
|
-
* when STIGMER_MCP_ROSTER names
|
|
430
|
-
*
|
|
461
|
+
* The stdio server for the configured roster: the channels-only or
|
|
462
|
+
* memory-only roster when STIGMER_MCP_ROSTER names one (what the OSS
|
|
463
|
+
* runner-synthesized attachments spawn), the full roster otherwise.
|
|
431
464
|
*/
|
|
432
465
|
export function stdioServer(target: BackendTarget, cfg: Config): McpServer {
|
|
433
466
|
if (cfg.roster === "channels") return createChannelsServer(target);
|
|
467
|
+
if (cfg.roster === "memory") return createMemoryServer(target);
|
|
434
468
|
return createServer(target);
|
|
435
469
|
}
|
|
436
470
|
|