@nexusm/mcp-server 0.1.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 (44) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +13 -0
  3. package/RUNBOOK.md +190 -0
  4. package/dist/auth.d.ts +49 -0
  5. package/dist/auth.d.ts.map +1 -0
  6. package/dist/auth.js +62 -0
  7. package/dist/auth.js.map +1 -0
  8. package/dist/errors.d.ts +211 -0
  9. package/dist/errors.d.ts.map +1 -0
  10. package/dist/errors.js +245 -0
  11. package/dist/errors.js.map +1 -0
  12. package/dist/index.d.ts +28 -0
  13. package/dist/index.d.ts.map +1 -0
  14. package/dist/index.js +146 -0
  15. package/dist/index.js.map +1 -0
  16. package/dist/metrics.d.ts +146 -0
  17. package/dist/metrics.d.ts.map +1 -0
  18. package/dist/metrics.js +245 -0
  19. package/dist/metrics.js.map +1 -0
  20. package/dist/tools/context.d.ts +48 -0
  21. package/dist/tools/context.d.ts.map +1 -0
  22. package/dist/tools/context.js +229 -0
  23. package/dist/tools/context.js.map +1 -0
  24. package/dist/tools/index.d.ts +12 -0
  25. package/dist/tools/index.d.ts.map +1 -0
  26. package/dist/tools/index.js +19 -0
  27. package/dist/tools/index.js.map +1 -0
  28. package/dist/tools/memory_create.d.ts +37 -0
  29. package/dist/tools/memory_create.d.ts.map +1 -0
  30. package/dist/tools/memory_create.js +242 -0
  31. package/dist/tools/memory_create.js.map +1 -0
  32. package/dist/tools/memory_feedback.d.ts +44 -0
  33. package/dist/tools/memory_feedback.d.ts.map +1 -0
  34. package/dist/tools/memory_feedback.js +259 -0
  35. package/dist/tools/memory_feedback.js.map +1 -0
  36. package/dist/tools/memory_search.d.ts +44 -0
  37. package/dist/tools/memory_search.d.ts.map +1 -0
  38. package/dist/tools/memory_search.js +160 -0
  39. package/dist/tools/memory_search.js.map +1 -0
  40. package/dist/tools/types.d.ts +36 -0
  41. package/dist/tools/types.d.ts.map +1 -0
  42. package/dist/tools/types.js +29 -0
  43. package/dist/tools/types.js.map +1 -0
  44. package/package.json +50 -0
