@usewhisper/mcp-server 1.4.0 → 2.0.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 +88 -18
- package/dist/server.js +383 -98
- 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,46 @@ 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
|
+
Write the generated config to a file:
|
|
108
162
|
|
|
109
|
-
```
|
|
110
|
-
whisper-context-mcp scope --project my-project --source github --client vscode --write
|
|
163
|
+
```text
|
|
164
|
+
whisper-context-mcp scope --project my-project --source github --client vscode --write /absolute/path/to/mcp.json
|
|
111
165
|
```
|
|
112
166
|
|
|
113
167
|
## Breaking Rename Migration
|
|
114
168
|
|
|
115
169
|
Print full map:
|
|
116
170
|
|
|
117
|
-
```
|
|
171
|
+
```text
|
|
118
172
|
whisper-context-mcp --print-tool-map
|
|
119
173
|
```
|
|
120
174
|
|
|
121
175
|
This release removes legacy un-namespaced tool names.
|
|
122
176
|
|
|
177
|
+
## Contract Metadata
|
|
178
|
+
|
|
179
|
+
Public API metadata is available at:
|
|
180
|
+
|
|
181
|
+
```text
|
|
182
|
+
GET /v1/contracts/meta
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
It exposes:
|
|
186
|
+
|
|
187
|
+
- current contract version
|
|
188
|
+
- active surfaces (`http`, `sdk`, `mcp`)
|
|
189
|
+
- approved primary MCP verbs
|
|
190
|
+
- migration window and removal policy
|
|
191
|
+
- deprecated HTTP routes with replacement hints
|
|
192
|
+
|
|
193
|
+
It does not expose internal security checklist fields.
|
|
194
|
+
|
|
123
195
|
## Security Defaults
|
|
124
196
|
|
|
125
197
|
Local ingest (`index.local_scan_ingest` and `type=local`) enforces:
|
|
@@ -129,18 +201,16 @@ Local ingest (`index.local_scan_ingest` and `type=local`) enforces:
|
|
|
129
201
|
|
|
130
202
|
## 30-Second Demo
|
|
131
203
|
|
|
132
|
-
One
|
|
204
|
+
One command:
|
|
133
205
|
|
|
134
|
-
```
|
|
206
|
+
```text
|
|
135
207
|
npx whisper-wizard
|
|
136
208
|
```
|
|
137
209
|
|
|
138
210
|
Flow:
|
|
139
211
|
1. Run wizard and complete auth/project setup.
|
|
140
|
-
2. Add a source with MCP:
|
|
141
|
-
`context.
|
|
142
|
-
3. Ask a grounded question:
|
|
143
|
-
`context.query` or `context.evidence_answer` for citation-locked output.
|
|
212
|
+
2. Add a source with MCP: `context.add_source`
|
|
213
|
+
3. Ask a grounded question: `context.query` or `context.evidence_answer`
|
|
144
214
|
|
|
145
215
|
## License
|
|
146
216
|
|
package/dist/server.js
CHANGED
|
@@ -488,7 +488,7 @@ var WhisperContext = class _WhisperContext {
|
|
|
488
488
|
});
|
|
489
489
|
warnDeprecatedOnce(
|
|
490
490
|
"whisper_context_class",
|
|
491
|
-
"[Whisper SDK] WhisperContext
|
|
491
|
+
"[Whisper SDK] WhisperContext is deprecated in v3 and scheduled for removal in v4. Prefer WhisperClient for runtime features and future contract compatibility."
|
|
492
492
|
);
|
|
493
493
|
}
|
|
494
494
|
withProject(project) {
|
|
@@ -1090,6 +1090,16 @@ var WhisperContext = class _WhisperContext {
|
|
|
1090
1090
|
}
|
|
1091
1091
|
});
|
|
1092
1092
|
}
|
|
1093
|
+
async getMemory(memoryId) {
|
|
1094
|
+
try {
|
|
1095
|
+
return await this.request(`/v1/memory/${memoryId}`);
|
|
1096
|
+
} catch (error) {
|
|
1097
|
+
if (!this.isEndpointNotFoundError(error)) {
|
|
1098
|
+
throw error;
|
|
1099
|
+
}
|
|
1100
|
+
return this.request(`/v1/memories/${memoryId}`);
|
|
1101
|
+
}
|
|
1102
|
+
}
|
|
1093
1103
|
async getMemoryVersions(memoryId) {
|
|
1094
1104
|
return this.request(`/v1/memory/${memoryId}/versions`);
|
|
1095
1105
|
}
|
|
@@ -1288,6 +1298,7 @@ var WhisperContext = class _WhisperContext {
|
|
|
1288
1298
|
ingestSession: (params) => this.ingestSession(params),
|
|
1289
1299
|
getSessionMemories: (params) => this.getSessionMemories(params),
|
|
1290
1300
|
getUserProfile: (params) => this.getUserProfile(params),
|
|
1301
|
+
get: (memoryId) => this.getMemory(memoryId),
|
|
1291
1302
|
getVersions: (memoryId) => this.getMemoryVersions(memoryId),
|
|
1292
1303
|
update: (memoryId, params) => this.updateMemory(memoryId, params),
|
|
1293
1304
|
delete: (memoryId) => this.deleteMemory(memoryId),
|
|
@@ -1321,6 +1332,75 @@ var WhisperContext = class _WhisperContext {
|
|
|
1321
1332
|
};
|
|
1322
1333
|
};
|
|
1323
1334
|
|
|
1335
|
+
// ../src/mcp/search-payload.mjs
|
|
1336
|
+
function normalizeExactMemory(memory) {
|
|
1337
|
+
if (!memory) return null;
|
|
1338
|
+
return {
|
|
1339
|
+
id: memory.id ? String(memory.id) : null,
|
|
1340
|
+
type: memory.type ? String(memory.type) : memory.memoryType ? String(memory.memoryType) : null,
|
|
1341
|
+
content: String(memory.content || ""),
|
|
1342
|
+
user_id: memory.user_id ? String(memory.user_id) : memory.userId ? String(memory.userId) : null,
|
|
1343
|
+
session_id: memory.session_id ? String(memory.session_id) : memory.sessionId ? String(memory.sessionId) : null,
|
|
1344
|
+
updated_at: memory.updated_at ? String(memory.updated_at) : memory.updatedAt ? String(memory.updatedAt) : null,
|
|
1345
|
+
metadata: memory.metadata && typeof memory.metadata === "object" && !Array.isArray(memory.metadata) ? memory.metadata : null
|
|
1346
|
+
};
|
|
1347
|
+
}
|
|
1348
|
+
function normalizeSearchResults(results) {
|
|
1349
|
+
return (results || []).map((result) => {
|
|
1350
|
+
const candidate = result?.memory ? result.memory : result;
|
|
1351
|
+
const similarity = result?.similarity;
|
|
1352
|
+
return {
|
|
1353
|
+
id: candidate?.id ? String(candidate.id) : null,
|
|
1354
|
+
content: String(candidate?.content || result?.content || ""),
|
|
1355
|
+
score: typeof similarity === "number" ? similarity : typeof result?.score === "number" ? result.score : similarity != null ? Number(similarity) : result?.score != null ? Number(result.score) : null,
|
|
1356
|
+
source: result?.source ? String(result.source) : result?.chunk ? "memory" : null,
|
|
1357
|
+
document: result?.document ? String(result.document) : result?.chunk?.id ? String(result.chunk.id) : null,
|
|
1358
|
+
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,
|
|
1359
|
+
memory_type: candidate?.type ? String(candidate.type) : candidate?.memory_type ? String(candidate.memory_type) : null
|
|
1360
|
+
};
|
|
1361
|
+
});
|
|
1362
|
+
}
|
|
1363
|
+
function normalizeCanonicalResults(input) {
|
|
1364
|
+
if (Array.isArray(input?.results)) return input.results;
|
|
1365
|
+
if (Array.isArray(input?.memories)) return input.memories;
|
|
1366
|
+
return [];
|
|
1367
|
+
}
|
|
1368
|
+
function buildPrimaryToolSuccess(payload) {
|
|
1369
|
+
return {
|
|
1370
|
+
success: true,
|
|
1371
|
+
...payload
|
|
1372
|
+
};
|
|
1373
|
+
}
|
|
1374
|
+
function buildPrimaryToolError(message, options = {}) {
|
|
1375
|
+
return {
|
|
1376
|
+
success: false,
|
|
1377
|
+
error: {
|
|
1378
|
+
code: options.code ?? "tool_error",
|
|
1379
|
+
message
|
|
1380
|
+
}
|
|
1381
|
+
};
|
|
1382
|
+
}
|
|
1383
|
+
function buildMcpSearchPayload(input) {
|
|
1384
|
+
const normalizedResults = normalizeSearchResults(input.results);
|
|
1385
|
+
return buildPrimaryToolSuccess({
|
|
1386
|
+
mode: input.mode,
|
|
1387
|
+
query: input.query ?? null,
|
|
1388
|
+
id: input.id ?? null,
|
|
1389
|
+
exact_memory: normalizeExactMemory(input.exactMemory),
|
|
1390
|
+
context: input.context ?? "",
|
|
1391
|
+
results: normalizedResults,
|
|
1392
|
+
count: normalizedResults.length,
|
|
1393
|
+
degraded_mode: Boolean(input.degradedMode),
|
|
1394
|
+
degraded_reason: input.degradedReason ?? null,
|
|
1395
|
+
warnings: input.warnings || []
|
|
1396
|
+
});
|
|
1397
|
+
}
|
|
1398
|
+
function buildMcpSearchError(message, options = {}) {
|
|
1399
|
+
return buildPrimaryToolError(message, {
|
|
1400
|
+
code: options.code ?? "invalid_request"
|
|
1401
|
+
});
|
|
1402
|
+
}
|
|
1403
|
+
|
|
1324
1404
|
// ../src/mcp/server.ts
|
|
1325
1405
|
var API_KEY = process.env.WHISPER_API_KEY || "";
|
|
1326
1406
|
var DEFAULT_PROJECT = process.env.WHISPER_PROJECT || "";
|
|
@@ -1366,7 +1446,7 @@ var TOOL_MIGRATION_MAP = [
|
|
|
1366
1446
|
{ old: "semantic_search_codebase", next: "code.search_semantic" }
|
|
1367
1447
|
];
|
|
1368
1448
|
var ALIAS_TOOL_MAP = [
|
|
1369
|
-
{ alias: "search", target: "context.query" },
|
|
1449
|
+
{ alias: "search", target: "context.query | memory.get" },
|
|
1370
1450
|
{ alias: "search_code", target: "code.search_semantic" },
|
|
1371
1451
|
{ alias: "grep", target: "code.search_text" },
|
|
1372
1452
|
{ alias: "read", target: "local.file_read" },
|
|
@@ -1374,7 +1454,8 @@ var ALIAS_TOOL_MAP = [
|
|
|
1374
1454
|
{ alias: "research", target: "research.oracle" },
|
|
1375
1455
|
{ alias: "index", target: "context.add_source | index.workspace_run" },
|
|
1376
1456
|
{ alias: "remember", target: "memory.add" },
|
|
1377
|
-
{ alias: "
|
|
1457
|
+
{ alias: "record", target: "memory.ingest_conversation" },
|
|
1458
|
+
{ alias: "learn", target: "context.add_text | context.add_source | context.add_document" },
|
|
1378
1459
|
{ alias: "share_context", target: "context.share" }
|
|
1379
1460
|
];
|
|
1380
1461
|
function ensureStateDir() {
|
|
@@ -1567,6 +1648,25 @@ function countCodeFiles(searchPath, maxFiles = 5e3) {
|
|
|
1567
1648
|
function toTextResult(payload) {
|
|
1568
1649
|
return { content: [{ type: "text", text: JSON.stringify(payload, null, 2) }] };
|
|
1569
1650
|
}
|
|
1651
|
+
function primaryToolSuccess(payload) {
|
|
1652
|
+
return toTextResult(buildPrimaryToolSuccess(payload));
|
|
1653
|
+
}
|
|
1654
|
+
function primaryToolError(message, code = "tool_error") {
|
|
1655
|
+
return toTextResult(buildPrimaryToolError(message, { code }));
|
|
1656
|
+
}
|
|
1657
|
+
function formatCanonicalMemoryResults(rawResults) {
|
|
1658
|
+
const results = normalizeCanonicalResults(rawResults);
|
|
1659
|
+
return results.map((result) => {
|
|
1660
|
+
const memory = result?.memory || result;
|
|
1661
|
+
return {
|
|
1662
|
+
id: memory?.id ? String(memory.id) : null,
|
|
1663
|
+
content: String(memory?.content || result?.content || ""),
|
|
1664
|
+
memory_type: memory?.type ? String(memory.type) : memory?.memory_type ? String(memory.memory_type) : null,
|
|
1665
|
+
similarity: typeof result?.similarity === "number" ? result.similarity : typeof result?.score === "number" ? result.score : null,
|
|
1666
|
+
metadata: memory?.metadata && typeof memory.metadata === "object" && !Array.isArray(memory.metadata) ? memory.metadata : null
|
|
1667
|
+
};
|
|
1668
|
+
});
|
|
1669
|
+
}
|
|
1570
1670
|
function likelyEmbeddingFailure(error) {
|
|
1571
1671
|
const message = String(error?.message || error || "").toLowerCase();
|
|
1572
1672
|
return message.includes("embedding") || message.includes("vector") || message.includes("timeout") || message.includes("timed out") || message.includes("temporarily unavailable");
|
|
@@ -1798,6 +1898,90 @@ async function createSourceByType(params) {
|
|
|
1798
1898
|
warnings: []
|
|
1799
1899
|
};
|
|
1800
1900
|
}
|
|
1901
|
+
function normalizeRecordMessages(input) {
|
|
1902
|
+
if (Array.isArray(input.messages) && input.messages.length > 0) {
|
|
1903
|
+
return input.messages.map((message) => ({
|
|
1904
|
+
role: message.role || "user",
|
|
1905
|
+
content: message.content,
|
|
1906
|
+
timestamp: message.timestamp || (/* @__PURE__ */ new Date()).toISOString()
|
|
1907
|
+
}));
|
|
1908
|
+
}
|
|
1909
|
+
if (!input.content) {
|
|
1910
|
+
throw new Error("Provide messages[] or content.");
|
|
1911
|
+
}
|
|
1912
|
+
return [{
|
|
1913
|
+
role: input.role || "user",
|
|
1914
|
+
content: input.content,
|
|
1915
|
+
timestamp: input.timestamp || (/* @__PURE__ */ new Date()).toISOString()
|
|
1916
|
+
}];
|
|
1917
|
+
}
|
|
1918
|
+
async function learnFromInput(input) {
|
|
1919
|
+
const resolvedProject = await resolveProjectRef(input.project);
|
|
1920
|
+
if (!resolvedProject) {
|
|
1921
|
+
throw new Error("No project resolved. Set WHISPER_PROJECT or provide project.");
|
|
1922
|
+
}
|
|
1923
|
+
if (input.content) {
|
|
1924
|
+
const result = await whisper.addContext({
|
|
1925
|
+
project: resolvedProject,
|
|
1926
|
+
content: input.content,
|
|
1927
|
+
title: input.title || "Learned Context",
|
|
1928
|
+
metadata: input.metadata
|
|
1929
|
+
});
|
|
1930
|
+
return {
|
|
1931
|
+
mode: "text",
|
|
1932
|
+
project: resolvedProject,
|
|
1933
|
+
ingested: result.ingested
|
|
1934
|
+
};
|
|
1935
|
+
}
|
|
1936
|
+
if (input.owner && input.repo) {
|
|
1937
|
+
return createSourceByType({
|
|
1938
|
+
project: resolvedProject,
|
|
1939
|
+
type: "github",
|
|
1940
|
+
owner: input.owner,
|
|
1941
|
+
repo: input.repo,
|
|
1942
|
+
branch: input.branch,
|
|
1943
|
+
name: input.name,
|
|
1944
|
+
auto_index: true,
|
|
1945
|
+
metadata: input.metadata
|
|
1946
|
+
});
|
|
1947
|
+
}
|
|
1948
|
+
if (input.path) {
|
|
1949
|
+
return createSourceByType({
|
|
1950
|
+
project: resolvedProject,
|
|
1951
|
+
type: "local",
|
|
1952
|
+
path: input.path,
|
|
1953
|
+
glob: input.glob,
|
|
1954
|
+
max_files: input.max_files,
|
|
1955
|
+
name: input.name,
|
|
1956
|
+
metadata: input.metadata
|
|
1957
|
+
});
|
|
1958
|
+
}
|
|
1959
|
+
if (input.file_path) {
|
|
1960
|
+
return createSourceByType({
|
|
1961
|
+
project: resolvedProject,
|
|
1962
|
+
type: "pdf",
|
|
1963
|
+
file_path: input.file_path,
|
|
1964
|
+
name: input.name,
|
|
1965
|
+
metadata: input.metadata
|
|
1966
|
+
});
|
|
1967
|
+
}
|
|
1968
|
+
if (input.url) {
|
|
1969
|
+
return createSourceByType({
|
|
1970
|
+
project: resolvedProject,
|
|
1971
|
+
type: input.url.endsWith(".pdf") ? "pdf" : "web",
|
|
1972
|
+
url: input.url,
|
|
1973
|
+
name: input.name,
|
|
1974
|
+
metadata: input.metadata,
|
|
1975
|
+
crawl_depth: input.crawl_depth,
|
|
1976
|
+
channel_ids: input.channel_ids,
|
|
1977
|
+
token: input.token,
|
|
1978
|
+
workspace_id: input.workspace_id,
|
|
1979
|
+
since: input.since,
|
|
1980
|
+
auth_ref: input.auth_ref
|
|
1981
|
+
});
|
|
1982
|
+
}
|
|
1983
|
+
throw new Error("Provide content, owner+repo, path, file_path, or url.");
|
|
1984
|
+
}
|
|
1801
1985
|
function scopeConfigJson(project, source, client) {
|
|
1802
1986
|
const serverDef = {
|
|
1803
1987
|
command: "npx",
|
|
@@ -2288,14 +2472,15 @@ server.tool(
|
|
|
2288
2472
|
top_k,
|
|
2289
2473
|
memory_types
|
|
2290
2474
|
});
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
|
|
2475
|
+
const normalizedResults = formatCanonicalMemoryResults(results);
|
|
2476
|
+
return primaryToolSuccess({
|
|
2477
|
+
tool: "memory.search",
|
|
2478
|
+
query,
|
|
2479
|
+
results: normalizedResults,
|
|
2480
|
+
count: normalizedResults.length
|
|
2481
|
+
});
|
|
2297
2482
|
} catch (error) {
|
|
2298
|
-
return
|
|
2483
|
+
return primaryToolError(error.message);
|
|
2299
2484
|
}
|
|
2300
2485
|
}
|
|
2301
2486
|
);
|
|
@@ -2511,23 +2696,17 @@ server.tool(
|
|
|
2511
2696
|
top_k,
|
|
2512
2697
|
include_relations
|
|
2513
2698
|
});
|
|
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 }] };
|
|
2699
|
+
const normalizedResults = formatCanonicalMemoryResults(results);
|
|
2700
|
+
return primaryToolSuccess({
|
|
2701
|
+
tool: "memory.search_sota",
|
|
2702
|
+
query,
|
|
2703
|
+
question_date: question_date || null,
|
|
2704
|
+
include_relations,
|
|
2705
|
+
results: normalizedResults,
|
|
2706
|
+
count: normalizedResults.length
|
|
2707
|
+
});
|
|
2529
2708
|
} catch (error) {
|
|
2530
|
-
return
|
|
2709
|
+
return primaryToolError(error.message);
|
|
2531
2710
|
}
|
|
2532
2711
|
}
|
|
2533
2712
|
);
|
|
@@ -3460,25 +3639,50 @@ server.tool(
|
|
|
3460
3639
|
);
|
|
3461
3640
|
server.tool(
|
|
3462
3641
|
"search",
|
|
3463
|
-
"Search
|
|
3642
|
+
"Search retrievable context by query, exact id, or both. Use `id` for exact fetch and `query` for semantic retrieval.",
|
|
3464
3643
|
{
|
|
3465
3644
|
project: z.string().optional().describe("Project name or slug"),
|
|
3466
|
-
query: z.string().describe("
|
|
3645
|
+
query: z.string().optional().describe("Semantic retrieval query"),
|
|
3646
|
+
id: z.string().optional().describe("Exact memory id to fetch"),
|
|
3467
3647
|
top_k: z.number().optional().default(10),
|
|
3468
3648
|
include_memories: z.boolean().optional().default(false),
|
|
3469
3649
|
include_graph: z.boolean().optional().default(false),
|
|
3470
3650
|
user_id: z.string().optional(),
|
|
3471
3651
|
session_id: z.string().optional()
|
|
3472
3652
|
},
|
|
3473
|
-
async ({ project, query, top_k, include_memories, include_graph, user_id, session_id }) => {
|
|
3653
|
+
async ({ project, query, id, top_k, include_memories, include_graph, user_id, session_id }) => {
|
|
3474
3654
|
try {
|
|
3655
|
+
if (!id && !query) {
|
|
3656
|
+
return toTextResult(buildMcpSearchError("Provide query, id, or both."));
|
|
3657
|
+
}
|
|
3658
|
+
let exactMemory;
|
|
3659
|
+
if (id) {
|
|
3660
|
+
const memoryResult = await whisper.getMemory(id);
|
|
3661
|
+
exactMemory = memoryResult?.memory || memoryResult;
|
|
3662
|
+
}
|
|
3663
|
+
if (!query) {
|
|
3664
|
+
return toTextResult(buildMcpSearchPayload({
|
|
3665
|
+
mode: "exact",
|
|
3666
|
+
id,
|
|
3667
|
+
exactMemory
|
|
3668
|
+
}));
|
|
3669
|
+
}
|
|
3475
3670
|
const resolvedProject = await resolveProjectRef(project);
|
|
3476
3671
|
if (!resolvedProject) {
|
|
3477
|
-
|
|
3672
|
+
if (exactMemory) {
|
|
3673
|
+
return toTextResult(buildMcpSearchPayload({
|
|
3674
|
+
mode: "hybrid",
|
|
3675
|
+
query,
|
|
3676
|
+
id,
|
|
3677
|
+
exactMemory,
|
|
3678
|
+
warnings: ["Project not resolved, so semantic context was skipped."]
|
|
3679
|
+
}));
|
|
3680
|
+
}
|
|
3681
|
+
return toTextResult(buildMcpSearchError("No project resolved. Set WHISPER_PROJECT or pass project."));
|
|
3478
3682
|
}
|
|
3479
3683
|
const queryResult = await queryWithDegradedFallback({
|
|
3480
3684
|
project: resolvedProject,
|
|
3481
|
-
query,
|
|
3685
|
+
query: query || exactMemory?.content || "",
|
|
3482
3686
|
top_k,
|
|
3483
3687
|
include_memories,
|
|
3484
3688
|
include_graph,
|
|
@@ -3486,15 +3690,18 @@ server.tool(
|
|
|
3486
3690
|
session_id
|
|
3487
3691
|
});
|
|
3488
3692
|
const response = queryResult.response;
|
|
3489
|
-
|
|
3490
|
-
|
|
3491
|
-
|
|
3492
|
-
|
|
3493
|
-
|
|
3494
|
-
|
|
3495
|
-
|
|
3693
|
+
return toTextResult(buildMcpSearchPayload({
|
|
3694
|
+
mode: exactMemory ? "hybrid" : "semantic",
|
|
3695
|
+
query,
|
|
3696
|
+
id,
|
|
3697
|
+
exactMemory,
|
|
3698
|
+
context: response.context,
|
|
3699
|
+
results: response.results,
|
|
3700
|
+
degradedMode: queryResult.degraded_mode,
|
|
3701
|
+
degradedReason: queryResult.degraded_reason
|
|
3702
|
+
}));
|
|
3496
3703
|
} catch (error) {
|
|
3497
|
-
return
|
|
3704
|
+
return primaryToolError(error.message);
|
|
3498
3705
|
}
|
|
3499
3706
|
}
|
|
3500
3707
|
);
|
|
@@ -3534,7 +3741,13 @@ server.tool(
|
|
|
3534
3741
|
}
|
|
3535
3742
|
collect(rootPath);
|
|
3536
3743
|
if (files.length === 0) {
|
|
3537
|
-
return {
|
|
3744
|
+
return primaryToolSuccess({
|
|
3745
|
+
tool: "search_code",
|
|
3746
|
+
query,
|
|
3747
|
+
path: rootPath,
|
|
3748
|
+
results: [],
|
|
3749
|
+
count: 0
|
|
3750
|
+
});
|
|
3538
3751
|
}
|
|
3539
3752
|
const documents = [];
|
|
3540
3753
|
for (const filePath of files) {
|
|
@@ -3555,13 +3768,23 @@ server.tool(
|
|
|
3555
3768
|
threshold: threshold ?? 0.2
|
|
3556
3769
|
});
|
|
3557
3770
|
if (!response.results?.length) {
|
|
3558
|
-
return {
|
|
3771
|
+
return primaryToolSuccess({
|
|
3772
|
+
tool: "search_code",
|
|
3773
|
+
query,
|
|
3774
|
+
path: rootPath,
|
|
3775
|
+
results: [],
|
|
3776
|
+
count: 0
|
|
3777
|
+
});
|
|
3559
3778
|
}
|
|
3560
|
-
|
|
3561
|
-
|
|
3562
|
-
|
|
3779
|
+
return primaryToolSuccess({
|
|
3780
|
+
tool: "search_code",
|
|
3781
|
+
query,
|
|
3782
|
+
path: rootPath,
|
|
3783
|
+
results: response.results,
|
|
3784
|
+
count: response.results.length
|
|
3785
|
+
});
|
|
3563
3786
|
} catch (error) {
|
|
3564
|
-
return
|
|
3787
|
+
return primaryToolError(`Semantic search failed: ${error.message}`);
|
|
3565
3788
|
}
|
|
3566
3789
|
}
|
|
3567
3790
|
);
|
|
@@ -3585,9 +3808,9 @@ server.tool(
|
|
|
3585
3808
|
const stat = statSync(filePath);
|
|
3586
3809
|
if (stat.size > 512 * 1024) continue;
|
|
3587
3810
|
const text = readFileSync(filePath, "utf-8");
|
|
3588
|
-
const
|
|
3811
|
+
const lines = text.split("\n");
|
|
3589
3812
|
const matches = [];
|
|
3590
|
-
|
|
3813
|
+
lines.forEach((line, index) => {
|
|
3591
3814
|
regex.lastIndex = 0;
|
|
3592
3815
|
if (regex.test(line)) {
|
|
3593
3816
|
matches.push({ line: index + 1, content: line.trimEnd() });
|
|
@@ -3600,14 +3823,21 @@ server.tool(
|
|
|
3600
3823
|
}
|
|
3601
3824
|
}
|
|
3602
3825
|
if (!results.length) {
|
|
3603
|
-
return {
|
|
3826
|
+
return primaryToolSuccess({
|
|
3827
|
+
tool: "grep",
|
|
3828
|
+
query,
|
|
3829
|
+
path: rootPath,
|
|
3830
|
+
results: [],
|
|
3831
|
+
count: 0
|
|
3832
|
+
});
|
|
3604
3833
|
}
|
|
3605
|
-
|
|
3606
|
-
|
|
3607
|
-
|
|
3608
|
-
|
|
3609
|
-
|
|
3610
|
-
|
|
3834
|
+
return primaryToolSuccess({
|
|
3835
|
+
tool: "grep",
|
|
3836
|
+
query,
|
|
3837
|
+
path: rootPath,
|
|
3838
|
+
results,
|
|
3839
|
+
count: results.length
|
|
3840
|
+
});
|
|
3611
3841
|
}
|
|
3612
3842
|
);
|
|
3613
3843
|
server.tool(
|
|
@@ -3623,11 +3853,17 @@ server.tool(
|
|
|
3623
3853
|
const fullPath = path.includes(":") || path.startsWith("/") ? path : join(process.cwd(), path);
|
|
3624
3854
|
const stats = statSync(fullPath);
|
|
3625
3855
|
if (!stats.isFile()) {
|
|
3626
|
-
return {
|
|
3856
|
+
return primaryToolError(`${path} is not a file.`, "invalid_request");
|
|
3627
3857
|
}
|
|
3628
|
-
return {
|
|
3858
|
+
return primaryToolSuccess({
|
|
3859
|
+
tool: "read",
|
|
3860
|
+
path: fullPath,
|
|
3861
|
+
start_line,
|
|
3862
|
+
end_line,
|
|
3863
|
+
content: readFileWindow(fullPath, start_line, end_line)
|
|
3864
|
+
});
|
|
3629
3865
|
} catch (error) {
|
|
3630
|
-
return
|
|
3866
|
+
return primaryToolError(error.message);
|
|
3631
3867
|
}
|
|
3632
3868
|
}
|
|
3633
3869
|
);
|
|
@@ -3644,11 +3880,21 @@ server.tool(
|
|
|
3644
3880
|
const rootPath = path || process.cwd();
|
|
3645
3881
|
const tree = listTree(rootPath, max_depth, max_entries);
|
|
3646
3882
|
if (!tree.length) {
|
|
3647
|
-
return {
|
|
3883
|
+
return primaryToolSuccess({
|
|
3884
|
+
tool: "explore",
|
|
3885
|
+
path: rootPath,
|
|
3886
|
+
entries: [],
|
|
3887
|
+
count: 0
|
|
3888
|
+
});
|
|
3648
3889
|
}
|
|
3649
|
-
return {
|
|
3890
|
+
return primaryToolSuccess({
|
|
3891
|
+
tool: "explore",
|
|
3892
|
+
path: rootPath,
|
|
3893
|
+
entries: tree,
|
|
3894
|
+
count: tree.length
|
|
3895
|
+
});
|
|
3650
3896
|
} catch (error) {
|
|
3651
|
-
return
|
|
3897
|
+
return primaryToolError(error.message);
|
|
3652
3898
|
}
|
|
3653
3899
|
}
|
|
3654
3900
|
);
|
|
@@ -3665,17 +3911,16 @@ server.tool(
|
|
|
3665
3911
|
async ({ project, query, mode, max_results, max_steps }) => {
|
|
3666
3912
|
try {
|
|
3667
3913
|
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 }] };
|
|
3914
|
+
return primaryToolSuccess({
|
|
3915
|
+
tool: "research",
|
|
3916
|
+
mode,
|
|
3917
|
+
query,
|
|
3918
|
+
answer: results.answer || null,
|
|
3919
|
+
results: results.results || [],
|
|
3920
|
+
count: Array.isArray(results.results) ? results.results.length : 0
|
|
3921
|
+
});
|
|
3677
3922
|
} catch (error) {
|
|
3678
|
-
return
|
|
3923
|
+
return primaryToolError(error.message);
|
|
3679
3924
|
}
|
|
3680
3925
|
}
|
|
3681
3926
|
);
|
|
@@ -3771,34 +4016,77 @@ server.tool(
|
|
|
3771
4016
|
async ({ project, content, memory_type, user_id, session_id, agent_id, importance }) => {
|
|
3772
4017
|
try {
|
|
3773
4018
|
const result = await whisper.addMemory({ project, content, memory_type, user_id, session_id, agent_id, importance });
|
|
3774
|
-
return
|
|
4019
|
+
return primaryToolSuccess({
|
|
4020
|
+
tool: "remember",
|
|
4021
|
+
id: result.id || null,
|
|
4022
|
+
memory_type,
|
|
4023
|
+
stored: result.success === true
|
|
4024
|
+
});
|
|
3775
4025
|
} catch (error) {
|
|
3776
|
-
return
|
|
4026
|
+
return primaryToolError(error.message);
|
|
3777
4027
|
}
|
|
3778
4028
|
}
|
|
3779
4029
|
);
|
|
3780
4030
|
server.tool(
|
|
3781
|
-
"
|
|
3782
|
-
"
|
|
4031
|
+
"record",
|
|
4032
|
+
"Record what just happened in a session or conversation. Use this for temporal capture, not durable preference storage.",
|
|
3783
4033
|
{
|
|
3784
4034
|
project: z.string().optional(),
|
|
3785
|
-
|
|
4035
|
+
session_id: z.string().describe("Session to record into"),
|
|
3786
4036
|
user_id: z.string().optional(),
|
|
3787
|
-
|
|
3788
|
-
|
|
3789
|
-
|
|
4037
|
+
messages: z.array(z.object({
|
|
4038
|
+
role: z.string().optional(),
|
|
4039
|
+
content: z.string(),
|
|
4040
|
+
timestamp: z.string().optional()
|
|
4041
|
+
})).optional(),
|
|
4042
|
+
role: z.string().optional(),
|
|
4043
|
+
content: z.string().optional(),
|
|
4044
|
+
timestamp: z.string().optional()
|
|
3790
4045
|
},
|
|
3791
|
-
async ({ project,
|
|
4046
|
+
async ({ project, session_id, user_id, messages, role, content, timestamp }) => {
|
|
3792
4047
|
try {
|
|
3793
|
-
const
|
|
3794
|
-
|
|
3795
|
-
|
|
3796
|
-
|
|
3797
|
-
|
|
3798
|
-
|
|
3799
|
-
|
|
4048
|
+
const normalizedMessages = normalizeRecordMessages({ messages, role, content, timestamp });
|
|
4049
|
+
const result = await whisper.ingestSession({ project, session_id, user_id, messages: normalizedMessages });
|
|
4050
|
+
return primaryToolSuccess({
|
|
4051
|
+
tool: "record",
|
|
4052
|
+
session_id,
|
|
4053
|
+
messages_recorded: normalizedMessages.length,
|
|
4054
|
+
memories_created: result.memories_created,
|
|
4055
|
+
relations_created: result.relations_created
|
|
4056
|
+
});
|
|
3800
4057
|
} catch (error) {
|
|
3801
|
-
return
|
|
4058
|
+
return primaryToolError(error.message);
|
|
4059
|
+
}
|
|
4060
|
+
}
|
|
4061
|
+
);
|
|
4062
|
+
server.tool(
|
|
4063
|
+
"learn",
|
|
4064
|
+
"Learn content or connect a source so it becomes retrievable later. Use this for docs, URLs, repos, files, or local paths.",
|
|
4065
|
+
{
|
|
4066
|
+
project: z.string().optional(),
|
|
4067
|
+
content: z.string().optional().describe("Inline text content to ingest"),
|
|
4068
|
+
title: z.string().optional().describe("Title for inline content"),
|
|
4069
|
+
url: z.string().optional().describe("URL to learn from"),
|
|
4070
|
+
owner: z.string().optional().describe("GitHub owner"),
|
|
4071
|
+
repo: z.string().optional().describe("GitHub repository"),
|
|
4072
|
+
branch: z.string().optional(),
|
|
4073
|
+
path: z.string().optional().describe("Local path to learn from"),
|
|
4074
|
+
file_path: z.string().optional().describe("Single file path to learn from"),
|
|
4075
|
+
name: z.string().optional().describe("Optional source name"),
|
|
4076
|
+
metadata: z.record(z.string()).optional(),
|
|
4077
|
+
max_files: z.number().optional(),
|
|
4078
|
+
glob: z.string().optional(),
|
|
4079
|
+
crawl_depth: z.number().optional()
|
|
4080
|
+
},
|
|
4081
|
+
async (input) => {
|
|
4082
|
+
try {
|
|
4083
|
+
const result = await learnFromInput(input);
|
|
4084
|
+
return primaryToolSuccess({
|
|
4085
|
+
tool: "learn",
|
|
4086
|
+
...result
|
|
4087
|
+
});
|
|
4088
|
+
} catch (error) {
|
|
4089
|
+
return primaryToolError(error.message);
|
|
3802
4090
|
}
|
|
3803
4091
|
}
|
|
3804
4092
|
);
|
|
@@ -3814,18 +4102,15 @@ server.tool(
|
|
|
3814
4102
|
async ({ project, session_id, title, expiry_days }) => {
|
|
3815
4103
|
try {
|
|
3816
4104
|
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
|
-
};
|
|
4105
|
+
return primaryToolSuccess({
|
|
4106
|
+
tool: "share_context",
|
|
4107
|
+
share_id: result.share_id,
|
|
4108
|
+
share_url: result.share_url,
|
|
4109
|
+
expires_at: result.expires_at || null,
|
|
4110
|
+
title: result.title || title || null
|
|
4111
|
+
});
|
|
3827
4112
|
} catch (error) {
|
|
3828
|
-
return
|
|
4113
|
+
return primaryToolError(error.message);
|
|
3829
4114
|
}
|
|
3830
4115
|
}
|
|
3831
4116
|
);
|
package/package.json
CHANGED