@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,242 @@
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 { NexusClient } from '@nexusm/sdk';
34
+ import { loadAuthConfig } from '../auth.js';
35
+ import { McpErrorCode, NexusError, isAxiosLikeError, mapHttpStatusToMcpError } from '../errors.js';
36
+ const NAME = 'nexus.memory_create';
37
+ /** memory_type enum locked in proposal §R2 Tool 3 (matches SDK MemoryType). */
38
+ const MEMORY_TYPE_ENUM = ['episodic', 'semantic', 'procedural'];
39
+ const DEFAULT_MEMORY_TYPE = 'semantic';
40
+ /** valid_until_source enum locked in proposal §R2.1 (5 values, backend Literal). */
41
+ const VALID_UNTIL_SOURCE_ENUM = [
42
+ 'permanent',
43
+ 'extracted',
44
+ 'sdk_provided',
45
+ 'extraction_failed',
46
+ 'superseded_by_conflict',
47
+ ];
48
+ /**
49
+ * conflict_resolution.status enum locked in proposal §ai R2 D-10, matching
50
+ * migration 020 `memory_conflicts.resolution_status` 9-state CHECK constraint.
51
+ */
52
+ const CONFLICT_STATUS_ENUM = [
53
+ 'resolved_keep_new',
54
+ 'resolved_keep_old',
55
+ 'resolved_merge',
56
+ 'resolved_keep_both',
57
+ 'pending_judge',
58
+ 'failed_llm',
59
+ 'failed_nli',
60
+ 'skipped_disabled',
61
+ 'no_conflict',
62
+ ];
63
+ /** Metadata cap per proposal §ai R2 D-8. */
64
+ const METADATA_MAX_KEYS = 10;
65
+ const METADATA_MAX_VALUE_LEN = 200;
66
+ /** Lazily-instantiated SDK client. Reset by `__resetClientForTesting`. */
67
+ let clientSingleton = null;
68
+ function getClient() {
69
+ if (clientSingleton === null) {
70
+ const auth = loadAuthConfig();
71
+ clientSingleton = new NexusClient({
72
+ apiKey: auth.apiToken,
73
+ baseUrl: auth.apiUrl,
74
+ tenantId: auth.tenantId,
75
+ });
76
+ }
77
+ return clientSingleton;
78
+ }
79
+ /** Test-only seam — mirrors `memory_search.ts`. @internal */
80
+ export function __resetClientForTesting() {
81
+ clientSingleton = null;
82
+ }
83
+ /**
84
+ * Guard a backend conflict_resolution payload against the locked 9-state
85
+ * enum. Unknown status → drift → `InternalError` (proposal §ai R2 D-10).
86
+ */
87
+ function validateConflictResolution(cr) {
88
+ if (cr === null || cr === undefined)
89
+ return null;
90
+ if (typeof cr !== 'object') {
91
+ throw new NexusError('SDK returned non-object conflict_resolution', McpErrorCode.InternalError);
92
+ }
93
+ const obj = cr;
94
+ const status = obj.status;
95
+ if (typeof status !== 'string' || !CONFLICT_STATUS_ENUM.includes(status)) {
96
+ throw new NexusError(`SDK returned unknown conflict_resolution.status="${String(status)}" (backend drift; ` +
97
+ `expected one of ${CONFLICT_STATUS_ENUM.join('|')})`, McpErrorCode.InternalError);
98
+ }
99
+ return obj;
100
+ }
101
+ export const memoryCreateTool = {
102
+ name: NAME,
103
+ description: "Persist a new memory. Use when user explicitly asks to 'remember X' or when storing structured facts (preferences, decisions, code snippets with language tag). Set memory_type to 'episodic' for events, 'semantic' for facts, 'procedural' for how-tos.",
104
+ inputSchema: {
105
+ type: 'object',
106
+ properties: {
107
+ user_id: { type: 'string' },
108
+ content: { type: 'string' },
109
+ memory_type: {
110
+ type: 'string',
111
+ enum: [...MEMORY_TYPE_ENUM],
112
+ default: 'semantic',
113
+ },
114
+ metadata: {
115
+ type: 'object',
116
+ additionalProperties: true,
117
+ description: "Free-form structured tags (e.g., {language: 'python', tags: ['snippet', 'react-hooks']}). " +
118
+ 'Use ≤ 10 keys, value length ≤ 200 chars (proposal §ai R2 D-8 cap; over-cap → InvalidParams).',
119
+ },
120
+ valid_until: { type: 'string', format: 'date-time', nullable: true },
121
+ valid_until_source: {
122
+ type: 'string',
123
+ enum: [...VALID_UNTIL_SOURCE_ENUM],
124
+ nullable: true,
125
+ description: 'v6 US-035 temporal validity (backend ValidUntilSource Literal, 5 values, locked in proposal §R2.1). ' +
126
+ "MCP client typically passes 'sdk_provided' (user-declared) or omits to let backend worker auto-extract. " +
127
+ 'Any value outside the 5-enum is rejected at args parse stage with InvalidParams.',
128
+ },
129
+ agent_id: { type: 'string', nullable: true },
130
+ },
131
+ required: ['user_id', 'content'],
132
+ },
133
+ outputSchema: {
134
+ type: 'object',
135
+ properties: {
136
+ memory_id: { type: 'string', format: 'uuid' },
137
+ created_at: { type: 'string', format: 'date-time' },
138
+ conflict_resolution: {
139
+ type: 'object',
140
+ nullable: true,
141
+ description: 'If v6 US-036 ConflictResolver is enabled (per-tenant feature flag), resolution_status echoed here. ' +
142
+ 'NULL when feature flag disabled. status enum locked to 9 values (migration 020 CHECK).',
143
+ properties: {
144
+ status: { type: 'string', enum: [...CONFLICT_STATUS_ENUM] },
145
+ superseded_memory_ids: { type: 'array', items: { type: 'string' } },
146
+ },
147
+ required: ['status'],
148
+ },
149
+ },
150
+ required: ['memory_id', 'created_at'],
151
+ },
152
+ handler: async (args) => {
153
+ // ---- Required fields ----
154
+ if (typeof args.user_id !== 'string' || args.user_id.length === 0) {
155
+ throw new NexusError('user_id is required (non-empty string)', McpErrorCode.InvalidParams, 422);
156
+ }
157
+ if (typeof args.content !== 'string' || args.content.length === 0) {
158
+ throw new NexusError('content is required (non-empty string)', McpErrorCode.InvalidParams, 422);
159
+ }
160
+ // ---- memory_type enum + default ----
161
+ let memory_type;
162
+ if (args.memory_type === undefined || args.memory_type === null) {
163
+ memory_type = DEFAULT_MEMORY_TYPE;
164
+ }
165
+ else if (typeof args.memory_type === 'string' &&
166
+ MEMORY_TYPE_ENUM.includes(args.memory_type)) {
167
+ memory_type = args.memory_type;
168
+ }
169
+ else {
170
+ throw new NexusError(`Invalid memory_type "${String(args.memory_type)}". Allowed: ${MEMORY_TYPE_ENUM.join(', ')}.`, McpErrorCode.InvalidParams, 422);
171
+ }
172
+ // ---- metadata cap (proposal §ai R2 D-8) ----
173
+ let metadata;
174
+ if (args.metadata !== undefined && args.metadata !== null) {
175
+ if (typeof args.metadata !== 'object' || Array.isArray(args.metadata)) {
176
+ throw new NexusError('metadata must be an object', McpErrorCode.InvalidParams, 422);
177
+ }
178
+ metadata = args.metadata;
179
+ const keys = Object.keys(metadata);
180
+ if (keys.length > METADATA_MAX_KEYS) {
181
+ throw new NexusError(`metadata exceeds cap of ${METADATA_MAX_KEYS} keys (got ${keys.length})`, McpErrorCode.InvalidParams, 422);
182
+ }
183
+ for (const [k, v] of Object.entries(metadata)) {
184
+ if (typeof v === 'string' && v.length > METADATA_MAX_VALUE_LEN) {
185
+ throw new NexusError(`metadata.${k} value length ${v.length} exceeds cap of ${METADATA_MAX_VALUE_LEN}`, McpErrorCode.InvalidParams, 422);
186
+ }
187
+ }
188
+ }
189
+ // ---- valid_until_source enum (R2.1 LOCKED) ----
190
+ let valid_until_source;
191
+ if (args.valid_until_source !== undefined && args.valid_until_source !== null) {
192
+ if (typeof args.valid_until_source !== 'string' ||
193
+ !VALID_UNTIL_SOURCE_ENUM.includes(args.valid_until_source)) {
194
+ throw new NexusError(`Invalid valid_until_source "${String(args.valid_until_source)}". ` +
195
+ `Allowed: ${VALID_UNTIL_SOURCE_ENUM.join(', ')}.`, McpErrorCode.InvalidParams, 422);
196
+ }
197
+ valid_until_source = args.valid_until_source;
198
+ }
199
+ const body = {
200
+ user_id: args.user_id,
201
+ content: args.content,
202
+ memory_type,
203
+ };
204
+ if (metadata !== undefined)
205
+ body.metadata = metadata;
206
+ if (typeof args.valid_until === 'string')
207
+ body.valid_until = args.valid_until;
208
+ if (valid_until_source !== undefined)
209
+ body.valid_until_source = valid_until_source;
210
+ if (typeof args.agent_id === 'string')
211
+ body.agent_id = args.agent_id;
212
+ const client = getClient();
213
+ // Wave 2B mid_audit-to-pre_merge fix: wrap SDK call + map errors per §M-3
214
+ // (mirrors context.ts pattern). Without this, axios-like 401/403/429
215
+ // surface as JSON-RPC InternalError instead of Unauthorized/RateLimited.
216
+ let created;
217
+ try {
218
+ created = (await client.memories.create(body));
219
+ }
220
+ catch (err) {
221
+ if (isAxiosLikeError(err)) {
222
+ const status = err.response?.status ?? null;
223
+ const respBody = err.response?.data ?? null;
224
+ const headers = err.response?.headers;
225
+ throw mapHttpStatusToMcpError(status, respBody, headers);
226
+ }
227
+ throw mapHttpStatusToMcpError(null, null);
228
+ }
229
+ // ---- conflict_resolution drift guard (proposal §ai R2 D-10) ----
230
+ const conflict = validateConflictResolution(created.conflict_resolution);
231
+ const memory_id = (created.id ?? created.memory_id);
232
+ const created_at = created.created_at;
233
+ const output = { memory_id, created_at };
234
+ if (conflict !== null)
235
+ output.conflict_resolution = conflict;
236
+ return {
237
+ content: [{ type: 'text', text: JSON.stringify(output) }],
238
+ structuredContent: output,
239
+ };
240
+ },
241
+ };
242
+ //# sourceMappingURL=memory_create.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"memory_create.js","sourceRoot":"","sources":["../../src/tools/memory_create.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Tool: nexus.memory_feedback
3
+ *
4
+ * Submit per-memory feedback on a previous nexus.context_retrieve call.
5
+ * Schema locked in proposal §"R2 工具 Schema 锁定" Tool 4 (R2.1 grep-corrected).
6
+ *
7
+ * Wave 2 (TASK-012): full handler — validates inputs, structlogs `user_id`
8
+ * for audit, then forwards a body that **excludes** `user_id` to
9
+ * `@nexusm/sdk` `FeedbackService.submit(retrieveId, body)`.
10
+ *
11
+ * R2.1 grep corrections vs R2 draft (proposal §"R2.1 grep 修正"):
12
+ * - `item_feedback[].reason` maxLength: 255 (was 500)
13
+ * - `expected_missing` maxLength: 2000 (was 500)
14
+ * - outputSchema is `{feedback_id, retrieve_id, status, created_at}`
15
+ * with status="accepted" (R2 wrote `{feedback_id, acknowledged}`,
16
+ * which does not match backend FeedbackResponse).
17
+ *
18
+ * Backend contract (R2.1 D-1, /v1/feedback/{retrieve_id} PUT):
19
+ * The request body MUST NOT contain `user_id`. The backend derives
20
+ * user_id from the retrieve_log keyed by the URL path `retrieve_id`.
21
+ * We surface `user_id` in inputSchema only so the MCP server can
22
+ * structlog it for the audit trail and metric labels. This handler
23
+ * strips it before calling the SDK.
24
+ *
25
+ * Security:
26
+ * - Bearer token is never logged: the audit structlog includes only
27
+ * {tool, user_id, retrieve_id, rating}, never headers or env values.
28
+ * - SDK errors may carry the bearer token in `cause.config.headers`.
29
+ * We never log the raw error; we wrap-and-rethrow so toJSON() (which
30
+ * omits `cause`) gates serialization.
31
+ */
32
+ import type { ToolDefinition } from './types.js';
33
+ /**
34
+ * Test helper — reset the lazy singleton between cases so each test
35
+ * picks up a fresh `vi.mock('@nexusm/sdk')` factory invocation.
36
+ */
37
+ export declare function __resetClientForTesting(): void;
38
+ /** Structlog sink. Always stderr (MCP stdio invariant — stdout is JSON-RPC). */
39
+ type AuditLogger = (line: string) => void;
40
+ /** Test helper — override the audit sink. Pair with __resetClientForTesting. */
41
+ export declare function __setAuditLoggerForTesting(fn: AuditLogger | null): void;
42
+ export declare const memoryFeedbackTool: ToolDefinition;
43
+ export {};
44
+ //# sourceMappingURL=memory_feedback.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"memory_feedback.d.ts","sourceRoot":"","sources":["../../src/tools/memory_feedback.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAOH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAoBjD;;;GAGG;AACH,wBAAgB,uBAAuB,IAAI,IAAI,CAE9C;AAED,gFAAgF;AAChF,KAAK,WAAW,GAAG,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;AAK1C,gFAAgF;AAChF,wBAAgB,0BAA0B,CAAC,EAAE,EAAE,WAAW,GAAG,IAAI,GAAG,IAAI,CAEvE;AAyHD,eAAO,MAAM,kBAAkB,EAAE,cAyGhC,CAAC"}
@@ -0,0 +1,259 @@
1
+ /**
2
+ * Tool: nexus.memory_feedback
3
+ *
4
+ * Submit per-memory feedback on a previous nexus.context_retrieve call.
5
+ * Schema locked in proposal §"R2 工具 Schema 锁定" Tool 4 (R2.1 grep-corrected).
6
+ *
7
+ * Wave 2 (TASK-012): full handler — validates inputs, structlogs `user_id`
8
+ * for audit, then forwards a body that **excludes** `user_id` to
9
+ * `@nexusm/sdk` `FeedbackService.submit(retrieveId, body)`.
10
+ *
11
+ * R2.1 grep corrections vs R2 draft (proposal §"R2.1 grep 修正"):
12
+ * - `item_feedback[].reason` maxLength: 255 (was 500)
13
+ * - `expected_missing` maxLength: 2000 (was 500)
14
+ * - outputSchema is `{feedback_id, retrieve_id, status, created_at}`
15
+ * with status="accepted" (R2 wrote `{feedback_id, acknowledged}`,
16
+ * which does not match backend FeedbackResponse).
17
+ *
18
+ * Backend contract (R2.1 D-1, /v1/feedback/{retrieve_id} PUT):
19
+ * The request body MUST NOT contain `user_id`. The backend derives
20
+ * user_id from the retrieve_log keyed by the URL path `retrieve_id`.
21
+ * We surface `user_id` in inputSchema only so the MCP server can
22
+ * structlog it for the audit trail and metric labels. This handler
23
+ * strips it before calling the SDK.
24
+ *
25
+ * Security:
26
+ * - Bearer token is never logged: the audit structlog includes only
27
+ * {tool, user_id, retrieve_id, rating}, never headers or env values.
28
+ * - SDK errors may carry the bearer token in `cause.config.headers`.
29
+ * We never log the raw error; we wrap-and-rethrow so toJSON() (which
30
+ * omits `cause`) gates serialization.
31
+ */
32
+ import { NexusClient } from '@nexusm/sdk';
33
+ import { loadAuthConfig } from '../auth.js';
34
+ import { McpErrorCode, NexusError, isAxiosLikeError, mapHttpStatusToMcpError } from '../errors.js';
35
+ const NAME = 'nexus.memory_feedback';
36
+ /** Lazy NexusClient singleton — built on first handler invocation so
37
+ * importing the module does not require env vars (matches memory_search). */
38
+ let _client = null;
39
+ function getClient() {
40
+ if (_client === null) {
41
+ const cfg = loadAuthConfig();
42
+ _client = new NexusClient({
43
+ apiKey: cfg.apiToken,
44
+ baseUrl: cfg.apiUrl,
45
+ tenantId: cfg.tenantId,
46
+ });
47
+ }
48
+ return _client;
49
+ }
50
+ /**
51
+ * Test helper — reset the lazy singleton between cases so each test
52
+ * picks up a fresh `vi.mock('@nexusm/sdk')` factory invocation.
53
+ */
54
+ export function __resetClientForTesting() {
55
+ _client = null;
56
+ }
57
+ let auditLogger = (line) => {
58
+ process.stderr.write(`${line}\n`);
59
+ };
60
+ /** Test helper — override the audit sink. Pair with __resetClientForTesting. */
61
+ export function __setAuditLoggerForTesting(fn) {
62
+ auditLogger = fn ?? ((line) => process.stderr.write(`${line}\n`));
63
+ }
64
+ /**
65
+ * Validate raw MCP `arguments` against the R2.1 schema rules that JSON
66
+ * Schema cannot express purely at the SDK layer (range, length caps).
67
+ *
68
+ * Throws {@link NexusError}(InvalidParams) on any violation — caught at
69
+ * the MCP dispatch layer (src/index.ts) and converted to a JSON-RPC
70
+ * error response per proposal §M-3. Matches the memory_search pattern.
71
+ */
72
+ function parseAndValidate(args) {
73
+ const reject = (msg) => {
74
+ throw new NexusError(msg, McpErrorCode.InvalidParams, null);
75
+ };
76
+ // user_id — required, non-empty string.
77
+ const userIdRaw = args['user_id'];
78
+ if (typeof userIdRaw !== 'string' || userIdRaw.trim() === '') {
79
+ reject("'user_id' is required and must be a non-empty string");
80
+ }
81
+ const user_id = userIdRaw.trim();
82
+ // retrieve_id — required, non-empty string.
83
+ const retrieveIdRaw = args['retrieve_id'];
84
+ if (typeof retrieveIdRaw !== 'string' || retrieveIdRaw.trim() === '') {
85
+ reject("'retrieve_id' is required and must be a non-empty string");
86
+ }
87
+ const retrieve_id = retrieveIdRaw.trim();
88
+ // rating — required, integer 1..5 (hard reject 0 / 6 / floats / non-numbers).
89
+ const ratingRaw = args['rating'];
90
+ if (typeof ratingRaw !== 'number' ||
91
+ !Number.isInteger(ratingRaw) ||
92
+ ratingRaw < 1 ||
93
+ ratingRaw > 5) {
94
+ reject("'rating' must be an integer between 1 and 5");
95
+ }
96
+ const rating = ratingRaw;
97
+ // item_feedback — optional array of {memory_id, useful, reason?}.
98
+ let item_feedback;
99
+ if (args['item_feedback'] !== undefined && args['item_feedback'] !== null) {
100
+ if (!Array.isArray(args['item_feedback'])) {
101
+ reject("'item_feedback' must be an array when provided");
102
+ }
103
+ item_feedback = args['item_feedback'].map((raw, idx) => {
104
+ if (typeof raw !== 'object' || raw === null) {
105
+ reject(`'item_feedback[${idx}]' must be an object`);
106
+ }
107
+ const item = raw;
108
+ const memory_id = item['memory_id'];
109
+ const useful = item['useful'];
110
+ if (typeof memory_id !== 'string' || memory_id.trim() === '') {
111
+ reject(`'item_feedback[${idx}].memory_id' is required (non-empty string)`);
112
+ }
113
+ if (typeof useful !== 'boolean') {
114
+ reject(`'item_feedback[${idx}].useful' is required (boolean)`);
115
+ }
116
+ const reasonRaw = item['reason'];
117
+ let reason;
118
+ if (reasonRaw !== undefined && reasonRaw !== null) {
119
+ if (typeof reasonRaw !== 'string') {
120
+ reject(`'item_feedback[${idx}].reason' must be a string when provided`);
121
+ }
122
+ if (reasonRaw.length > 255) {
123
+ reject(`'item_feedback[${idx}].reason' exceeds maxLength 255 (got ${reasonRaw.length})`);
124
+ }
125
+ reason = reasonRaw;
126
+ }
127
+ return {
128
+ memory_id: memory_id,
129
+ useful: useful,
130
+ ...(reason !== undefined ? { reason } : {}),
131
+ };
132
+ });
133
+ }
134
+ // expected_missing — optional string <= 2000 chars.
135
+ let expected_missing;
136
+ const emRaw = args['expected_missing'];
137
+ if (emRaw !== undefined && emRaw !== null) {
138
+ if (typeof emRaw !== 'string') {
139
+ reject("'expected_missing' must be a string when provided");
140
+ }
141
+ if (emRaw.length > 2000) {
142
+ reject(`'expected_missing' exceeds maxLength 2000 (got ${emRaw.length})`);
143
+ }
144
+ expected_missing = emRaw;
145
+ }
146
+ // context — optional free-form object.
147
+ let context;
148
+ const ctxRaw = args['context'];
149
+ if (ctxRaw !== undefined && ctxRaw !== null) {
150
+ if (typeof ctxRaw !== 'object' || Array.isArray(ctxRaw)) {
151
+ reject("'context' must be a plain object when provided");
152
+ }
153
+ context = ctxRaw;
154
+ }
155
+ // Build body — `user_id` deliberately omitted (R2.1 D-1).
156
+ const body = {
157
+ rating,
158
+ ...(item_feedback !== undefined ? { item_feedback } : {}),
159
+ ...(expected_missing !== undefined ? { expected_missing } : {}),
160
+ ...(context !== undefined ? { context } : {}),
161
+ };
162
+ return { user_id, retrieve_id, body };
163
+ }
164
+ export const memoryFeedbackTool = {
165
+ name: NAME,
166
+ description: 'Submit per-memory feedback on a previous nexus.context_retrieve call. Pass retrieve_id from earlier output. Rating 1-5, plus per-memory useful flag with optional reason. v5 feedback loop drives quality_score reranking.',
167
+ inputSchema: {
168
+ type: 'object',
169
+ properties: {
170
+ user_id: {
171
+ type: 'string',
172
+ description: 'MCP server **internal** audit/logging field, **not forwarded** to backend FeedbackRequest body. Backend derives user_id from retrieve_log (route is PUT /v1/feedback/{retrieve_id}). This field is only used for MCP server-side structlog + metric label.',
173
+ },
174
+ retrieve_id: {
175
+ type: 'string',
176
+ format: 'uuid',
177
+ description: 'From earlier nexus.context_retrieve output. MCP server uses this value as PUT URL path parameter.',
178
+ },
179
+ rating: { type: 'integer', minimum: 1, maximum: 5 },
180
+ item_feedback: {
181
+ type: 'array',
182
+ items: {
183
+ type: 'object',
184
+ properties: {
185
+ memory_id: { type: 'string', format: 'uuid' },
186
+ useful: { type: 'boolean' },
187
+ reason: { type: 'string', nullable: true, maxLength: 255 },
188
+ },
189
+ required: ['memory_id', 'useful'],
190
+ },
191
+ description: 'Per-memory useful flag with optional reason. Maps to backend FeedbackRequest.item_feedback[].',
192
+ },
193
+ expected_missing: {
194
+ type: 'string',
195
+ nullable: true,
196
+ maxLength: 2000,
197
+ description: 'Free-text on what relevant memories were missing from retrieval. (PII-filtered before storage by backend)',
198
+ },
199
+ context: {
200
+ type: 'object',
201
+ nullable: true,
202
+ additionalProperties: true,
203
+ description: "Free-form context for feedback (e.g., {client: 'claude-code', session_id: '...'}).",
204
+ },
205
+ },
206
+ required: ['user_id', 'retrieve_id', 'rating'],
207
+ },
208
+ outputSchema: {
209
+ type: 'object',
210
+ properties: {
211
+ feedback_id: { type: 'string', format: 'uuid' },
212
+ retrieve_id: { type: 'string', format: 'uuid' },
213
+ status: {
214
+ type: 'string',
215
+ enum: ['accepted'],
216
+ description: "Submission status (currently always 'accepted', enum reserved for future expansion)",
217
+ },
218
+ created_at: { type: 'string', format: 'date-time' },
219
+ },
220
+ required: ['feedback_id', 'retrieve_id', 'status', 'created_at'],
221
+ },
222
+ handler: async (args) => {
223
+ // parseAndValidate throws NexusError(InvalidParams) on violation;
224
+ // we let it propagate so the MCP dispatch layer can map it to a
225
+ // JSON-RPC error response (matches memory_search convention).
226
+ const parsed = parseAndValidate(args);
227
+ // Audit structlog (stderr only). Body intentionally does NOT include
228
+ // user_id — that property travels only on this log line.
229
+ auditLogger(JSON.stringify({
230
+ event: 'mcp.memory_feedback.submit',
231
+ tool: NAME,
232
+ user_id: parsed.user_id,
233
+ retrieve_id: parsed.retrieve_id,
234
+ rating: parsed.body.rating,
235
+ }));
236
+ const client = getClient();
237
+ // Wave 2B mid_audit-to-pre_merge fix: wrap SDK call + map errors per §M-3
238
+ // (mirrors context.ts pattern). Without this, axios-like 401/403/429
239
+ // surface as JSON-RPC InternalError instead of Unauthorized/RateLimited.
240
+ let result;
241
+ try {
242
+ result = await client.feedback.submit(parsed.retrieve_id, parsed.body);
243
+ }
244
+ catch (err) {
245
+ if (isAxiosLikeError(err)) {
246
+ const status = err.response?.status ?? null;
247
+ const respBody = err.response?.data ?? null;
248
+ const headers = err.response?.headers;
249
+ throw mapHttpStatusToMcpError(status, respBody, headers);
250
+ }
251
+ throw mapHttpStatusToMcpError(null, null);
252
+ }
253
+ return {
254
+ content: [{ type: 'text', text: JSON.stringify(result) }],
255
+ structuredContent: result,
256
+ };
257
+ },
258
+ };
259
+ //# sourceMappingURL=memory_feedback.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"memory_feedback.js","sourceRoot":"","sources":["../../src/tools/memory_feedback.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Tool: nexus.memory_search (US-037 TASK-010, Wave 2).
3
+ *
4
+ * Targeted memory search over the Nexus REST API via `@nexusm/sdk`.
5
+ *
6
+ * Schema is locked in proposal §"R2 工具 Schema 锁定" Tool 2 (+ R2.1) and
7
+ * MUST be preserved verbatim — the parity check
8
+ * `tests/unit/schema_sync.test.ts` will fail loudly if drift is introduced.
9
+ *
10
+ * Design decisions resolved during implementation:
11
+ *
12
+ * - `mode` defaults to `'hybrid'` per proposal §M-11 / §A2-D-1
13
+ * (no pure-keyword mode in Phase 1; hybrid handles CJK via the
14
+ * backend's trigram `word_similarity` fallback documented in
15
+ * `repositories/__init__.py::search_by_trigram`).
16
+ *
17
+ * - Enum violations on `mode` are surfaced as a JSON-RPC
18
+ * `InvalidParams` protocol error (proposal §M-3 mapping table:
19
+ * 422 → InvalidParams). The MCP core dispatcher in `src/index.ts`
20
+ * does NOT validate input against the declared inputSchema — that
21
+ * responsibility lives in this handler.
22
+ *
23
+ * - `score_threshold` is forwarded to the SDK body verbatim. The
24
+ * backend `/memories/search` endpoint accepts it under that exact
25
+ * name (the SDK's older `threshold` field is a historical alias
26
+ * that the backend also accepts; the locked MCP schema uses the
27
+ * new name, so we forward the new name).
28
+ *
29
+ * - `NexusClient` is lazily constructed on first handler invocation
30
+ * from `loadAuthConfig()` and cached for subsequent calls. This
31
+ * keeps construction cost off the `tools/list` hot path and lets
32
+ * tests inject a mock via `vi.mock('@nexusm/sdk', ...)` before the
33
+ * first call.
34
+ */
35
+ import { type ToolDefinition } from './types.js';
36
+ /**
37
+ * Test-only seam: reset the cached client so a `vi.mock` of `@nexusm/sdk`
38
+ * applied between tests can re-construct against the fresh mock.
39
+ *
40
+ * @internal
41
+ */
42
+ export declare function __resetClientForTesting(): void;
43
+ export declare const memorySearchTool: ToolDefinition;
44
+ //# sourceMappingURL=memory_search.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"memory_search.d.ts","sourceRoot":"","sources":["../../src/tools/memory_search.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AAKH,OAAO,EAAE,KAAK,cAAc,EAAE,MAAM,YAAY,CAAC;AAuBjD;;;;;GAKG;AACH,wBAAgB,uBAAuB,IAAI,IAAI,CAE9C;AAaD,eAAO,MAAM,gBAAgB,EAAE,cAwG9B,CAAC"}