@@ -0,0 +1,245 @@
1
+ /**
2
+ * Prometheus metrics for the Nexus MCP server (US-037 Wave 2 TASK-014).
3
+ *
4
+ * Layer
5
+ * =====
6
+ * This file measures the **MCP protocol dispatch layer** — every `tools/call`
7
+ * and `tools/list` JSON-RPC request the MCP server handles. It is NOT the
8
+ * same as the Python-side `src/nexus/observability/metrics/mcp.py` which
9
+ * measures the **backend REST attribution layer** (post-MCP-dispatch).
10
+ *
11
+ * Both metric families share the `nexus_mcp_*` prefix but observe different
12
+ * events at different layers, so:
13
+ * - TS metric names use `nexus_mcp_tool_*` (this file)
14
+ * - Python metric names use `nexus_mcp_backend_*` (the backend mirror)
15
+ * - Alert rules and Grafana panels must NOT sum across both — they would
16
+ * double-count a single user action
17
+ * - See ADR-001 + Wave 2 mid_audit tech-lead Important #2 (2026-05-22)
18
+ *
19
+ * Design notes
20
+ * ============
21
+ * - Spins up an **independent** HTTP listener on `NEXUS_METRICS_PORT` (default
22
+ * 9090) so the Prometheus scrape endpoint never touches stdin/stdout — the
23
+ * stdio MCP transport monopolises those streams.
24
+ * - Uses `prom-client` for registry management; all metric instances are
25
+ * module-level singletons (CollectorRegistry deduplication).
26
+ * - Cardinality guard (R2 ai D-5): the `client` label is limited to a fixed
27
+ * allowlist; anything outside the list is coerced to `'unknown'` and a
28
+ * separate debug counter tracks the raw string so operators can promote a
29
+ * client to the allowlist without restarting the server.
30
+ *
31
+ * Startup integration
32
+ * ===================
33
+ * Call `startMetricsServer()` once from `src/index.ts` main() — it is a
34
+ * fire-and-forget async function that opens the HTTP port and logs to stderr.
35
+ * (Parent session must wire this call; see TASK-014 note.)
36
+ *
37
+ * prom-client npm dep note
38
+ * ========================
39
+ * Add `"prom-client": "^15.1.0"` to `dependencies` in `package.json`.
40
+ * This subagent does not modify package.json per task constraints.
41
+ */
42
+ import * as http from 'node:http';
43
+ import { Registry, Counter, Histogram, Gauge, collectDefaultMetrics } from 'prom-client';
44
+ // ---------------------------------------------------------------------------
45
+ // 1. Isolated Prometheus registry
46
+ // Using a custom registry so default-metrics labels (process, v8) stay
47
+ // clean and tests can create isolated registries without global pollution.
48
+ // ---------------------------------------------------------------------------
49
+ export const registry = new Registry();
50
+ // Collect default Node.js process metrics into our registry.
51
+ collectDefaultMetrics({ register: registry });
52
+ // ---------------------------------------------------------------------------
53
+ // 2. Allowlist for the `client` label (R2 ai D-5 cardinality guard)
54
+ // ---------------------------------------------------------------------------
55
+ const KNOWN_CLIENTS = new Set(['claude-code', 'cursor', 'windsurf', 'cline', 'mcp-cli']);
56
+ /**
57
+ * Normalise a raw client identifier to an allowlisted value.
58
+ * Returns `'unknown'` if the client is not in the allowlist.
59
+ */
60
+ function normaliseClient(raw) {
61
+ return KNOWN_CLIENTS.has(raw) ? raw : 'unknown';
62
+ }
63
+ // ---------------------------------------------------------------------------
64
+ // 3. Metric definitions
65
+ // ---------------------------------------------------------------------------
66
+ /**
67
+ * nexus_mcp_tool_calls_total — counts every tools/call dispatch.
68
+ *
69
+ * Labels:
70
+ * tool — MCP tool name (e.g. `nexus.memory_search`)
71
+ * status — `'success'` | `'error'` | `'not_implemented'`
72
+ * client — normalised client name from KNOWN_CLIENTS (or `'unknown'`)
73
+ */
74
+ export const MCP_TOOL_CALLS_TOTAL = new Counter({
75
+ name: 'nexus_mcp_tool_calls_total',
76
+ help: 'Total number of MCP tool/call requests dispatched, labelled by tool name, outcome, and client identity',
77
+ labelNames: ['tool', 'status', 'client'],
78
+ registers: [registry],
79
+ });
80
+ /**
81
+ * nexus_mcp_tool_duration_seconds — latency histogram per tool.
82
+ *
83
+ * Buckets cover sub-millisecond to 10 s to capture both local (fast) and
84
+ * remote Nexus API (network-bound) call distributions.
85
+ *
86
+ * Labels:
87
+ * tool — MCP tool name
88
+ */
89
+ export const MCP_TOOL_DURATION_SECONDS = new Histogram({
90
+ name: 'nexus_mcp_tool_duration_seconds',
91
+ help: 'Latency of MCP tool/call handler execution in seconds',
92
+ labelNames: ['tool'],
93
+ buckets: [0.001, 0.005, 0.01, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10],
94
+ registers: [registry],
95
+ });
96
+ /**
97
+ * nexus_mcp_tools_list_calls_total — counts every tools/list request.
98
+ *
99
+ * Labels:
100
+ * client — normalised client name
101
+ */
102
+ export const MCP_TOOLS_LIST_CALLS_TOTAL = new Counter({
103
+ name: 'nexus_mcp_tools_list_calls_total',
104
+ help: 'Total number of MCP tools/list requests, labelled by client identity',
105
+ labelNames: ['client'],
106
+ registers: [registry],
107
+ });
108
+ /**
109
+ * nexus_mcp_tool_description_version — Info-style gauge exposing the schema hash.
110
+ *
111
+ * Set to 1.0 with a `hash` label containing the first 8 characters of the
112
+ * SHA-256 of the concatenated tool description strings. Alerts fire when
113
+ * the hash drifts between replicas or between server restarts.
114
+ *
115
+ * Labels:
116
+ * hash — 8-char hex prefix of SHA-256(all tool descriptions)
117
+ */
118
+ export const MCP_TOOL_DESCRIPTION_VERSION = new Gauge({
119
+ name: 'nexus_mcp_tool_description_version',
120
+ help: 'Info metric: always 1, label hash identifies the tool-description schema version (SHA-256 prefix)',
121
+ labelNames: ['hash'],
122
+ registers: [registry],
123
+ });
124
+ /**
125
+ * nexus_mcp_unknown_client_total — debug counter for cardinality guard.
126
+ *
127
+ * Incremented when a raw client name is not in KNOWN_CLIENTS. The `raw`
128
+ * label carries the original string so operators can identify clients to
129
+ * add to the allowlist.
130
+ *
131
+ * Labels:
132
+ * raw — the raw client identifier as received (verbatim)
133
+ */
134
+ export const MCP_UNKNOWN_CLIENT_TOTAL = new Counter({
135
+ name: 'nexus_mcp_unknown_client_total',
136
+ help: 'Debug counter: raw client identifiers that were not in the known-client allowlist',
137
+ labelNames: ['raw'],
138
+ registers: [registry],
139
+ });
140
+ // ---------------------------------------------------------------------------
141
+ // 4. Public helper functions
142
+ // ---------------------------------------------------------------------------
143
+ /**
144
+ * Record a tool/call dispatch.
145
+ *
146
+ * @param tool - MCP tool name (e.g. `'nexus.memory_search'`)
147
+ * @param status - outcome string (`'success'`, `'error'`, `'not_implemented'`)
148
+ * @param client - raw client identifier; normalised internally against the allowlist
149
+ */
150
+ export function emitToolCall(tool, status, client) {
151
+ const normClient = normaliseClient(client);
152
+ MCP_TOOL_CALLS_TOTAL.inc({ tool, status, client: normClient });
153
+ if (normClient === 'unknown') {
154
+ emitUnknownClient(client);
155
+ }
156
+ }
157
+ /**
158
+ * Record a tools/list request.
159
+ *
160
+ * @param client - raw client identifier; normalised internally against the allowlist
161
+ */
162
+ export function emitToolsList(client) {
163
+ const normClient = normaliseClient(client);
164
+ MCP_TOOLS_LIST_CALLS_TOTAL.inc({ client: normClient });
165
+ if (normClient === 'unknown') {
166
+ emitUnknownClient(client);
167
+ }
168
+ }
169
+ /**
170
+ * Record an observation in the tool duration histogram.
171
+ *
172
+ * Call this with `tool` and the elapsed time in seconds. Designed so callers
173
+ * wrap their handler with `Date.now()` before/after and pass
174
+ * `(end - start) / 1000`.
175
+ *
176
+ * @param tool - MCP tool name
177
+ * @param durationSec - elapsed time in seconds
178
+ */
179
+ export function emitToolDuration(tool, durationSec) {
180
+ MCP_TOOL_DURATION_SECONDS.observe({ tool }, durationSec);
181
+ }
182
+ /**
183
+ * Increment the debug counter for an unknown client.
184
+ *
185
+ * Called internally by `emitToolCall` and `emitToolsList`; exposed so callers
186
+ * can also emit directly if they detect the anomaly before dispatching.
187
+ *
188
+ * @param rawName - raw client string that was not in the allowlist
189
+ */
190
+ export function emitUnknownClient(rawName) {
191
+ MCP_UNKNOWN_CLIENT_TOTAL.inc({ raw: rawName });
192
+ }
193
+ /**
194
+ * Register the tool-description schema hash in the Info gauge.
195
+ *
196
+ * Set this once during startup after the tool registry is built. Pass the
197
+ * first 8 hex characters of SHA-256(concatenated tool descriptions) as `hash`.
198
+ *
199
+ * @param hash - 8-char hex prefix of the schema hash
200
+ */
201
+ export function setDescriptionHash(hash) {
202
+ MCP_TOOL_DESCRIPTION_VERSION.set({ hash }, 1);
203
+ }
204
+ // ---------------------------------------------------------------------------
205
+ // 5. Metrics HTTP server (independent of stdio/HTTP MCP transport)
206
+ // ---------------------------------------------------------------------------
207
+ /**
208
+ * Start the Prometheus metrics scrape endpoint.
209
+ *
210
+ * Opens an HTTP server on `process.env.NEXUS_METRICS_PORT ?? 9090`. The
211
+ * server only handles `GET /metrics`; all other paths return 404.
212
+ *
213
+ * This function must be called from `src/index.ts` main() (parent session
214
+ * wires the call). It is intentionally fire-and-forget — it does not compete
215
+ * with the MCP transport for stdin/stdout.
216
+ */
217
+ export async function startMetricsServer() {
218
+ const port = Number.parseInt(process.env.NEXUS_METRICS_PORT ?? '9090', 10);
219
+ const server = http.createServer(async (req, res) => {
220
+ if (req.method === 'GET' && req.url === '/metrics') {
221
+ try {
222
+ const output = await registry.metrics();
223
+ const contentType = registry.contentType;
224
+ res.writeHead(200, { 'Content-Type': contentType });
225
+ res.end(output);
226
+ }
227
+ catch (err) {
228
+ const msg = err instanceof Error ? err.message : String(err);
229
+ res.writeHead(500, { 'Content-Type': 'text/plain' });
230
+ res.end(`metrics collection error: ${msg}`);
231
+ }
232
+ }
233
+ else {
234
+ res.writeHead(404, { 'Content-Type': 'text/plain' });
235
+ res.end('not found');
236
+ }
237
+ });
238
+ await new Promise((resolve) => {
239
+ server.listen(port, () => {
240
+ console.error(`nexusm-mcp-server metrics listening on http://0.0.0.0:${port}/metrics`);
241
+ resolve();
242
+ });
243
+ });
244
+ }
245
+ //# sourceMappingURL=metrics.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"metrics.js","sourceRoot":"","sources":["../src/metrics.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Tool: nexus.context_retrieve
3
+ *
4
+ * Aggregated retrieval over memories + conversation turns + knowledge entities.
5
+ * Schema locked in `openspec/changes/us-037-mcp-server-exposure/proposal.md`
6
+ * §"R2 工具 Schema 锁定" Tool 1 (and R2.1 grep corrections).
7
+ *
8
+ * Wave 2 (TASK-009): full impl wiring to `@nexusm/sdk` ContextService.retrieve().
9
+ * Implements:
10
+ * - R2.1 ai D-1: retrieve_id banner in MCP content text (LLM-visible)
11
+ * - R2.1 ai D-2 / A2-D-4: as_of cap (> 90 days in the past → InvalidParams)
12
+ * - R2 M-3: partial-degradation passthrough via outputSchema.errors +
13
+ * _warnings string array
14
+ *
15
+ * Auth / SDK client construction notes:
16
+ * - The SDK client is instantiated lazily on first call (memoised) from the
17
+ * env-derived AuthConfig (TASK-004). This keeps the module side-effect
18
+ * free at import time — important for `tools/list` which is called
19
+ * before the transport is fully up.
20
+ * - For testability, the client factory is exported and can be replaced
21
+ * via `__setClientForTesting`. Production code uses the default factory
22
+ * which reads `process.env` once.
23
+ */
24
+ import type { ContextRequest, ContextRetrieveResponse } from '@nexusm/sdk';
25
+ import type { ToolDefinition } from './types.js';
26
+ /** Validate that `as_of` (if present) is ISO 8601 *with* timezone and not
27
+ * more than 90 days in the past. Throws a `NexusError(InvalidParams)` on
28
+ * any violation — caller must invoke this BEFORE constructing the SDK
29
+ * request so the SDK is never reached for invalid input.
30
+ *
31
+ * Location decision (TASK-013 §"as_of cap helper"): kept in context.ts
32
+ * rather than extracted to errors.ts or a shared validation.ts. Date
33
+ * validation is tool-domain logic, not error taxonomy; moving it to
34
+ * errors.ts would create conceptual pollution and a circular-dep risk
35
+ * (errors.ts importing from tools/). A new validation.ts for a single
36
+ * function is not warranted. */
37
+ export declare function validateAsOf(asOf: string | undefined, now?: Date): void;
38
+ /** Minimal subset of NexusClient used by this handler — keeps the mock
39
+ * surface tiny in tests. */
40
+ export interface ContextClient {
41
+ context: {
42
+ retrieve: (req: ContextRequest) => Promise<ContextRetrieveResponse>;
43
+ };
44
+ }
45
+ /** @internal — for tests. Replace the cached client. Pass `null` to reset. */
46
+ export declare function __setClientForTesting(client: ContextClient | null): void;
47
+ export declare const contextRetrieveTool: ToolDefinition;
48
+ //# sourceMappingURL=context.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../../src/tools/context.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAIH,OAAO,KAAK,EAAE,cAAc,EAAE,uBAAuB,EAAE,MAAM,aAAa,CAAC;AAI3E,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AA6BjD;;;;;;;;;;iCAUiC;AACjC,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,EAAE,GAAG,GAAE,IAAiB,GAAG,IAAI,CAmCnF;AAMD;6BAC6B;AAC7B,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE;QAAE,QAAQ,EAAE,CAAC,GAAG,EAAE,cAAc,KAAK,OAAO,CAAC,uBAAuB,CAAC,CAAA;KAAE,CAAC;CAClF;AAID,8EAA8E;AAC9E,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,aAAa,GAAG,IAAI,GAAG,IAAI,CAExE;AA6HD,eAAO,MAAM,mBAAmB,EAAE,cAmDjC,CAAC"}
@@ -0,0 +1,229 @@
1
+ /**
2
+ * Tool: nexus.context_retrieve
3
+ *
4
+ * Aggregated retrieval over memories + conversation turns + knowledge entities.
5
+ * Schema locked in `openspec/changes/us-037-mcp-server-exposure/proposal.md`
6
+ * §"R2 工具 Schema 锁定" Tool 1 (and R2.1 grep corrections).
7
+ *
8
+ * Wave 2 (TASK-009): full impl wiring to `@nexusm/sdk` ContextService.retrieve().
9
+ * Implements:
10
+ * - R2.1 ai D-1: retrieve_id banner in MCP content text (LLM-visible)
11
+ * - R2.1 ai D-2 / A2-D-4: as_of cap (> 90 days in the past → InvalidParams)
12
+ * - R2 M-3: partial-degradation passthrough via outputSchema.errors +
13
+ * _warnings string array
14
+ *
15
+ * Auth / SDK client construction notes:
16
+ * - The SDK client is instantiated lazily on first call (memoised) from the
17
+ * env-derived AuthConfig (TASK-004). This keeps the module side-effect
18
+ * free at import time — important for `tools/list` which is called
19
+ * before the transport is fully up.
20
+ * - For testability, the client factory is exported and can be replaced
21
+ * via `__setClientForTesting`. Production code uses the default factory
22
+ * which reads `process.env` once.
23
+ */
24
+ import { NexusClient } from '@nexusm/sdk';
25
+ import { loadAuthConfig } from '../auth.js';
26
+ import { NexusError, McpErrorCode, isAxiosLikeError, mapHttpStatusToMcpError } from '../errors.js';
27
+ const NAME = 'nexus.context_retrieve';
28
+ /** 90-day cap from R2.1 ai D-2 / A2-D-4. Expressed in ms for precise math. */
29
+ const AS_OF_MAX_AGE_MS = 90 * 24 * 60 * 60 * 1000;
30
+ /** Strict ISO 8601 with timezone — `Z` or `±HH:MM` suffix required.
31
+ * Mirrors the SDK Zod schema rejection of naive datetimes (see
32
+ * packages/nexus-sdk-js/src/schemas/context.ts). We re-validate at the
33
+ * MCP layer so a clear MCP error surfaces *before* SDK call (per
34
+ * test case 5). */
35
+ const ISO_8601_WITH_TZ = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})$/;
36
+ /** Validate that `as_of` (if present) is ISO 8601 *with* timezone and not
37
+ * more than 90 days in the past. Throws a `NexusError(InvalidParams)` on
38
+ * any violation — caller must invoke this BEFORE constructing the SDK
39
+ * request so the SDK is never reached for invalid input.
40
+ *
41
+ * Location decision (TASK-013 §"as_of cap helper"): kept in context.ts
42
+ * rather than extracted to errors.ts or a shared validation.ts. Date
43
+ * validation is tool-domain logic, not error taxonomy; moving it to
44
+ * errors.ts would create conceptual pollution and a circular-dep risk
45
+ * (errors.ts importing from tools/). A new validation.ts for a single
46
+ * function is not warranted. */
47
+ export function validateAsOf(asOf, now = new Date()) {
48
+ if (asOf === undefined)
49
+ return;
50
+ if (!ISO_8601_WITH_TZ.test(asOf)) {
51
+ throw new NexusError(`as_of must be ISO 8601 with timezone (e.g. "2026-01-01T00:00:00Z" or "2026-01-01T00:00:00+08:00"); got ${JSON.stringify(asOf)}`, McpErrorCode.InvalidParams, 422);
52
+ }
53
+ const parsed = Date.parse(asOf);
54
+ if (Number.isNaN(parsed)) {
55
+ throw new NexusError(`as_of is not a valid datetime: ${JSON.stringify(asOf)}`, McpErrorCode.InvalidParams, 422);
56
+ }
57
+ const ageMs = now.getTime() - parsed;
58
+ if (ageMs > AS_OF_MAX_AGE_MS) {
59
+ throw new NexusError(`as_of is more than 90 days in the past (age=${Math.floor(ageMs / 86_400_000)}d, max=90d). Per US-037 R2.1 ai D-2 / A2-D-4 cap.`, McpErrorCode.InvalidParams, 422);
60
+ }
61
+ // Wave 2 mid_audit qa-engineer I-3: future as_of is semantically ill-defined
62
+ // (cannot retrieve memories "from the future"); reject explicitly rather
63
+ // than letting the SDK / backend silently accept-then-fail.
64
+ if (ageMs < 0) {
65
+ throw new NexusError(`as_of is in the future (now=${now.toISOString()}, as_of=${asOf}). Cannot retrieve memories anchored to a future point in time.`, McpErrorCode.InvalidParams, 422);
66
+ }
67
+ }
68
+ let cachedClient = null;
69
+ /** @internal — for tests. Replace the cached client. Pass `null` to reset. */
70
+ export function __setClientForTesting(client) {
71
+ cachedClient = client;
72
+ }
73
+ function defaultClientFactory() {
74
+ const cfg = loadAuthConfig();
75
+ // NexusClient takes `apiKey` (mapped from NEXUS_API_TOKEN), `tenantId`,
76
+ // and `baseUrl` (mapped from NEXUS_API_URL).
77
+ return new NexusClient({
78
+ apiKey: cfg.apiToken,
79
+ tenantId: cfg.tenantId,
80
+ baseUrl: cfg.apiUrl,
81
+ });
82
+ }
83
+ function getClient() {
84
+ if (cachedClient === null) {
85
+ cachedClient = defaultClientFactory();
86
+ }
87
+ return cachedClient;
88
+ }
89
+ // ---------------------------------------------------------------------------
90
+ // Handler
91
+ // ---------------------------------------------------------------------------
92
+ function buildBanner(retrieveId, summary) {
93
+ // Banner format is *exact* per test case 8 — keep the prefix stable.
94
+ return `## Retrieved context (retrieve_id=${retrieveId})\n${summary}`;
95
+ }
96
+ function summarise(resp) {
97
+ const memCount = resp.profile?.memories?.length ?? 0;
98
+ const histCount = resp.history?.messages?.length ?? 0;
99
+ const entCount = resp.graph?.entities?.length ?? 0;
100
+ return `memories=${memCount}, conversation_turns=${histCount}, knowledge_entities=${entCount}`;
101
+ }
102
+ async function handler(args) {
103
+ // ---- Input narrowing (schema enforcement is the MCP server's job; we
104
+ // ---- defensively coerce here for runtime safety) -------------------
105
+ const userId = args.user_id;
106
+ const query = args.query;
107
+ const limit = args.limit ?? 10;
108
+ const asOf = args.as_of;
109
+ // ---- R2.1 ai D-2: as_of cap check BEFORE SDK call -------------------
110
+ validateAsOf(asOf);
111
+ // ---- Build SDK ContextRequest ---------------------------------------
112
+ const sdkRequest = {
113
+ user_id: userId,
114
+ query,
115
+ // The MCP `limit` is a coarse total cap. Map it to all three SDK
116
+ // per-layer limits so the LLM gets predictable totals regardless of
117
+ // which layers return data. (Aggressive but simple — tunable later.)
118
+ profile_limit: limit,
119
+ history_limit: limit,
120
+ graph_limit: limit,
121
+ };
122
+ if (asOf !== undefined)
123
+ sdkRequest.as_of = asOf;
124
+ // ---- Call SDK; translate errors to NexusError using TASK-013 mapping ---
125
+ let resp;
126
+ try {
127
+ resp = (await getClient().context.retrieve(sdkRequest));
128
+ }
129
+ catch (err) {
130
+ // Re-throw NexusError unchanged (already translated, e.g. from validateAsOf).
131
+ if (err instanceof NexusError)
132
+ throw err;
133
+ // Axios-like error from the SDK: use the canonical HTTP→MCP mapping.
134
+ if (isAxiosLikeError(err)) {
135
+ throw mapHttpStatusToMcpError(err.response?.status ?? null, err.response?.data, err.response?.headers);
136
+ }
137
+ // Unknown / plain Error: surface as InternalError.
138
+ const msg = err instanceof Error ? err.message : String(err);
139
+ throw new NexusError(`nexus.context_retrieve failed: ${msg}`, McpErrorCode.InternalError);
140
+ }
141
+ // ---- Map SDK response → MCP outputSchema shape ----------------------
142
+ const retrieveId = resp.retrieve_id ?? '';
143
+ const memories = resp.profile?.memories ?? [];
144
+ const conversationTurns = resp.history?.messages ?? [];
145
+ const knowledgeEntities = resp.graph?.entities ?? [];
146
+ const errors = resp.errors ?? null;
147
+ // R2 M-3 partial-degradation warning channel
148
+ const warnings = [];
149
+ if (errors !== null && Object.keys(errors).length > 0) {
150
+ for (const [layer, msg] of Object.entries(errors)) {
151
+ warnings.push(`layer "${layer}" degraded: ${msg}`);
152
+ }
153
+ }
154
+ const structured = {
155
+ retrieve_id: retrieveId,
156
+ memories,
157
+ conversation_turns: conversationTurns,
158
+ knowledge_entities: knowledgeEntities,
159
+ errors,
160
+ _warnings: warnings,
161
+ };
162
+ // R2.1 ai D-1: retrieve_id banner in user-visible content[0].text so the
163
+ // LLM can read it and pass to nexus.memory_feedback. Structured payload
164
+ // is duplicated in a second content item so MCP clients that only render
165
+ // text still see the data, while structured-output clients read
166
+ // `structuredContent`.
167
+ return {
168
+ content: [
169
+ {
170
+ type: 'text',
171
+ text: buildBanner(retrieveId, summarise(resp)),
172
+ },
173
+ {
174
+ type: 'text',
175
+ text: JSON.stringify(structured, null, 2),
176
+ },
177
+ ],
178
+ structuredContent: structured,
179
+ isError: false,
180
+ };
181
+ }
182
+ export const contextRetrieveTool = {
183
+ name: NAME,
184
+ description: 'Use when user asks anything that might need context from prior sessions, prior conversations, or stored facts. Single call returns relevant memories, recent conversation turns, and knowledge entities together. Prefer this over nexus.memory_search when query is open-ended.',
185
+ inputSchema: {
186
+ type: 'object',
187
+ properties: {
188
+ user_id: {
189
+ type: 'string',
190
+ description: 'User identifier within tenant scope. Required per-call.',
191
+ },
192
+ query: { type: 'string' },
193
+ limit: { type: 'integer', default: 10, minimum: 1, maximum: 50 },
194
+ as_of: {
195
+ type: 'string',
196
+ format: 'date-time',
197
+ description: 'ISO 8601 with timezone, max 90 days in the past. Default: NULL (current valid memories, no anchor inheritance from previous calls). Do NOT infer as_of from conversation context — user must explicitly express time intent.',
198
+ },
199
+ },
200
+ required: ['user_id', 'query'],
201
+ },
202
+ outputSchema: {
203
+ type: 'object',
204
+ properties: {
205
+ retrieve_id: {
206
+ type: 'string',
207
+ format: 'uuid',
208
+ description: 'PASS THIS to nexus.memory_feedback to rate this retrieval. Save it before continuing the conversation.',
209
+ },
210
+ memories: { type: 'array', items: { type: 'object' } },
211
+ conversation_turns: { type: 'array', items: { type: 'object' } },
212
+ knowledge_entities: { type: 'array', items: { type: 'object' } },
213
+ errors: {
214
+ type: 'object',
215
+ additionalProperties: { type: 'string' },
216
+ nullable: true,
217
+ description: 'Non-empty if partial degradation (dict[str,str] type, key=layer, value=error message). Null when all layers healthy. HTTP 200 + errors!=null indicates partial result.',
218
+ },
219
+ _warnings: {
220
+ type: 'array',
221
+ items: { type: 'string' },
222
+ description: 'Partial-result warning channel; populated when one or more retrieval layers degraded.',
223
+ },
224
+ },
225
+ required: ['retrieve_id', 'memories', 'conversation_turns', 'knowledge_entities'],
226
+ },
227
+ handler,
228
+ };
229
+ //# sourceMappingURL=context.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context.js","sourceRoot":"","sources":["../../src/tools/context.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Tool registry. Exports the 4 MVP tools registered with the MCP server in
3
+ * `src/index.ts`. The order here is the order returned by `tools/list`.
4
+ *
5
+ * Adding/removing a tool: update this file + add corresponding fixture in
6
+ * `tests/unit/schema_sync.test.ts`.
7
+ */
8
+ import type { ToolDefinition } from './types.js';
9
+ export declare const tools: readonly ToolDefinition[];
10
+ export declare const toolsByName: ReadonlyMap<string, ToolDefinition>;
11
+ export type { ToolDefinition } from './types.js';
12
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/tools/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAMH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAEjD,eAAO,MAAM,KAAK,EAAE,SAAS,cAAc,EAKjC,CAAC;AAEX,eAAO,MAAM,WAAW,EAAE,WAAW,CAAC,MAAM,EAAE,cAAc,CAE3D,CAAC;AAEF,YAAY,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC"}
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Tool registry. Exports the 4 MVP tools registered with the MCP server in
3
+ * `src/index.ts`. The order here is the order returned by `tools/list`.
4
+ *
5
+ * Adding/removing a tool: update this file + add corresponding fixture in
6
+ * `tests/unit/schema_sync.test.ts`.
7
+ */
8
+ import { contextRetrieveTool } from './context.js';
9
+ import { memoryCreateTool } from './memory_create.js';
10
+ import { memoryFeedbackTool } from './memory_feedback.js';
11
+ import { memorySearchTool } from './memory_search.js';
12
+ export const tools = [
13
+ contextRetrieveTool,
14
+ memorySearchTool,
15
+ memoryCreateTool,
16
+ memoryFeedbackTool,
17
+ ];
18
+ export const toolsByName = new Map(tools.map((t) => [t.name, t]));
19
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/tools/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AACnD,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAC1D,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAGtD,MAAM,CAAC,MAAM,KAAK,GAA8B;IAC9C,mBAAmB;IACnB,gBAAgB;IAChB,gBAAgB;IAChB,kBAAkB;CACV,CAAC;AAEX,MAAM,CAAC,MAAM,WAAW,GAAwC,IAAI,GAAG,CACrE,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CAC9B,CAAC"}
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Tool: nexus.memory_create (US-037 TASK-011, Wave 2).
3
+ *
4
+ * Persist a new memory via the Nexus REST API through `@nexusm/sdk`.
5
+ *
6
+ * Schema is locked in proposal §"R2 工具 Schema 锁定" Tool 3 (+ R2.1 grep
7
+ * corrections + ai R2 D-8 metadata cap + ai R2 D-10 conflict_resolution
8
+ * enum lock) and MUST be preserved verbatim — `tests/unit/schema_sync.test.ts`
9
+ * is the parity gate.
10
+ *
11
+ * Validation responsibilities (the MCP dispatcher does NOT validate inputs
12
+ * against the declared inputSchema):
13
+ *
14
+ * 1. `valid_until_source` — 5-value enum locked in R2.1
15
+ * (permanent / extracted / sdk_provided / extraction_failed /
16
+ * superseded_by_conflict). Any other value → `InvalidParams`.
17
+ *
18
+ * 2. `metadata` cap (proposal §ai R2 D-8):
19
+ * - ≤ 10 keys
20
+ * - each string value ≤ 200 chars
21
+ * Over-cap → `InvalidParams` BEFORE the SDK call.
22
+ *
23
+ * 3. Response `conflict_resolution.status` — 9-value enum locked in
24
+ * §ai R2 D-10 (matches migration 020 `memory_conflicts.resolution_status`
25
+ * CHECK). Any other value → `InternalError` (treat as backend drift
26
+ * or server bug; do NOT silently echo).
27
+ *
28
+ * Client construction matches the sibling pattern in `memory_search.ts`:
29
+ * lazy `NexusClient` singleton from `loadAuthConfig()`, with a
30
+ * `__resetClientForTesting()` seam so `vi.mock('@nexusm/sdk', ...)` can
31
+ * re-construct against the fresh mock between cases.
32
+ */
33
+ import { type ToolDefinition } from './types.js';
34
+ /** Test-only seam — mirrors `memory_search.ts`. @internal */
35
+ export declare function __resetClientForTesting(): void;
36
+ export declare const memoryCreateTool: ToolDefinition;
37
+ //# sourceMappingURL=memory_create.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"memory_create.d.ts","sourceRoot":"","sources":["../../src/tools/memory_create.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAMH,OAAO,EAAE,KAAK,cAAc,EAAE,MAAM,YAAY,CAAC;AAuDjD,6DAA6D;AAC7D,wBAAgB,uBAAuB,IAAI,IAAI,CAE9C;AA6BD,eAAO,MAAM,gBAAgB,EAAE,cA4L9B,CAAC"}