@usewhisper/mcp-server 1.4.0 → 2.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.
- package/README.md +104 -18
- package/dist/server.js +535 -121
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -14,13 +14,18 @@ Whisper MCP is the universal context bridge for coding agents. It connects Claud
|
|
|
14
14
|
|
|
15
15
|
## Install
|
|
16
16
|
|
|
17
|
-
```
|
|
17
|
+
```text
|
|
18
18
|
npm i -g @usewhisper/mcp-server
|
|
19
19
|
```
|
|
20
20
|
|
|
21
|
+
## Terminal vs MCP
|
|
22
|
+
|
|
23
|
+
Use the terminal commands only to install the server, print migration info, or generate client config.
|
|
24
|
+
Once the server is running, agents should call MCP tools like `search`, `remember`, `learn`, and `share_context`, not shell commands.
|
|
25
|
+
|
|
21
26
|
## Required Environment
|
|
22
27
|
|
|
23
|
-
- `WHISPER_API_KEY` (required)
|
|
28
|
+
- `WHISPER_API_KEY` (required, use your `wsk_*` API key)
|
|
24
29
|
- `WHISPER_PROJECT` (optional default)
|
|
25
30
|
- `WHISPER_BASE_URL` (optional, defaults to `https://context.usewhisper.dev`)
|
|
26
31
|
- `WHISPER_MCP_MODE` (optional: `remote|local|auto`, default `remote`)
|
|
@@ -28,6 +33,22 @@ npm i -g @usewhisper/mcp-server
|
|
|
28
33
|
- `SLACK_BOT_TOKEN` (required for Slack connector runs)
|
|
29
34
|
- `SLACK_CHANNEL_ID` (required for Slack connector runs)
|
|
30
35
|
|
|
36
|
+
Example values:
|
|
37
|
+
|
|
38
|
+
```text
|
|
39
|
+
WHISPER_API_KEY=wsk_your_api_key_here
|
|
40
|
+
WHISPER_PROJECT=my-project
|
|
41
|
+
WHISPER_BASE_URL=https://context.usewhisper.dev
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Example shell setup:
|
|
45
|
+
|
|
46
|
+
```text
|
|
47
|
+
export WHISPER_API_KEY="wsk_your_api_key_here"
|
|
48
|
+
export WHISPER_PROJECT="my-project"
|
|
49
|
+
export WHISPER_BASE_URL="https://context.usewhisper.dev"
|
|
50
|
+
```
|
|
51
|
+
|
|
31
52
|
## Connector Status + Credentials
|
|
32
53
|
|
|
33
54
|
| Connector | Status | Minimum required config/creds |
|
|
@@ -65,21 +86,52 @@ npm i -g @usewhisper/mcp-server
|
|
|
65
86
|
22. `code.search_text`
|
|
66
87
|
23. `code.search_semantic`
|
|
67
88
|
|
|
68
|
-
## Agent-Friendly
|
|
89
|
+
## Agent-Friendly Primary Verbs
|
|
69
90
|
|
|
70
|
-
These
|
|
91
|
+
These verbs are the primary MCP interface for agents. Namespaced tools remain available as compatibility and advanced surfaces.
|
|
71
92
|
|
|
72
|
-
- `search` -> `
|
|
93
|
+
- `search` -> exact fetch by `id`, semantic retrieval by `query`, or hybrid retrieval when both are present
|
|
73
94
|
- `search_code` -> `code.search_semantic`
|
|
74
95
|
- `grep` -> `code.search_text`
|
|
75
96
|
- `read` -> local file read with optional line ranges
|
|
76
97
|
- `explore` -> local repository tree browsing
|
|
77
98
|
- `research` -> `research.oracle`
|
|
78
|
-
- `index` -> source add or workspace refresh
|
|
79
99
|
- `remember` -> `memory.add`
|
|
80
|
-
- `
|
|
100
|
+
- `record` -> `memory.ingest_conversation`
|
|
101
|
+
- `learn` -> `context.add_text | context.add_source | context.add_document`
|
|
81
102
|
- `share_context` -> `context.share`
|
|
82
103
|
|
|
104
|
+
`index` remains available as an advanced/admin compatibility tool for indexing jobs and workspace refresh operations.
|
|
105
|
+
|
|
106
|
+
### `search` behavior
|
|
107
|
+
|
|
108
|
+
`search` is the only primary retrieval verb.
|
|
109
|
+
|
|
110
|
+
- `search({ id })` returns an exact memory fetch
|
|
111
|
+
- `search({ query })` returns semantic retrieval
|
|
112
|
+
- `search({ id, query })` returns the exact memory plus related retrieval context
|
|
113
|
+
- `search({})` is invalid and returns `{ "success": false, "error": { "code": "invalid_request", "message": "..." } }`
|
|
114
|
+
|
|
115
|
+
All primary verbs return structured JSON text payloads. Success payloads begin with `"success": true`; failures use the shared `{ success: false, error: { code, message } }` envelope.
|
|
116
|
+
|
|
117
|
+
All valid `search` calls return the same top-level payload shape:
|
|
118
|
+
|
|
119
|
+
```json
|
|
120
|
+
{
|
|
121
|
+
"success": true,
|
|
122
|
+
"mode": "exact | semantic | hybrid",
|
|
123
|
+
"query": "string | null",
|
|
124
|
+
"id": "string | null",
|
|
125
|
+
"exact_memory": {},
|
|
126
|
+
"context": "string",
|
|
127
|
+
"results": [],
|
|
128
|
+
"count": 0,
|
|
129
|
+
"degraded_mode": false,
|
|
130
|
+
"degraded_reason": null,
|
|
131
|
+
"warnings": []
|
|
132
|
+
}
|
|
133
|
+
```
|
|
134
|
+
|
|
83
135
|
## Source Contract (`context.add_source`)
|
|
84
136
|
|
|
85
137
|
Input:
|
|
@@ -100,26 +152,52 @@ Output:
|
|
|
100
152
|
|
|
101
153
|
## Scoped MCP Generator
|
|
102
154
|
|
|
103
|
-
|
|
155
|
+
Print config to stdout:
|
|
156
|
+
|
|
157
|
+
```text
|
|
104
158
|
whisper-context-mcp scope --project my-project --source github --client claude
|
|
105
159
|
```
|
|
106
160
|
|
|
107
|
-
|
|
161
|
+
OpenCode project config generation:
|
|
162
|
+
|
|
163
|
+
```text
|
|
164
|
+
whisper-context-mcp scope --project my-project --source github --client opencode
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
Write the generated config to a file:
|
|
108
168
|
|
|
109
|
-
```
|
|
110
|
-
whisper-context-mcp scope --project my-project --source github --client vscode --write
|
|
169
|
+
```text
|
|
170
|
+
whisper-context-mcp scope --project my-project --source github --client vscode --write /absolute/path/to/mcp.json
|
|
111
171
|
```
|
|
112
172
|
|
|
113
173
|
## Breaking Rename Migration
|
|
114
174
|
|
|
115
175
|
Print full map:
|
|
116
176
|
|
|
117
|
-
```
|
|
177
|
+
```text
|
|
118
178
|
whisper-context-mcp --print-tool-map
|
|
119
179
|
```
|
|
120
180
|
|
|
121
181
|
This release removes legacy un-namespaced tool names.
|
|
122
182
|
|
|
183
|
+
## Contract Metadata
|
|
184
|
+
|
|
185
|
+
Public API metadata is available at:
|
|
186
|
+
|
|
187
|
+
```text
|
|
188
|
+
GET /v1/contracts/meta
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
It exposes:
|
|
192
|
+
|
|
193
|
+
- current contract version
|
|
194
|
+
- active surfaces (`http`, `sdk`, `mcp`)
|
|
195
|
+
- approved primary MCP verbs
|
|
196
|
+
- migration window and removal policy
|
|
197
|
+
- deprecated HTTP routes with replacement hints
|
|
198
|
+
|
|
199
|
+
It does not expose internal security checklist fields.
|
|
200
|
+
|
|
123
201
|
## Security Defaults
|
|
124
202
|
|
|
125
203
|
Local ingest (`index.local_scan_ingest` and `type=local`) enforces:
|
|
@@ -129,18 +207,26 @@ Local ingest (`index.local_scan_ingest` and `type=local`) enforces:
|
|
|
129
207
|
|
|
130
208
|
## 30-Second Demo
|
|
131
209
|
|
|
132
|
-
One
|
|
210
|
+
One command:
|
|
133
211
|
|
|
134
|
-
```
|
|
212
|
+
```text
|
|
135
213
|
npx whisper-wizard
|
|
136
214
|
```
|
|
137
215
|
|
|
138
216
|
Flow:
|
|
139
217
|
1. Run wizard and complete auth/project setup.
|
|
140
|
-
2. Add a source with MCP:
|
|
141
|
-
`context.
|
|
142
|
-
|
|
143
|
-
|
|
218
|
+
2. Add a source with MCP: `context.add_source`
|
|
219
|
+
3. Ask a grounded question: `context.query` or `context.evidence_answer`
|
|
220
|
+
4. If you selected OpenCode, wizard also updates `opencode.json` and creates `.opencode/plugins/whisper-context.ts`
|
|
221
|
+
|
|
222
|
+
## OpenCode Plugin Notes
|
|
223
|
+
|
|
224
|
+
When using OpenCode, the plugin runtime adds two OpenCode-only helper tools:
|
|
225
|
+
|
|
226
|
+
- `whisper_status`
|
|
227
|
+
- `whisper_flush_session`
|
|
228
|
+
|
|
229
|
+
These are plugin tools, not MCP tools. Continue using Whisper MCP for retrieval and persistence verbs.
|
|
144
230
|
|
|
145
231
|
## License
|
|
146
232
|
|
package/dist/server.js
CHANGED
|
@@ -144,6 +144,7 @@ var RuntimeClient = class {
|
|
|
144
144
|
diagnostics;
|
|
145
145
|
inFlight = /* @__PURE__ */ new Map();
|
|
146
146
|
sendApiKeyHeader;
|
|
147
|
+
fetchImpl;
|
|
147
148
|
constructor(options, diagnostics) {
|
|
148
149
|
if (!options.apiKey) {
|
|
149
150
|
throw new RuntimeClientError({
|
|
@@ -168,6 +169,7 @@ var RuntimeClient = class {
|
|
|
168
169
|
...options.timeouts || {}
|
|
169
170
|
};
|
|
170
171
|
this.sendApiKeyHeader = process.env.WHISPER_SEND_X_API_KEY === "1";
|
|
172
|
+
this.fetchImpl = options.fetchImpl || fetch;
|
|
171
173
|
this.diagnostics = diagnostics || new DiagnosticsStore(1e3);
|
|
172
174
|
}
|
|
173
175
|
getDiagnosticsStore() {
|
|
@@ -288,7 +290,7 @@ var RuntimeClient = class {
|
|
|
288
290
|
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
289
291
|
try {
|
|
290
292
|
const attachApiKeyHeader = this.shouldAttachApiKeyHeader(normalizedEndpoint);
|
|
291
|
-
const response = await
|
|
293
|
+
const response = await this.fetchImpl(`${this.baseUrl}${normalizedEndpoint}`, {
|
|
292
294
|
method,
|
|
293
295
|
signal: controller.signal,
|
|
294
296
|
keepalive: method !== "GET",
|
|
@@ -488,7 +490,7 @@ var WhisperContext = class _WhisperContext {
|
|
|
488
490
|
});
|
|
489
491
|
warnDeprecatedOnce(
|
|
490
492
|
"whisper_context_class",
|
|
491
|
-
"[Whisper SDK] WhisperContext
|
|
493
|
+
"[Whisper SDK] WhisperContext is deprecated in v3 and scheduled for removal in v4. Prefer WhisperClient for runtime features and future contract compatibility."
|
|
492
494
|
);
|
|
493
495
|
}
|
|
494
496
|
withProject(project) {
|
|
@@ -774,6 +776,9 @@ var WhisperContext = class _WhisperContext {
|
|
|
774
776
|
if (params.auth_ref) config.auth_ref = params.auth_ref;
|
|
775
777
|
}
|
|
776
778
|
if (params.metadata) config.metadata = params.metadata;
|
|
779
|
+
if (params.ingestion_profile) config.ingestion_profile = params.ingestion_profile;
|
|
780
|
+
if (params.strategy_override) config.strategy_override = params.strategy_override;
|
|
781
|
+
if (params.profile_config) config.profile_config = params.profile_config;
|
|
777
782
|
config.auto_index = params.auto_index ?? true;
|
|
778
783
|
const created = await this.addSource(project, {
|
|
779
784
|
name: params.name || `${params.type}-source-${Date.now()}`,
|
|
@@ -858,12 +863,37 @@ var WhisperContext = class _WhisperContext {
|
|
|
858
863
|
write_mode: params.write_mode
|
|
859
864
|
})
|
|
860
865
|
});
|
|
861
|
-
const
|
|
866
|
+
const mode = direct?.mode === "async" ? "async" : direct?.mode === "sync" ? "sync" : void 0;
|
|
867
|
+
const memoryId = direct?.memory?.id || direct?.memory_id || (mode !== "async" ? direct?.id : void 0);
|
|
868
|
+
const jobId = direct?.job_id || (mode === "async" ? direct?.id : void 0);
|
|
869
|
+
const id2 = memoryId || jobId || "";
|
|
862
870
|
if (id2) {
|
|
863
|
-
return {
|
|
871
|
+
return {
|
|
872
|
+
id: id2,
|
|
873
|
+
success: true,
|
|
874
|
+
path: "sota",
|
|
875
|
+
fallback_used: false,
|
|
876
|
+
mode,
|
|
877
|
+
...memoryId ? { memory_id: memoryId } : {},
|
|
878
|
+
...jobId ? { job_id: jobId } : {},
|
|
879
|
+
...direct?.status_url ? { status_url: direct.status_url } : {},
|
|
880
|
+
...direct?.accepted_at ? { accepted_at: direct.accepted_at } : {},
|
|
881
|
+
...direct?.visibility_sla_ms ? { visibility_sla_ms: direct.visibility_sla_ms } : {},
|
|
882
|
+
...direct?.pending_visibility !== void 0 ? { pending_visibility: Boolean(direct.pending_visibility) } : {}
|
|
883
|
+
};
|
|
864
884
|
}
|
|
865
885
|
if (direct?.success === true) {
|
|
866
|
-
return {
|
|
886
|
+
return {
|
|
887
|
+
id: "",
|
|
888
|
+
success: true,
|
|
889
|
+
path: "sota",
|
|
890
|
+
fallback_used: false,
|
|
891
|
+
mode,
|
|
892
|
+
...direct?.status_url ? { status_url: direct.status_url } : {},
|
|
893
|
+
...direct?.accepted_at ? { accepted_at: direct.accepted_at } : {},
|
|
894
|
+
...direct?.visibility_sla_ms ? { visibility_sla_ms: direct.visibility_sla_ms } : {},
|
|
895
|
+
...direct?.pending_visibility !== void 0 ? { pending_visibility: Boolean(direct.pending_visibility) } : {}
|
|
896
|
+
};
|
|
867
897
|
}
|
|
868
898
|
} catch (error) {
|
|
869
899
|
if (params.allow_legacy_fallback === false) {
|
|
@@ -891,7 +921,14 @@ var WhisperContext = class _WhisperContext {
|
|
|
891
921
|
message: "Memory create succeeded but no memory id was returned by the API"
|
|
892
922
|
});
|
|
893
923
|
}
|
|
894
|
-
return {
|
|
924
|
+
return {
|
|
925
|
+
id,
|
|
926
|
+
success: true,
|
|
927
|
+
path: "legacy",
|
|
928
|
+
fallback_used: true,
|
|
929
|
+
mode: "sync",
|
|
930
|
+
memory_id: id
|
|
931
|
+
};
|
|
895
932
|
});
|
|
896
933
|
}
|
|
897
934
|
async addMemoriesBulk(params) {
|
|
@@ -1090,6 +1127,16 @@ var WhisperContext = class _WhisperContext {
|
|
|
1090
1127
|
}
|
|
1091
1128
|
});
|
|
1092
1129
|
}
|
|
1130
|
+
async getMemory(memoryId) {
|
|
1131
|
+
try {
|
|
1132
|
+
return await this.request(`/v1/memory/${memoryId}`);
|
|
1133
|
+
} catch (error) {
|
|
1134
|
+
if (!this.isEndpointNotFoundError(error)) {
|
|
1135
|
+
throw error;
|
|
1136
|
+
}
|
|
1137
|
+
return this.request(`/v1/memories/${memoryId}`);
|
|
1138
|
+
}
|
|
1139
|
+
}
|
|
1093
1140
|
async getMemoryVersions(memoryId) {
|
|
1094
1141
|
return this.request(`/v1/memory/${memoryId}/versions`);
|
|
1095
1142
|
}
|
|
@@ -1288,6 +1335,7 @@ var WhisperContext = class _WhisperContext {
|
|
|
1288
1335
|
ingestSession: (params) => this.ingestSession(params),
|
|
1289
1336
|
getSessionMemories: (params) => this.getSessionMemories(params),
|
|
1290
1337
|
getUserProfile: (params) => this.getUserProfile(params),
|
|
1338
|
+
get: (memoryId) => this.getMemory(memoryId),
|
|
1291
1339
|
getVersions: (memoryId) => this.getMemoryVersions(memoryId),
|
|
1292
1340
|
update: (memoryId, params) => this.updateMemory(memoryId, params),
|
|
1293
1341
|
delete: (memoryId) => this.deleteMemory(memoryId),
|
|
@@ -1321,6 +1369,75 @@ var WhisperContext = class _WhisperContext {
|
|
|
1321
1369
|
};
|
|
1322
1370
|
};
|
|
1323
1371
|
|
|
1372
|
+
// ../src/mcp/search-payload.mjs
|
|
1373
|
+
function normalizeExactMemory(memory) {
|
|
1374
|
+
if (!memory) return null;
|
|
1375
|
+
return {
|
|
1376
|
+
id: memory.id ? String(memory.id) : null,
|
|
1377
|
+
type: memory.type ? String(memory.type) : memory.memoryType ? String(memory.memoryType) : null,
|
|
1378
|
+
content: String(memory.content || ""),
|
|
1379
|
+
user_id: memory.user_id ? String(memory.user_id) : memory.userId ? String(memory.userId) : null,
|
|
1380
|
+
session_id: memory.session_id ? String(memory.session_id) : memory.sessionId ? String(memory.sessionId) : null,
|
|
1381
|
+
updated_at: memory.updated_at ? String(memory.updated_at) : memory.updatedAt ? String(memory.updatedAt) : null,
|
|
1382
|
+
metadata: memory.metadata && typeof memory.metadata === "object" && !Array.isArray(memory.metadata) ? memory.metadata : null
|
|
1383
|
+
};
|
|
1384
|
+
}
|
|
1385
|
+
function normalizeSearchResults(results) {
|
|
1386
|
+
return (results || []).map((result) => {
|
|
1387
|
+
const candidate = result?.memory ? result.memory : result;
|
|
1388
|
+
const similarity = result?.similarity;
|
|
1389
|
+
return {
|
|
1390
|
+
id: candidate?.id ? String(candidate.id) : null,
|
|
1391
|
+
content: String(candidate?.content || result?.content || ""),
|
|
1392
|
+
score: typeof similarity === "number" ? similarity : typeof result?.score === "number" ? result.score : similarity != null ? Number(similarity) : result?.score != null ? Number(result.score) : null,
|
|
1393
|
+
source: result?.source ? String(result.source) : result?.chunk ? "memory" : null,
|
|
1394
|
+
document: result?.document ? String(result.document) : result?.chunk?.id ? String(result.chunk.id) : null,
|
|
1395
|
+
metadata: result?.metadata && typeof result.metadata === "object" && !Array.isArray(result.metadata) ? result.metadata : candidate?.metadata && typeof candidate.metadata === "object" && !Array.isArray(candidate.metadata) ? candidate.metadata : null,
|
|
1396
|
+
memory_type: candidate?.type ? String(candidate.type) : candidate?.memory_type ? String(candidate.memory_type) : null
|
|
1397
|
+
};
|
|
1398
|
+
});
|
|
1399
|
+
}
|
|
1400
|
+
function normalizeCanonicalResults(input) {
|
|
1401
|
+
if (Array.isArray(input?.results)) return input.results;
|
|
1402
|
+
if (Array.isArray(input?.memories)) return input.memories;
|
|
1403
|
+
return [];
|
|
1404
|
+
}
|
|
1405
|
+
function buildPrimaryToolSuccess(payload) {
|
|
1406
|
+
return {
|
|
1407
|
+
success: true,
|
|
1408
|
+
...payload
|
|
1409
|
+
};
|
|
1410
|
+
}
|
|
1411
|
+
function buildPrimaryToolError(message, options = {}) {
|
|
1412
|
+
return {
|
|
1413
|
+
success: false,
|
|
1414
|
+
error: {
|
|
1415
|
+
code: options.code ?? "tool_error",
|
|
1416
|
+
message
|
|
1417
|
+
}
|
|
1418
|
+
};
|
|
1419
|
+
}
|
|
1420
|
+
function buildMcpSearchPayload(input) {
|
|
1421
|
+
const normalizedResults = normalizeSearchResults(input.results);
|
|
1422
|
+
return buildPrimaryToolSuccess({
|
|
1423
|
+
mode: input.mode,
|
|
1424
|
+
query: input.query ?? null,
|
|
1425
|
+
id: input.id ?? null,
|
|
1426
|
+
exact_memory: normalizeExactMemory(input.exactMemory),
|
|
1427
|
+
context: input.context ?? "",
|
|
1428
|
+
results: normalizedResults,
|
|
1429
|
+
count: normalizedResults.length,
|
|
1430
|
+
degraded_mode: Boolean(input.degradedMode),
|
|
1431
|
+
degraded_reason: input.degradedReason ?? null,
|
|
1432
|
+
warnings: input.warnings || []
|
|
1433
|
+
});
|
|
1434
|
+
}
|
|
1435
|
+
function buildMcpSearchError(message, options = {}) {
|
|
1436
|
+
return buildPrimaryToolError(message, {
|
|
1437
|
+
code: options.code ?? "invalid_request"
|
|
1438
|
+
});
|
|
1439
|
+
}
|
|
1440
|
+
|
|
1324
1441
|
// ../src/mcp/server.ts
|
|
1325
1442
|
var API_KEY = process.env.WHISPER_API_KEY || "";
|
|
1326
1443
|
var DEFAULT_PROJECT = process.env.WHISPER_PROJECT || "";
|
|
@@ -1328,15 +1445,25 @@ var BASE_URL = process.env.WHISPER_BASE_URL;
|
|
|
1328
1445
|
var RUNTIME_MODE = (process.env.WHISPER_MCP_MODE || "remote").toLowerCase();
|
|
1329
1446
|
var CLI_ARGS = process.argv.slice(2);
|
|
1330
1447
|
var IS_MANAGEMENT_ONLY = CLI_ARGS.includes("--print-tool-map") || CLI_ARGS[0] === "scope";
|
|
1331
|
-
|
|
1332
|
-
apiKey
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
}
|
|
1448
|
+
function createWhisperMcpClient(options) {
|
|
1449
|
+
const apiKey = options?.apiKey ?? API_KEY;
|
|
1450
|
+
if (!apiKey || IS_MANAGEMENT_ONLY) {
|
|
1451
|
+
return null;
|
|
1452
|
+
}
|
|
1453
|
+
return new WhisperContext({
|
|
1454
|
+
apiKey,
|
|
1455
|
+
project: options?.project ?? DEFAULT_PROJECT,
|
|
1456
|
+
...(options?.baseUrl ?? BASE_URL) && { baseUrl: options?.baseUrl ?? BASE_URL }
|
|
1457
|
+
});
|
|
1458
|
+
}
|
|
1459
|
+
var whisper = createWhisperMcpClient();
|
|
1336
1460
|
var server = new McpServer({
|
|
1337
1461
|
name: "whisper-context",
|
|
1338
1462
|
version: "0.2.8"
|
|
1339
1463
|
});
|
|
1464
|
+
function createMcpServer() {
|
|
1465
|
+
return server;
|
|
1466
|
+
}
|
|
1340
1467
|
var STATE_DIR = join(homedir(), ".whisper-mcp");
|
|
1341
1468
|
var STATE_PATH = join(STATE_DIR, "state.json");
|
|
1342
1469
|
var AUDIT_LOG_PATH = join(STATE_DIR, "forget-audit.log");
|
|
@@ -1366,7 +1493,7 @@ var TOOL_MIGRATION_MAP = [
|
|
|
1366
1493
|
{ old: "semantic_search_codebase", next: "code.search_semantic" }
|
|
1367
1494
|
];
|
|
1368
1495
|
var ALIAS_TOOL_MAP = [
|
|
1369
|
-
{ alias: "search", target: "context.query" },
|
|
1496
|
+
{ alias: "search", target: "context.query | memory.get" },
|
|
1370
1497
|
{ alias: "search_code", target: "code.search_semantic" },
|
|
1371
1498
|
{ alias: "grep", target: "code.search_text" },
|
|
1372
1499
|
{ alias: "read", target: "local.file_read" },
|
|
@@ -1374,7 +1501,8 @@ var ALIAS_TOOL_MAP = [
|
|
|
1374
1501
|
{ alias: "research", target: "research.oracle" },
|
|
1375
1502
|
{ alias: "index", target: "context.add_source | index.workspace_run" },
|
|
1376
1503
|
{ alias: "remember", target: "memory.add" },
|
|
1377
|
-
{ alias: "
|
|
1504
|
+
{ alias: "record", target: "memory.ingest_conversation" },
|
|
1505
|
+
{ alias: "learn", target: "context.add_text | context.add_source | context.add_document" },
|
|
1378
1506
|
{ alias: "share_context", target: "context.share" }
|
|
1379
1507
|
];
|
|
1380
1508
|
function ensureStateDir() {
|
|
@@ -1567,6 +1695,25 @@ function countCodeFiles(searchPath, maxFiles = 5e3) {
|
|
|
1567
1695
|
function toTextResult(payload) {
|
|
1568
1696
|
return { content: [{ type: "text", text: JSON.stringify(payload, null, 2) }] };
|
|
1569
1697
|
}
|
|
1698
|
+
function primaryToolSuccess(payload) {
|
|
1699
|
+
return toTextResult(buildPrimaryToolSuccess(payload));
|
|
1700
|
+
}
|
|
1701
|
+
function primaryToolError(message, code = "tool_error") {
|
|
1702
|
+
return toTextResult(buildPrimaryToolError(message, { code }));
|
|
1703
|
+
}
|
|
1704
|
+
function formatCanonicalMemoryResults(rawResults) {
|
|
1705
|
+
const results = normalizeCanonicalResults(rawResults);
|
|
1706
|
+
return results.map((result) => {
|
|
1707
|
+
const memory = result?.memory || result;
|
|
1708
|
+
return {
|
|
1709
|
+
id: memory?.id ? String(memory.id) : null,
|
|
1710
|
+
content: String(memory?.content || result?.content || ""),
|
|
1711
|
+
memory_type: memory?.type ? String(memory.type) : memory?.memory_type ? String(memory.memory_type) : null,
|
|
1712
|
+
similarity: typeof result?.similarity === "number" ? result.similarity : typeof result?.score === "number" ? result.score : null,
|
|
1713
|
+
metadata: memory?.metadata && typeof memory.metadata === "object" && !Array.isArray(memory.metadata) ? memory.metadata : null
|
|
1714
|
+
};
|
|
1715
|
+
});
|
|
1716
|
+
}
|
|
1570
1717
|
function likelyEmbeddingFailure(error) {
|
|
1571
1718
|
const message = String(error?.message || error || "").toLowerCase();
|
|
1572
1719
|
return message.includes("embedding") || message.includes("vector") || message.includes("timeout") || message.includes("timed out") || message.includes("temporarily unavailable");
|
|
@@ -1777,6 +1924,9 @@ async function createSourceByType(params) {
|
|
|
1777
1924
|
if (params.max_chunks !== void 0) config.max_chunks = params.max_chunks;
|
|
1778
1925
|
}
|
|
1779
1926
|
if (params.metadata) config.metadata = params.metadata;
|
|
1927
|
+
if (params.ingestion_profile) config.ingestion_profile = params.ingestion_profile;
|
|
1928
|
+
if (params.strategy_override) config.strategy_override = params.strategy_override;
|
|
1929
|
+
if (params.profile_config) config.profile_config = params.profile_config;
|
|
1780
1930
|
config.auto_index = params.auto_index ?? true;
|
|
1781
1931
|
const created = await whisper.addSource(params.project, {
|
|
1782
1932
|
name: params.name || `${params.type}-source-${Date.now()}`,
|
|
@@ -1798,7 +1948,109 @@ async function createSourceByType(params) {
|
|
|
1798
1948
|
warnings: []
|
|
1799
1949
|
};
|
|
1800
1950
|
}
|
|
1801
|
-
function
|
|
1951
|
+
function normalizeRecordMessages(input) {
|
|
1952
|
+
if (Array.isArray(input.messages) && input.messages.length > 0) {
|
|
1953
|
+
return input.messages.map((message) => ({
|
|
1954
|
+
role: message.role || "user",
|
|
1955
|
+
content: message.content,
|
|
1956
|
+
timestamp: message.timestamp || (/* @__PURE__ */ new Date()).toISOString()
|
|
1957
|
+
}));
|
|
1958
|
+
}
|
|
1959
|
+
if (!input.content) {
|
|
1960
|
+
throw new Error("Provide messages[] or content.");
|
|
1961
|
+
}
|
|
1962
|
+
return [{
|
|
1963
|
+
role: input.role || "user",
|
|
1964
|
+
content: input.content,
|
|
1965
|
+
timestamp: input.timestamp || (/* @__PURE__ */ new Date()).toISOString()
|
|
1966
|
+
}];
|
|
1967
|
+
}
|
|
1968
|
+
async function learnFromInput(input) {
|
|
1969
|
+
const resolvedProject = await resolveProjectRef(input.project);
|
|
1970
|
+
if (!resolvedProject) {
|
|
1971
|
+
throw new Error("No project resolved. Set WHISPER_PROJECT or provide project.");
|
|
1972
|
+
}
|
|
1973
|
+
if (input.content) {
|
|
1974
|
+
const mergedMetadata = {
|
|
1975
|
+
...input.metadata || {},
|
|
1976
|
+
...input.ingestion_profile ? { ingestion_profile: input.ingestion_profile } : {},
|
|
1977
|
+
...input.strategy_override ? { strategy_override: input.strategy_override } : {},
|
|
1978
|
+
...input.profile_config ? { profile_config: input.profile_config } : {}
|
|
1979
|
+
};
|
|
1980
|
+
const result = await whisper.addContext({
|
|
1981
|
+
project: resolvedProject,
|
|
1982
|
+
content: input.content,
|
|
1983
|
+
title: input.title || "Learned Context",
|
|
1984
|
+
metadata: mergedMetadata
|
|
1985
|
+
});
|
|
1986
|
+
return {
|
|
1987
|
+
mode: "text",
|
|
1988
|
+
project: resolvedProject,
|
|
1989
|
+
ingested: result.ingested
|
|
1990
|
+
};
|
|
1991
|
+
}
|
|
1992
|
+
if (input.owner && input.repo) {
|
|
1993
|
+
return createSourceByType({
|
|
1994
|
+
project: resolvedProject,
|
|
1995
|
+
type: "github",
|
|
1996
|
+
owner: input.owner,
|
|
1997
|
+
repo: input.repo,
|
|
1998
|
+
branch: input.branch,
|
|
1999
|
+
name: input.name,
|
|
2000
|
+
auto_index: true,
|
|
2001
|
+
metadata: input.metadata,
|
|
2002
|
+
ingestion_profile: input.ingestion_profile,
|
|
2003
|
+
strategy_override: input.strategy_override,
|
|
2004
|
+
profile_config: input.profile_config
|
|
2005
|
+
});
|
|
2006
|
+
}
|
|
2007
|
+
if (input.path) {
|
|
2008
|
+
return createSourceByType({
|
|
2009
|
+
project: resolvedProject,
|
|
2010
|
+
type: "local",
|
|
2011
|
+
path: input.path,
|
|
2012
|
+
glob: input.glob,
|
|
2013
|
+
max_files: input.max_files,
|
|
2014
|
+
name: input.name,
|
|
2015
|
+
metadata: input.metadata,
|
|
2016
|
+
ingestion_profile: input.ingestion_profile,
|
|
2017
|
+
strategy_override: input.strategy_override,
|
|
2018
|
+
profile_config: input.profile_config
|
|
2019
|
+
});
|
|
2020
|
+
}
|
|
2021
|
+
if (input.file_path) {
|
|
2022
|
+
return createSourceByType({
|
|
2023
|
+
project: resolvedProject,
|
|
2024
|
+
type: "pdf",
|
|
2025
|
+
file_path: input.file_path,
|
|
2026
|
+
name: input.name,
|
|
2027
|
+
metadata: input.metadata,
|
|
2028
|
+
ingestion_profile: input.ingestion_profile,
|
|
2029
|
+
strategy_override: input.strategy_override,
|
|
2030
|
+
profile_config: input.profile_config
|
|
2031
|
+
});
|
|
2032
|
+
}
|
|
2033
|
+
if (input.url) {
|
|
2034
|
+
return createSourceByType({
|
|
2035
|
+
project: resolvedProject,
|
|
2036
|
+
type: input.url.endsWith(".pdf") ? "pdf" : "web",
|
|
2037
|
+
url: input.url,
|
|
2038
|
+
name: input.name,
|
|
2039
|
+
metadata: input.metadata,
|
|
2040
|
+
ingestion_profile: input.ingestion_profile,
|
|
2041
|
+
strategy_override: input.strategy_override,
|
|
2042
|
+
profile_config: input.profile_config,
|
|
2043
|
+
crawl_depth: input.crawl_depth,
|
|
2044
|
+
channel_ids: input.channel_ids,
|
|
2045
|
+
token: input.token,
|
|
2046
|
+
workspace_id: input.workspace_id,
|
|
2047
|
+
since: input.since,
|
|
2048
|
+
auth_ref: input.auth_ref
|
|
2049
|
+
});
|
|
2050
|
+
}
|
|
2051
|
+
throw new Error("Provide content, owner+repo, path, file_path, or url.");
|
|
2052
|
+
}
|
|
2053
|
+
function renderScopedMcpConfig(project, source, client) {
|
|
1802
2054
|
const serverDef = {
|
|
1803
2055
|
command: "npx",
|
|
1804
2056
|
args: ["-y", "@usewhisper/mcp-server"],
|
|
@@ -2261,7 +2513,12 @@ server.tool(
|
|
|
2261
2513
|
agent_id,
|
|
2262
2514
|
importance
|
|
2263
2515
|
});
|
|
2264
|
-
|
|
2516
|
+
const memoryId = result?.memory_id || result.id;
|
|
2517
|
+
const jobId = result?.job_id;
|
|
2518
|
+
const mode = result?.mode;
|
|
2519
|
+
const typeLabel = memory_type || "factual";
|
|
2520
|
+
const text = mode === "async" || jobId ? `Memory queued (job_id: ${jobId || result.id}, type: ${typeLabel}).` : `Memory stored (id: ${memoryId}, type: ${typeLabel}).`;
|
|
2521
|
+
return { content: [{ type: "text", text }] };
|
|
2265
2522
|
} catch (error) {
|
|
2266
2523
|
return { content: [{ type: "text", text: `Error: ${error.message}` }] };
|
|
2267
2524
|
}
|
|
@@ -2288,14 +2545,15 @@ server.tool(
|
|
|
2288
2545
|
top_k,
|
|
2289
2546
|
memory_types
|
|
2290
2547
|
});
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
|
|
2548
|
+
const normalizedResults = formatCanonicalMemoryResults(results);
|
|
2549
|
+
return primaryToolSuccess({
|
|
2550
|
+
tool: "memory.search",
|
|
2551
|
+
query,
|
|
2552
|
+
results: normalizedResults,
|
|
2553
|
+
count: normalizedResults.length
|
|
2554
|
+
});
|
|
2297
2555
|
} catch (error) {
|
|
2298
|
-
return
|
|
2556
|
+
return primaryToolError(error.message);
|
|
2299
2557
|
}
|
|
2300
2558
|
}
|
|
2301
2559
|
);
|
|
@@ -2337,6 +2595,9 @@ server.tool(
|
|
|
2337
2595
|
name: z.string().optional(),
|
|
2338
2596
|
auto_index: z.boolean().optional().default(true),
|
|
2339
2597
|
metadata: z.record(z.string()).optional(),
|
|
2598
|
+
ingestion_profile: z.enum(["auto", "repo", "web_docs", "pdf_layout", "video_transcript", "plain_text"]).optional(),
|
|
2599
|
+
strategy_override: z.enum(["fixed", "recursive", "semantic", "hierarchical", "adaptive"]).optional(),
|
|
2600
|
+
profile_config: z.record(z.any()).optional(),
|
|
2340
2601
|
owner: z.string().optional(),
|
|
2341
2602
|
repo: z.string().optional(),
|
|
2342
2603
|
branch: z.string().optional(),
|
|
@@ -2372,6 +2633,9 @@ server.tool(
|
|
|
2372
2633
|
name: input.name,
|
|
2373
2634
|
auto_index: input.auto_index,
|
|
2374
2635
|
metadata: input.metadata,
|
|
2636
|
+
ingestion_profile: input.ingestion_profile,
|
|
2637
|
+
strategy_override: input.strategy_override,
|
|
2638
|
+
profile_config: input.profile_config,
|
|
2375
2639
|
owner: input.owner,
|
|
2376
2640
|
repo: input.repo,
|
|
2377
2641
|
branch: input.branch,
|
|
@@ -2422,14 +2686,22 @@ server.tool(
|
|
|
2422
2686
|
{
|
|
2423
2687
|
project: z.string().optional().describe("Project name or slug"),
|
|
2424
2688
|
title: z.string().describe("Title for this content"),
|
|
2425
|
-
content: z.string().describe("The text content to index")
|
|
2689
|
+
content: z.string().describe("The text content to index"),
|
|
2690
|
+
ingestion_profile: z.enum(["auto", "repo", "web_docs", "pdf_layout", "video_transcript", "plain_text"]).optional(),
|
|
2691
|
+
strategy_override: z.enum(["fixed", "recursive", "semantic", "hierarchical", "adaptive"]).optional(),
|
|
2692
|
+
profile_config: z.record(z.any()).optional()
|
|
2426
2693
|
},
|
|
2427
|
-
async ({ project, title, content }) => {
|
|
2694
|
+
async ({ project, title, content, ingestion_profile, strategy_override, profile_config }) => {
|
|
2428
2695
|
try {
|
|
2429
2696
|
await whisper.addContext({
|
|
2430
2697
|
project,
|
|
2431
2698
|
title,
|
|
2432
|
-
content
|
|
2699
|
+
content,
|
|
2700
|
+
metadata: {
|
|
2701
|
+
...ingestion_profile ? { ingestion_profile } : {},
|
|
2702
|
+
...strategy_override ? { strategy_override } : {},
|
|
2703
|
+
...profile_config ? { profile_config } : {}
|
|
2704
|
+
}
|
|
2433
2705
|
});
|
|
2434
2706
|
return { content: [{ type: "text", text: `Indexed "${title}" (${content.length} chars).` }] };
|
|
2435
2707
|
} catch (error) {
|
|
@@ -2449,9 +2721,12 @@ server.tool(
|
|
|
2449
2721
|
auto_sync: z.boolean().optional().default(true),
|
|
2450
2722
|
tags: z.array(z.string()).optional(),
|
|
2451
2723
|
platform: z.enum(["youtube", "loom", "generic"]).optional(),
|
|
2452
|
-
language: z.string().optional()
|
|
2724
|
+
language: z.string().optional(),
|
|
2725
|
+
ingestion_profile: z.enum(["auto", "repo", "web_docs", "pdf_layout", "video_transcript", "plain_text"]).optional(),
|
|
2726
|
+
strategy_override: z.enum(["fixed", "recursive", "semantic", "hierarchical", "adaptive"]).optional(),
|
|
2727
|
+
profile_config: z.record(z.any()).optional()
|
|
2453
2728
|
},
|
|
2454
|
-
async ({ project, source_type, title, content, url, auto_sync, tags, platform, language }) => {
|
|
2729
|
+
async ({ project, source_type, title, content, url, auto_sync, tags, platform, language, ingestion_profile, strategy_override, profile_config }) => {
|
|
2455
2730
|
try {
|
|
2456
2731
|
const resolvedProject = await resolveProjectRef(project);
|
|
2457
2732
|
if (!resolvedProject) {
|
|
@@ -2467,7 +2742,10 @@ server.tool(
|
|
|
2467
2742
|
auto_sync,
|
|
2468
2743
|
tags,
|
|
2469
2744
|
platform,
|
|
2470
|
-
language
|
|
2745
|
+
language,
|
|
2746
|
+
ingestion_profile,
|
|
2747
|
+
strategy_override,
|
|
2748
|
+
profile_config
|
|
2471
2749
|
});
|
|
2472
2750
|
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
2473
2751
|
}
|
|
@@ -2478,7 +2756,13 @@ server.tool(
|
|
|
2478
2756
|
project: resolvedProject,
|
|
2479
2757
|
title: title || "Document",
|
|
2480
2758
|
content,
|
|
2481
|
-
metadata: {
|
|
2759
|
+
metadata: {
|
|
2760
|
+
source: "mcp:add_document",
|
|
2761
|
+
tags: tags || [],
|
|
2762
|
+
...ingestion_profile ? { ingestion_profile } : {},
|
|
2763
|
+
...strategy_override ? { strategy_override } : {},
|
|
2764
|
+
...profile_config ? { profile_config } : {}
|
|
2765
|
+
}
|
|
2482
2766
|
});
|
|
2483
2767
|
return { content: [{ type: "text", text: `Indexed "${title || "Document"}" (${content.length} chars).` }] };
|
|
2484
2768
|
} catch (error) {
|
|
@@ -2511,23 +2795,17 @@ server.tool(
|
|
|
2511
2795
|
top_k,
|
|
2512
2796
|
include_relations
|
|
2513
2797
|
});
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
|
|
2517
|
-
|
|
2518
|
-
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
line += ` Event: ${new Date(r.event_date).toISOString().split("T")[0]}
|
|
2524
|
-
`;
|
|
2525
|
-
}
|
|
2526
|
-
return line;
|
|
2527
|
-
}).join("\n");
|
|
2528
|
-
return { content: [{ type: "text", text }] };
|
|
2798
|
+
const normalizedResults = formatCanonicalMemoryResults(results);
|
|
2799
|
+
return primaryToolSuccess({
|
|
2800
|
+
tool: "memory.search_sota",
|
|
2801
|
+
query,
|
|
2802
|
+
question_date: question_date || null,
|
|
2803
|
+
include_relations,
|
|
2804
|
+
results: normalizedResults,
|
|
2805
|
+
count: normalizedResults.length
|
|
2806
|
+
});
|
|
2529
2807
|
} catch (error) {
|
|
2530
|
-
return
|
|
2808
|
+
return primaryToolError(error.message);
|
|
2531
2809
|
}
|
|
2532
2810
|
}
|
|
2533
2811
|
);
|
|
@@ -2546,16 +2824,21 @@ server.tool(
|
|
|
2546
2824
|
},
|
|
2547
2825
|
async ({ project, session_id, user_id, messages }) => {
|
|
2548
2826
|
try {
|
|
2827
|
+
const normalizedMessages = messages.map((message) => ({
|
|
2828
|
+
role: message.role,
|
|
2829
|
+
content: message.content,
|
|
2830
|
+
timestamp: message.timestamp
|
|
2831
|
+
}));
|
|
2549
2832
|
const result = await whisper.ingestSession({
|
|
2550
2833
|
project,
|
|
2551
2834
|
session_id,
|
|
2552
2835
|
user_id,
|
|
2553
|
-
messages
|
|
2836
|
+
messages: normalizedMessages
|
|
2554
2837
|
});
|
|
2555
2838
|
return {
|
|
2556
2839
|
content: [{
|
|
2557
2840
|
type: "text",
|
|
2558
|
-
text: `Processed ${
|
|
2841
|
+
text: `Processed ${normalizedMessages.length} messages:
|
|
2559
2842
|
- Created ${result.memories_created} memories
|
|
2560
2843
|
- Detected ${result.relations_created} relations
|
|
2561
2844
|
- Updated ${result.memories_invalidated} outdated memories` + (result.errors && result.errors.length > 0 ? `
|
|
@@ -3460,25 +3743,50 @@ server.tool(
|
|
|
3460
3743
|
);
|
|
3461
3744
|
server.tool(
|
|
3462
3745
|
"search",
|
|
3463
|
-
"Search
|
|
3746
|
+
"Search retrievable context by query, exact id, or both. Use `id` for exact fetch and `query` for semantic retrieval.",
|
|
3464
3747
|
{
|
|
3465
3748
|
project: z.string().optional().describe("Project name or slug"),
|
|
3466
|
-
query: z.string().describe("
|
|
3749
|
+
query: z.string().optional().describe("Semantic retrieval query"),
|
|
3750
|
+
id: z.string().optional().describe("Exact memory id to fetch"),
|
|
3467
3751
|
top_k: z.number().optional().default(10),
|
|
3468
3752
|
include_memories: z.boolean().optional().default(false),
|
|
3469
3753
|
include_graph: z.boolean().optional().default(false),
|
|
3470
3754
|
user_id: z.string().optional(),
|
|
3471
3755
|
session_id: z.string().optional()
|
|
3472
3756
|
},
|
|
3473
|
-
async ({ project, query, top_k, include_memories, include_graph, user_id, session_id }) => {
|
|
3757
|
+
async ({ project, query, id, top_k, include_memories, include_graph, user_id, session_id }) => {
|
|
3474
3758
|
try {
|
|
3759
|
+
if (!id && !query) {
|
|
3760
|
+
return toTextResult(buildMcpSearchError("Provide query, id, or both."));
|
|
3761
|
+
}
|
|
3762
|
+
let exactMemory;
|
|
3763
|
+
if (id) {
|
|
3764
|
+
const memoryResult = await whisper.getMemory(id);
|
|
3765
|
+
exactMemory = memoryResult?.memory || memoryResult;
|
|
3766
|
+
}
|
|
3767
|
+
if (!query) {
|
|
3768
|
+
return toTextResult(buildMcpSearchPayload({
|
|
3769
|
+
mode: "exact",
|
|
3770
|
+
id,
|
|
3771
|
+
exactMemory
|
|
3772
|
+
}));
|
|
3773
|
+
}
|
|
3475
3774
|
const resolvedProject = await resolveProjectRef(project);
|
|
3476
3775
|
if (!resolvedProject) {
|
|
3477
|
-
|
|
3776
|
+
if (exactMemory) {
|
|
3777
|
+
return toTextResult(buildMcpSearchPayload({
|
|
3778
|
+
mode: "hybrid",
|
|
3779
|
+
query,
|
|
3780
|
+
id,
|
|
3781
|
+
exactMemory,
|
|
3782
|
+
warnings: ["Project not resolved, so semantic context was skipped."]
|
|
3783
|
+
}));
|
|
3784
|
+
}
|
|
3785
|
+
return toTextResult(buildMcpSearchError("No project resolved. Set WHISPER_PROJECT or pass project."));
|
|
3478
3786
|
}
|
|
3479
3787
|
const queryResult = await queryWithDegradedFallback({
|
|
3480
3788
|
project: resolvedProject,
|
|
3481
|
-
query,
|
|
3789
|
+
query: query || exactMemory?.content || "",
|
|
3482
3790
|
top_k,
|
|
3483
3791
|
include_memories,
|
|
3484
3792
|
include_graph,
|
|
@@ -3486,15 +3794,18 @@ server.tool(
|
|
|
3486
3794
|
session_id
|
|
3487
3795
|
});
|
|
3488
3796
|
const response = queryResult.response;
|
|
3489
|
-
|
|
3490
|
-
|
|
3491
|
-
|
|
3492
|
-
|
|
3493
|
-
|
|
3494
|
-
|
|
3495
|
-
|
|
3797
|
+
return toTextResult(buildMcpSearchPayload({
|
|
3798
|
+
mode: exactMemory ? "hybrid" : "semantic",
|
|
3799
|
+
query,
|
|
3800
|
+
id,
|
|
3801
|
+
exactMemory,
|
|
3802
|
+
context: response.context,
|
|
3803
|
+
results: response.results,
|
|
3804
|
+
degradedMode: queryResult.degraded_mode,
|
|
3805
|
+
degradedReason: queryResult.degraded_reason
|
|
3806
|
+
}));
|
|
3496
3807
|
} catch (error) {
|
|
3497
|
-
return
|
|
3808
|
+
return primaryToolError(error.message);
|
|
3498
3809
|
}
|
|
3499
3810
|
}
|
|
3500
3811
|
);
|
|
@@ -3534,7 +3845,13 @@ server.tool(
|
|
|
3534
3845
|
}
|
|
3535
3846
|
collect(rootPath);
|
|
3536
3847
|
if (files.length === 0) {
|
|
3537
|
-
return {
|
|
3848
|
+
return primaryToolSuccess({
|
|
3849
|
+
tool: "search_code",
|
|
3850
|
+
query,
|
|
3851
|
+
path: rootPath,
|
|
3852
|
+
results: [],
|
|
3853
|
+
count: 0
|
|
3854
|
+
});
|
|
3538
3855
|
}
|
|
3539
3856
|
const documents = [];
|
|
3540
3857
|
for (const filePath of files) {
|
|
@@ -3555,13 +3872,23 @@ server.tool(
|
|
|
3555
3872
|
threshold: threshold ?? 0.2
|
|
3556
3873
|
});
|
|
3557
3874
|
if (!response.results?.length) {
|
|
3558
|
-
return {
|
|
3875
|
+
return primaryToolSuccess({
|
|
3876
|
+
tool: "search_code",
|
|
3877
|
+
query,
|
|
3878
|
+
path: rootPath,
|
|
3879
|
+
results: [],
|
|
3880
|
+
count: 0
|
|
3881
|
+
});
|
|
3559
3882
|
}
|
|
3560
|
-
|
|
3561
|
-
|
|
3562
|
-
|
|
3883
|
+
return primaryToolSuccess({
|
|
3884
|
+
tool: "search_code",
|
|
3885
|
+
query,
|
|
3886
|
+
path: rootPath,
|
|
3887
|
+
results: response.results,
|
|
3888
|
+
count: response.results.length
|
|
3889
|
+
});
|
|
3563
3890
|
} catch (error) {
|
|
3564
|
-
return
|
|
3891
|
+
return primaryToolError(`Semantic search failed: ${error.message}`);
|
|
3565
3892
|
}
|
|
3566
3893
|
}
|
|
3567
3894
|
);
|
|
@@ -3585,9 +3912,9 @@ server.tool(
|
|
|
3585
3912
|
const stat = statSync(filePath);
|
|
3586
3913
|
if (stat.size > 512 * 1024) continue;
|
|
3587
3914
|
const text = readFileSync(filePath, "utf-8");
|
|
3588
|
-
const
|
|
3915
|
+
const lines = text.split("\n");
|
|
3589
3916
|
const matches = [];
|
|
3590
|
-
|
|
3917
|
+
lines.forEach((line, index) => {
|
|
3591
3918
|
regex.lastIndex = 0;
|
|
3592
3919
|
if (regex.test(line)) {
|
|
3593
3920
|
matches.push({ line: index + 1, content: line.trimEnd() });
|
|
@@ -3600,14 +3927,21 @@ server.tool(
|
|
|
3600
3927
|
}
|
|
3601
3928
|
}
|
|
3602
3929
|
if (!results.length) {
|
|
3603
|
-
return {
|
|
3930
|
+
return primaryToolSuccess({
|
|
3931
|
+
tool: "grep",
|
|
3932
|
+
query,
|
|
3933
|
+
path: rootPath,
|
|
3934
|
+
results: [],
|
|
3935
|
+
count: 0
|
|
3936
|
+
});
|
|
3604
3937
|
}
|
|
3605
|
-
|
|
3606
|
-
|
|
3607
|
-
|
|
3608
|
-
|
|
3609
|
-
|
|
3610
|
-
|
|
3938
|
+
return primaryToolSuccess({
|
|
3939
|
+
tool: "grep",
|
|
3940
|
+
query,
|
|
3941
|
+
path: rootPath,
|
|
3942
|
+
results,
|
|
3943
|
+
count: results.length
|
|
3944
|
+
});
|
|
3611
3945
|
}
|
|
3612
3946
|
);
|
|
3613
3947
|
server.tool(
|
|
@@ -3623,11 +3957,17 @@ server.tool(
|
|
|
3623
3957
|
const fullPath = path.includes(":") || path.startsWith("/") ? path : join(process.cwd(), path);
|
|
3624
3958
|
const stats = statSync(fullPath);
|
|
3625
3959
|
if (!stats.isFile()) {
|
|
3626
|
-
return {
|
|
3960
|
+
return primaryToolError(`${path} is not a file.`, "invalid_request");
|
|
3627
3961
|
}
|
|
3628
|
-
return {
|
|
3962
|
+
return primaryToolSuccess({
|
|
3963
|
+
tool: "read",
|
|
3964
|
+
path: fullPath,
|
|
3965
|
+
start_line,
|
|
3966
|
+
end_line,
|
|
3967
|
+
content: readFileWindow(fullPath, start_line, end_line)
|
|
3968
|
+
});
|
|
3629
3969
|
} catch (error) {
|
|
3630
|
-
return
|
|
3970
|
+
return primaryToolError(error.message);
|
|
3631
3971
|
}
|
|
3632
3972
|
}
|
|
3633
3973
|
);
|
|
@@ -3644,11 +3984,21 @@ server.tool(
|
|
|
3644
3984
|
const rootPath = path || process.cwd();
|
|
3645
3985
|
const tree = listTree(rootPath, max_depth, max_entries);
|
|
3646
3986
|
if (!tree.length) {
|
|
3647
|
-
return {
|
|
3987
|
+
return primaryToolSuccess({
|
|
3988
|
+
tool: "explore",
|
|
3989
|
+
path: rootPath,
|
|
3990
|
+
entries: [],
|
|
3991
|
+
count: 0
|
|
3992
|
+
});
|
|
3648
3993
|
}
|
|
3649
|
-
return {
|
|
3994
|
+
return primaryToolSuccess({
|
|
3995
|
+
tool: "explore",
|
|
3996
|
+
path: rootPath,
|
|
3997
|
+
entries: tree,
|
|
3998
|
+
count: tree.length
|
|
3999
|
+
});
|
|
3650
4000
|
} catch (error) {
|
|
3651
|
-
return
|
|
4001
|
+
return primaryToolError(error.message);
|
|
3652
4002
|
}
|
|
3653
4003
|
}
|
|
3654
4004
|
);
|
|
@@ -3665,17 +4015,16 @@ server.tool(
|
|
|
3665
4015
|
async ({ project, query, mode, max_results, max_steps }) => {
|
|
3666
4016
|
try {
|
|
3667
4017
|
const results = await whisper.oracleSearch({ project, query, mode, max_results, max_steps });
|
|
3668
|
-
|
|
3669
|
-
|
|
3670
|
-
|
|
3671
|
-
|
|
3672
|
-
|
|
3673
|
-
|
|
3674
|
-
|
|
3675
|
-
|
|
3676
|
-
return { content: [{ type: "text", text }] };
|
|
4018
|
+
return primaryToolSuccess({
|
|
4019
|
+
tool: "research",
|
|
4020
|
+
mode,
|
|
4021
|
+
query,
|
|
4022
|
+
answer: results.answer || null,
|
|
4023
|
+
results: results.results || [],
|
|
4024
|
+
count: Array.isArray(results.results) ? results.results.length : 0
|
|
4025
|
+
});
|
|
3677
4026
|
} catch (error) {
|
|
3678
|
-
return
|
|
4027
|
+
return primaryToolError(error.message);
|
|
3679
4028
|
}
|
|
3680
4029
|
}
|
|
3681
4030
|
);
|
|
@@ -3771,34 +4120,95 @@ server.tool(
|
|
|
3771
4120
|
async ({ project, content, memory_type, user_id, session_id, agent_id, importance }) => {
|
|
3772
4121
|
try {
|
|
3773
4122
|
const result = await whisper.addMemory({ project, content, memory_type, user_id, session_id, agent_id, importance });
|
|
3774
|
-
|
|
4123
|
+
const memoryId = result?.memory_id || (result.mode === "sync" ? result.id : null);
|
|
4124
|
+
const jobId = result?.job_id || (result.mode === "async" ? result.id : null);
|
|
4125
|
+
return primaryToolSuccess({
|
|
4126
|
+
tool: "remember",
|
|
4127
|
+
id: memoryId || jobId || null,
|
|
4128
|
+
memory_id: memoryId,
|
|
4129
|
+
job_id: jobId,
|
|
4130
|
+
mode: result?.mode || null,
|
|
4131
|
+
memory_type,
|
|
4132
|
+
stored: result.success === true,
|
|
4133
|
+
queued: result?.mode === "async" || Boolean(jobId)
|
|
4134
|
+
});
|
|
3775
4135
|
} catch (error) {
|
|
3776
|
-
return
|
|
4136
|
+
return primaryToolError(error.message);
|
|
3777
4137
|
}
|
|
3778
4138
|
}
|
|
3779
4139
|
);
|
|
3780
4140
|
server.tool(
|
|
3781
|
-
"
|
|
3782
|
-
"
|
|
4141
|
+
"record",
|
|
4142
|
+
"Record what just happened in a session or conversation. Use this for temporal capture, not durable preference storage.",
|
|
3783
4143
|
{
|
|
3784
4144
|
project: z.string().optional(),
|
|
3785
|
-
|
|
4145
|
+
session_id: z.string().describe("Session to record into"),
|
|
3786
4146
|
user_id: z.string().optional(),
|
|
3787
|
-
|
|
3788
|
-
|
|
3789
|
-
|
|
4147
|
+
messages: z.array(z.object({
|
|
4148
|
+
role: z.string().optional(),
|
|
4149
|
+
content: z.string(),
|
|
4150
|
+
timestamp: z.string().optional()
|
|
4151
|
+
})).optional(),
|
|
4152
|
+
role: z.string().optional(),
|
|
4153
|
+
content: z.string().optional(),
|
|
4154
|
+
timestamp: z.string().optional()
|
|
3790
4155
|
},
|
|
3791
|
-
async ({ project,
|
|
4156
|
+
async ({ project, session_id, user_id, messages, role, content, timestamp }) => {
|
|
3792
4157
|
try {
|
|
3793
|
-
const
|
|
3794
|
-
|
|
3795
|
-
|
|
3796
|
-
|
|
3797
|
-
|
|
3798
|
-
|
|
3799
|
-
|
|
4158
|
+
const normalizedMessages = normalizeRecordMessages({
|
|
4159
|
+
messages: messages?.map((message) => ({
|
|
4160
|
+
role: message.role,
|
|
4161
|
+
content: message.content,
|
|
4162
|
+
timestamp: message.timestamp
|
|
4163
|
+
})),
|
|
4164
|
+
role,
|
|
4165
|
+
content,
|
|
4166
|
+
timestamp
|
|
4167
|
+
});
|
|
4168
|
+
const result = await whisper.ingestSession({ project, session_id, user_id, messages: normalizedMessages });
|
|
4169
|
+
return primaryToolSuccess({
|
|
4170
|
+
tool: "record",
|
|
4171
|
+
session_id,
|
|
4172
|
+
messages_recorded: normalizedMessages.length,
|
|
4173
|
+
memories_created: result.memories_created,
|
|
4174
|
+
relations_created: result.relations_created
|
|
4175
|
+
});
|
|
3800
4176
|
} catch (error) {
|
|
3801
|
-
return
|
|
4177
|
+
return primaryToolError(error.message);
|
|
4178
|
+
}
|
|
4179
|
+
}
|
|
4180
|
+
);
|
|
4181
|
+
server.tool(
|
|
4182
|
+
"learn",
|
|
4183
|
+
"Learn content or connect a source so it becomes retrievable later. Use this for docs, URLs, repos, files, or local paths.",
|
|
4184
|
+
{
|
|
4185
|
+
project: z.string().optional(),
|
|
4186
|
+
content: z.string().optional().describe("Inline text content to ingest"),
|
|
4187
|
+
title: z.string().optional().describe("Title for inline content"),
|
|
4188
|
+
url: z.string().optional().describe("URL to learn from"),
|
|
4189
|
+
owner: z.string().optional().describe("GitHub owner"),
|
|
4190
|
+
repo: z.string().optional().describe("GitHub repository"),
|
|
4191
|
+
branch: z.string().optional(),
|
|
4192
|
+
path: z.string().optional().describe("Local path to learn from"),
|
|
4193
|
+
file_path: z.string().optional().describe("Single file path to learn from"),
|
|
4194
|
+
name: z.string().optional().describe("Optional source name"),
|
|
4195
|
+
metadata: z.record(z.string()).optional(),
|
|
4196
|
+
ingestion_profile: z.enum(["auto", "repo", "web_docs", "pdf_layout", "video_transcript", "plain_text"]).optional(),
|
|
4197
|
+
strategy_override: z.enum(["fixed", "recursive", "semantic", "hierarchical", "adaptive"]).optional(),
|
|
4198
|
+
profile_config: z.record(z.any()).optional(),
|
|
4199
|
+
max_files: z.number().optional(),
|
|
4200
|
+
glob: z.string().optional(),
|
|
4201
|
+
crawl_depth: z.number().optional()
|
|
4202
|
+
},
|
|
4203
|
+
async (input) => {
|
|
4204
|
+
try {
|
|
4205
|
+
const result = await learnFromInput(input);
|
|
4206
|
+
return primaryToolSuccess({
|
|
4207
|
+
tool: "learn",
|
|
4208
|
+
...result
|
|
4209
|
+
});
|
|
4210
|
+
} catch (error) {
|
|
4211
|
+
return primaryToolError(error.message);
|
|
3802
4212
|
}
|
|
3803
4213
|
}
|
|
3804
4214
|
);
|
|
@@ -3814,18 +4224,15 @@ server.tool(
|
|
|
3814
4224
|
async ({ project, session_id, title, expiry_days }) => {
|
|
3815
4225
|
try {
|
|
3816
4226
|
const result = await whisper.createSharedContext({ project, session_id, title, expiry_days });
|
|
3817
|
-
return {
|
|
3818
|
-
|
|
3819
|
-
|
|
3820
|
-
|
|
3821
|
-
|
|
3822
|
-
|
|
3823
|
-
|
|
3824
|
-
Share URL: ${result.share_url}`
|
|
3825
|
-
}]
|
|
3826
|
-
};
|
|
4227
|
+
return primaryToolSuccess({
|
|
4228
|
+
tool: "share_context",
|
|
4229
|
+
share_id: result.share_id,
|
|
4230
|
+
share_url: result.share_url,
|
|
4231
|
+
expires_at: result.expires_at || null,
|
|
4232
|
+
title: result.title || title || null
|
|
4233
|
+
});
|
|
3827
4234
|
} catch (error) {
|
|
3828
|
-
return
|
|
4235
|
+
return primaryToolError(error.message);
|
|
3829
4236
|
}
|
|
3830
4237
|
}
|
|
3831
4238
|
);
|
|
@@ -3845,7 +4252,7 @@ async function main() {
|
|
|
3845
4252
|
const source = readArg("--source") || "source-or-type";
|
|
3846
4253
|
const client = readArg("--client") || "json";
|
|
3847
4254
|
const outPath = readArg("--write");
|
|
3848
|
-
const rendered =
|
|
4255
|
+
const rendered = renderScopedMcpConfig(project, source, client);
|
|
3849
4256
|
if (outPath) {
|
|
3850
4257
|
const backup = existsSync(outPath) ? `${outPath}.bak-${Date.now()}` : void 0;
|
|
3851
4258
|
if (backup) writeFileSync(backup, readFileSync(outPath, "utf-8"), "utf-8");
|
|
@@ -3866,4 +4273,11 @@ async function main() {
|
|
|
3866
4273
|
await server.connect(transport);
|
|
3867
4274
|
console.error("Whisper Context MCP server running on stdio");
|
|
3868
4275
|
}
|
|
3869
|
-
|
|
4276
|
+
if (process.argv[1] && /server\.(mjs|cjs|js|ts)$/.test(process.argv[1])) {
|
|
4277
|
+
main().catch(console.error);
|
|
4278
|
+
}
|
|
4279
|
+
export {
|
|
4280
|
+
createMcpServer,
|
|
4281
|
+
createWhisperMcpClient,
|
|
4282
|
+
renderScopedMcpConfig
|
|
4283
|
+
};
|
package/package.json
CHANGED