recallx 1.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 +205 -0
- package/app/cli/bin/recallx-mcp.js +2 -0
- package/app/cli/bin/recallx.js +8 -0
- package/app/cli/src/cli.js +808 -0
- package/app/cli/src/format.js +242 -0
- package/app/cli/src/http.js +35 -0
- package/app/mcp/api-client.js +101 -0
- package/app/mcp/index.js +128 -0
- package/app/mcp/server.js +786 -0
- package/app/server/app.js +2263 -0
- package/app/server/config.js +27 -0
- package/app/server/db.js +399 -0
- package/app/server/errors.js +17 -0
- package/app/server/governance.js +466 -0
- package/app/server/index.js +26 -0
- package/app/server/inferred-relations.js +247 -0
- package/app/server/observability.js +495 -0
- package/app/server/project-graph.js +199 -0
- package/app/server/relation-scoring.js +59 -0
- package/app/server/repositories.js +2992 -0
- package/app/server/retrieval.js +486 -0
- package/app/server/semantic/chunker.js +85 -0
- package/app/server/semantic/provider.js +124 -0
- package/app/server/semantic/types.js +1 -0
- package/app/server/semantic/vector-store.js +169 -0
- package/app/server/utils.js +43 -0
- package/app/server/workspace-session.js +128 -0
- package/app/server/workspace.js +79 -0
- package/app/shared/contracts.js +268 -0
- package/app/shared/request-runtime.js +30 -0
- package/app/shared/types.js +1 -0
- package/app/shared/version.js +1 -0
- package/dist/renderer/assets/ProjectGraphCanvas-BMvz9DmE.js +312 -0
- package/dist/renderer/assets/index-C2-KXqBO.css +1 -0
- package/dist/renderer/assets/index-CrDu22h7.js +76 -0
- package/dist/renderer/index.html +13 -0
- package/package.json +49 -0
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
function pad(label, value) {
|
|
2
|
+
return `${label}: ${value ?? ""}`;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
export function renderJson(value) {
|
|
6
|
+
return `${JSON.stringify(value, null, 2)}\n`;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function renderText(value) {
|
|
10
|
+
if (value == null) {
|
|
11
|
+
return "";
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
if (typeof value === "string") {
|
|
15
|
+
return `${value}\n`;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
if (Array.isArray(value)) {
|
|
19
|
+
return `${value.map((item) => `- ${renderInline(item)}`).join("\n")}\n`;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (typeof value === "object") {
|
|
23
|
+
return `${Object.entries(value)
|
|
24
|
+
.map(([key, entry]) => `${key}: ${renderInline(entry)}`)
|
|
25
|
+
.join("\n")}\n`;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return `${String(value)}\n`;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function renderNode(node) {
|
|
32
|
+
const lines = [
|
|
33
|
+
`${node.title || node.id}`,
|
|
34
|
+
pad("id", node.id),
|
|
35
|
+
pad("type", node.type),
|
|
36
|
+
pad("status", node.status),
|
|
37
|
+
pad("canonicality", node.canonicality),
|
|
38
|
+
pad("summary", node.summary),
|
|
39
|
+
pad("source", node.sourceLabel || node.source_label || ""),
|
|
40
|
+
];
|
|
41
|
+
|
|
42
|
+
if (Array.isArray(node.tags) && node.tags.length > 0) {
|
|
43
|
+
lines.push(pad("tags", node.tags.join(", ")));
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (node.body) {
|
|
47
|
+
lines.push("");
|
|
48
|
+
lines.push(node.body);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return `${lines.join("\n")}\n`;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function renderSearchResults(data) {
|
|
55
|
+
const items = data?.items || [];
|
|
56
|
+
if (!items.length) {
|
|
57
|
+
return "No results.\n";
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return `${items
|
|
61
|
+
.map((item, index) => {
|
|
62
|
+
const summary = item.summary ? `\n ${item.summary}` : "";
|
|
63
|
+
return `${index + 1}. ${item.title || item.id} (${item.type || "node"})\n id: ${item.id}\n status: ${item.status || ""}${summary}`;
|
|
64
|
+
})
|
|
65
|
+
.join("\n\n")}\n`;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function renderActivitySearchResults(data) {
|
|
69
|
+
const items = data?.items || [];
|
|
70
|
+
if (!items.length) {
|
|
71
|
+
return "No activity results.\n";
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return `${items
|
|
75
|
+
.map((item, index) => {
|
|
76
|
+
const headline = item.targetNodeTitle || item.targetNodeId;
|
|
77
|
+
const body = item.body ? `\n ${item.body}` : "";
|
|
78
|
+
return `${index + 1}. ${headline} (${item.activityType})\n id: ${item.id}\n target: ${item.targetNodeId}\n created: ${item.createdAt}${body}`;
|
|
79
|
+
})
|
|
80
|
+
.join("\n\n")}\n`;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function renderWorkspaceSearchResults(data) {
|
|
84
|
+
const items = data?.items || [];
|
|
85
|
+
if (!items.length) {
|
|
86
|
+
return "No workspace results.\n";
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return `${items
|
|
90
|
+
.map((item, index) => {
|
|
91
|
+
if (item.resultType === "activity" && item.activity) {
|
|
92
|
+
const headline = item.activity.targetNodeTitle || item.activity.targetNodeId;
|
|
93
|
+
const body = item.activity.body ? `\n ${item.activity.body}` : "";
|
|
94
|
+
return `${index + 1}. [activity] ${headline} (${item.activity.activityType})\n id: ${item.activity.id}\n target: ${item.activity.targetNodeId}\n created: ${item.activity.createdAt}${body}`;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const node = item.node || {};
|
|
98
|
+
const summary = node.summary ? `\n ${node.summary}` : "";
|
|
99
|
+
return `${index + 1}. [node] ${node.title || node.id} (${node.type || "node"})\n id: ${node.id}\n status: ${node.status || ""}${summary}`;
|
|
100
|
+
})
|
|
101
|
+
.join("\n\n")}\n`;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export function renderRelated(data) {
|
|
105
|
+
const items = data?.items || data?.related || [];
|
|
106
|
+
if (!items.length) {
|
|
107
|
+
return "No related nodes.\n";
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return `${items
|
|
111
|
+
.map((item, index) => {
|
|
112
|
+
const node = item?.node || item;
|
|
113
|
+
const relation = item?.relation || item;
|
|
114
|
+
return `${index + 1}. ${node?.title || node?.nodeId || node?.id} (${relation?.relationType || node?.type || ""})`;
|
|
115
|
+
})
|
|
116
|
+
.join("\n")}\n`;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export function renderGovernanceIssues(data) {
|
|
120
|
+
const items = data?.items || [];
|
|
121
|
+
if (!items.length) {
|
|
122
|
+
return "No governance issues.\n";
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return `${items
|
|
126
|
+
.map(
|
|
127
|
+
(item, index) =>
|
|
128
|
+
`${index + 1}. ${item.title || item.entityId}\n entity: ${item.entityType || ""}:${item.entityId || ""}\n state: ${item.state || ""}\n confidence: ${item.confidence ?? ""}\n reasons: ${Array.isArray(item.reasons) ? item.reasons.join(", ") : ""}`,
|
|
129
|
+
)
|
|
130
|
+
.join("\n\n")}\n`;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export function renderWorkspaces(data) {
|
|
134
|
+
const items = data?.items || [];
|
|
135
|
+
if (!items.length) {
|
|
136
|
+
return "No workspaces.\n";
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return `${items
|
|
140
|
+
.map((item, index) => {
|
|
141
|
+
const marker = item.isCurrent ? "*" : " ";
|
|
142
|
+
return `${index + 1}. ${marker} ${item.workspaceName}\n root: ${item.rootPath}\n auth: ${item.authMode || ""}`;
|
|
143
|
+
})
|
|
144
|
+
.join("\n\n")}\n`;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export function renderBundleMarkdown(bundle) {
|
|
148
|
+
const lines = [];
|
|
149
|
+
lines.push(`# ${bundle.target?.title || bundle.target?.id || "Context bundle"}`);
|
|
150
|
+
lines.push("");
|
|
151
|
+
lines.push(`- mode: ${bundle.mode || ""}`);
|
|
152
|
+
lines.push(`- preset: ${bundle.preset || ""}`);
|
|
153
|
+
if (bundle.summary) {
|
|
154
|
+
lines.push("");
|
|
155
|
+
lines.push(bundle.summary);
|
|
156
|
+
}
|
|
157
|
+
if (Array.isArray(bundle.items) && bundle.items.length > 0) {
|
|
158
|
+
lines.push("");
|
|
159
|
+
lines.push("## Items");
|
|
160
|
+
for (const item of bundle.items) {
|
|
161
|
+
lines.push(`- ${item.title || item.nodeId || item.id}${item.summary ? `: ${item.summary}` : ""}`);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
if (Array.isArray(bundle.sources) && bundle.sources.length > 0) {
|
|
165
|
+
lines.push("");
|
|
166
|
+
lines.push("## Sources");
|
|
167
|
+
for (const source of bundle.sources) {
|
|
168
|
+
lines.push(`- ${source.nodeId || source.id}${source.sourceLabel ? ` (${source.sourceLabel})` : ""}`);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
return `${lines.join("\n")}\n`;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export function renderTelemetrySummary(data) {
|
|
175
|
+
const lines = [
|
|
176
|
+
`since: ${data?.since || ""}`,
|
|
177
|
+
`logs: ${data?.logsPath || ""}`,
|
|
178
|
+
`events: ${data?.totalEvents ?? 0}`,
|
|
179
|
+
];
|
|
180
|
+
|
|
181
|
+
const slow = Array.isArray(data?.slowOperations) ? data.slowOperations : [];
|
|
182
|
+
if (slow.length > 0) {
|
|
183
|
+
lines.push("");
|
|
184
|
+
lines.push("Slow operations:");
|
|
185
|
+
for (const item of slow.slice(0, 5)) {
|
|
186
|
+
lines.push(`- [${item.surface}] ${item.operation} p95=${item.p95DurationMs ?? ""}ms errors=${item.errorCount}/${item.count}`);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const mcpFailures = Array.isArray(data?.mcpToolFailures) ? data.mcpToolFailures : [];
|
|
191
|
+
if (mcpFailures.length > 0) {
|
|
192
|
+
lines.push("");
|
|
193
|
+
lines.push("MCP failures:");
|
|
194
|
+
for (const item of mcpFailures.slice(0, 5)) {
|
|
195
|
+
lines.push(`- ${item.operation}: ${item.count}`);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if (data?.ftsFallbackRate) {
|
|
200
|
+
lines.push("");
|
|
201
|
+
lines.push(
|
|
202
|
+
`fts fallback: ${data.ftsFallbackRate.fallbackCount}/${data.ftsFallbackRate.sampleCount} (${data.ftsFallbackRate.ratio ?? "n/a"})`
|
|
203
|
+
);
|
|
204
|
+
}
|
|
205
|
+
if (data?.semanticAugmentationRate) {
|
|
206
|
+
lines.push(
|
|
207
|
+
`semantic augmentation: ${data.semanticAugmentationRate.usedCount}/${data.semanticAugmentationRate.sampleCount} (${data.semanticAugmentationRate.ratio ?? "n/a"})`
|
|
208
|
+
);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
return `${lines.join("\n")}\n`;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
export function renderTelemetryErrors(data) {
|
|
215
|
+
const items = data?.items || [];
|
|
216
|
+
if (!items.length) {
|
|
217
|
+
return "No telemetry errors.\n";
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
return `${items
|
|
221
|
+
.map(
|
|
222
|
+
(item, index) =>
|
|
223
|
+
`${index + 1}. [${item.surface}] ${item.operation}\n ts: ${item.ts}\n trace: ${item.traceId}\n error: ${item.errorKind || ""}/${item.errorCode || ""}\n status: ${item.statusCode ?? ""}\n durationMs: ${item.durationMs ?? ""}`
|
|
224
|
+
)
|
|
225
|
+
.join("\n\n")}\n`;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function renderInline(value) {
|
|
229
|
+
if (value == null) {
|
|
230
|
+
return "";
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
if (Array.isArray(value)) {
|
|
234
|
+
return value.map(renderInline).join(", ");
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
if (typeof value === "object") {
|
|
238
|
+
return JSON.stringify(value);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
return String(value);
|
|
242
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { buildApiRequestInit, buildApiUrl, parseApiJsonBody } from "../../shared/request-runtime.js";
|
|
2
|
+
const DEFAULT_API_BASE = "http://127.0.0.1:8787/api/v1";
|
|
3
|
+
export function getApiBase(argvOptions = {}, env = process.env) {
|
|
4
|
+
return (argvOptions.api ||
|
|
5
|
+
env.RECALLX_API_URL ||
|
|
6
|
+
DEFAULT_API_BASE);
|
|
7
|
+
}
|
|
8
|
+
export function getAuthToken(argvOptions = {}, env = process.env) {
|
|
9
|
+
return argvOptions.token || env.RECALLX_TOKEN || "";
|
|
10
|
+
}
|
|
11
|
+
export async function requestJson(apiBase, path, { method = "GET", token, body } = {}) {
|
|
12
|
+
const response = await fetch(buildApiUrl(apiBase, path), buildApiRequestInit({ method, token, body }));
|
|
13
|
+
let payload = null;
|
|
14
|
+
let parseError = null;
|
|
15
|
+
try {
|
|
16
|
+
payload = await parseApiJsonBody(response);
|
|
17
|
+
}
|
|
18
|
+
catch (error) {
|
|
19
|
+
parseError = error;
|
|
20
|
+
}
|
|
21
|
+
if (!response.ok) {
|
|
22
|
+
const error = payload?.error;
|
|
23
|
+
const code = error?.code || `HTTP_${response.status}`;
|
|
24
|
+
const message = error?.message || response.statusText || "Request failed";
|
|
25
|
+
throw new Error(`${code}: ${message}`);
|
|
26
|
+
}
|
|
27
|
+
if (parseError) {
|
|
28
|
+
throw new Error("INVALID_RESPONSE: RecallX API returned non-JSON output.");
|
|
29
|
+
}
|
|
30
|
+
if (payload && payload.ok === false) {
|
|
31
|
+
const error = payload.error || {};
|
|
32
|
+
throw new Error(`${error.code || "INTERNAL_ERROR"}: ${error.message || "Request failed"}`);
|
|
33
|
+
}
|
|
34
|
+
return payload ?? {};
|
|
35
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { buildApiRequestInit, buildApiUrl, parseApiJsonBody } from "../shared/request-runtime.js";
|
|
2
|
+
import { currentTelemetryContext } from "../server/observability.js";
|
|
3
|
+
export class RecallXApiError extends Error {
|
|
4
|
+
status;
|
|
5
|
+
code;
|
|
6
|
+
details;
|
|
7
|
+
constructor(message, options) {
|
|
8
|
+
super(message);
|
|
9
|
+
this.name = "RecallXApiError";
|
|
10
|
+
this.status = options?.status ?? 500;
|
|
11
|
+
this.code = options?.code ?? "RECALLX_API_ERROR";
|
|
12
|
+
this.details = options?.details;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
function isApiErrorEnvelope(payload) {
|
|
16
|
+
return Boolean(payload &&
|
|
17
|
+
typeof payload === "object" &&
|
|
18
|
+
"ok" in payload &&
|
|
19
|
+
payload.ok === false &&
|
|
20
|
+
"error" in payload &&
|
|
21
|
+
payload.error &&
|
|
22
|
+
typeof payload.error === "object");
|
|
23
|
+
}
|
|
24
|
+
export class RecallXApiClient {
|
|
25
|
+
baseUrl;
|
|
26
|
+
apiToken;
|
|
27
|
+
constructor(baseUrl, apiToken) {
|
|
28
|
+
this.baseUrl = baseUrl;
|
|
29
|
+
this.apiToken = apiToken ?? null;
|
|
30
|
+
}
|
|
31
|
+
async get(path) {
|
|
32
|
+
return this.request("GET", path);
|
|
33
|
+
}
|
|
34
|
+
async post(path, body) {
|
|
35
|
+
return this.request("POST", path, body);
|
|
36
|
+
}
|
|
37
|
+
async patch(path, body) {
|
|
38
|
+
return this.request("PATCH", path, body);
|
|
39
|
+
}
|
|
40
|
+
async request(method, path, body) {
|
|
41
|
+
const headers = new Headers();
|
|
42
|
+
const telemetryContext = currentTelemetryContext();
|
|
43
|
+
if (telemetryContext?.traceId) {
|
|
44
|
+
headers.set("x-recallx-trace-id", telemetryContext.traceId);
|
|
45
|
+
}
|
|
46
|
+
if (telemetryContext?.toolName) {
|
|
47
|
+
headers.set("x-recallx-mcp-tool", telemetryContext.toolName);
|
|
48
|
+
}
|
|
49
|
+
let response;
|
|
50
|
+
try {
|
|
51
|
+
response = await fetch(buildApiUrl(this.baseUrl, path), buildApiRequestInit({
|
|
52
|
+
method,
|
|
53
|
+
token: this.apiToken,
|
|
54
|
+
body,
|
|
55
|
+
headers
|
|
56
|
+
}));
|
|
57
|
+
}
|
|
58
|
+
catch (error) {
|
|
59
|
+
throw new RecallXApiError("Failed to reach the local RecallX API.", {
|
|
60
|
+
status: 503,
|
|
61
|
+
code: "NETWORK_ERROR",
|
|
62
|
+
details: error
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
let payload;
|
|
66
|
+
try {
|
|
67
|
+
payload = await parseApiJsonBody(response);
|
|
68
|
+
}
|
|
69
|
+
catch (error) {
|
|
70
|
+
throw new RecallXApiError("RecallX API returned non-JSON output.", {
|
|
71
|
+
status: response.status,
|
|
72
|
+
code: "INVALID_RESPONSE",
|
|
73
|
+
details: error
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
if (!payload || typeof payload !== "object") {
|
|
77
|
+
throw new RecallXApiError("RecallX API returned an empty response.", {
|
|
78
|
+
status: response.status,
|
|
79
|
+
code: "EMPTY_RESPONSE"
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
if ("ok" in payload && payload.ok === true) {
|
|
83
|
+
return payload.data;
|
|
84
|
+
}
|
|
85
|
+
if (isApiErrorEnvelope(payload)) {
|
|
86
|
+
throw new RecallXApiError(payload.error.message, {
|
|
87
|
+
status: response.status,
|
|
88
|
+
code: payload.error.code,
|
|
89
|
+
details: payload.error.details
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
if (!response.ok) {
|
|
93
|
+
throw new RecallXApiError(`RecallX API request failed with status ${response.status}.`, {
|
|
94
|
+
status: response.status,
|
|
95
|
+
code: "HTTP_ERROR",
|
|
96
|
+
details: payload
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
return payload;
|
|
100
|
+
}
|
|
101
|
+
}
|
package/app/mcp/index.js
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
3
|
+
import { RecallXApiClient } from "./api-client.js";
|
|
4
|
+
import { createRecallXMcpServer } from "./server.js";
|
|
5
|
+
import { RECALLX_VERSION } from "../shared/version.js";
|
|
6
|
+
function createApiClient() {
|
|
7
|
+
return new RecallXApiClient(process.env.RECALLX_API_URL ?? "http://127.0.0.1:8787/api/v1", process.env.RECALLX_API_TOKEN);
|
|
8
|
+
}
|
|
9
|
+
function createObservabilityStateReader() {
|
|
10
|
+
let cachedState = null;
|
|
11
|
+
let cachedAt = 0;
|
|
12
|
+
let inFlight = null;
|
|
13
|
+
const cacheTtlMs = 5_000;
|
|
14
|
+
return async function readObservabilityState() {
|
|
15
|
+
const now = Date.now();
|
|
16
|
+
if (cachedState && now - cachedAt < cacheTtlMs) {
|
|
17
|
+
return cachedState;
|
|
18
|
+
}
|
|
19
|
+
if (inFlight) {
|
|
20
|
+
return inFlight;
|
|
21
|
+
}
|
|
22
|
+
inFlight = resolveObservabilityState()
|
|
23
|
+
.then((state) => {
|
|
24
|
+
cachedState = state;
|
|
25
|
+
cachedAt = Date.now();
|
|
26
|
+
return state;
|
|
27
|
+
})
|
|
28
|
+
.finally(() => {
|
|
29
|
+
inFlight = null;
|
|
30
|
+
});
|
|
31
|
+
return inFlight;
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
async function resolveObservabilityState() {
|
|
35
|
+
try {
|
|
36
|
+
const client = createApiClient();
|
|
37
|
+
const [workspacePayload, settingsPayload] = await Promise.all([
|
|
38
|
+
client.get("/workspace"),
|
|
39
|
+
client.get("/settings?keys=observability.enabled,observability.retentionDays,observability.slowRequestMs,observability.capturePayloadShape")
|
|
40
|
+
]);
|
|
41
|
+
const workspace = workspacePayload ?? {};
|
|
42
|
+
const values = (settingsPayload.values ?? {});
|
|
43
|
+
return {
|
|
44
|
+
enabled: values["observability.enabled"] === true,
|
|
45
|
+
workspaceRoot: typeof workspace.rootPath === "string" ? workspace.rootPath : process.cwd(),
|
|
46
|
+
workspaceName: typeof workspace.workspaceName === "string" ? workspace.workspaceName : "RecallX MCP",
|
|
47
|
+
retentionDays: typeof values["observability.retentionDays"] === "number" ? values["observability.retentionDays"] : 14,
|
|
48
|
+
slowRequestMs: typeof values["observability.slowRequestMs"] === "number" ? values["observability.slowRequestMs"] : 250,
|
|
49
|
+
capturePayloadShape: values["observability.capturePayloadShape"] !== false
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
return {
|
|
54
|
+
enabled: false,
|
|
55
|
+
workspaceRoot: process.cwd(),
|
|
56
|
+
workspaceName: "RecallX MCP",
|
|
57
|
+
retentionDays: 14,
|
|
58
|
+
slowRequestMs: 250,
|
|
59
|
+
capturePayloadShape: true
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
function printHelp() {
|
|
64
|
+
console.error(`RecallX MCP server
|
|
65
|
+
|
|
66
|
+
Usage:
|
|
67
|
+
npm run mcp
|
|
68
|
+
npm run dev:mcp
|
|
69
|
+
node dist/server/app/mcp/index.js --api http://127.0.0.1:8787/api/v1
|
|
70
|
+
recallx-mcp --api http://127.0.0.1:8787/api/v1
|
|
71
|
+
|
|
72
|
+
Environment:
|
|
73
|
+
RECALLX_API_URL Local RecallX API base URL (default: http://127.0.0.1:8787/api/v1)
|
|
74
|
+
RECALLX_API_TOKEN Optional bearer token for auth-enabled RecallX instances
|
|
75
|
+
RECALLX_MCP_SOURCE_LABEL Default provenance label for writes (default: RecallX MCP)
|
|
76
|
+
RECALLX_MCP_TOOL_NAME Default provenance tool name (default: recallx-mcp)
|
|
77
|
+
`);
|
|
78
|
+
}
|
|
79
|
+
function parseArgs(argv) {
|
|
80
|
+
const options = {};
|
|
81
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
82
|
+
const arg = argv[index];
|
|
83
|
+
if (!arg.startsWith("--")) {
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
const key = arg.slice(2);
|
|
87
|
+
const next = argv[index + 1];
|
|
88
|
+
if (!next || next.startsWith("--")) {
|
|
89
|
+
options[key] = true;
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
options[key] = next;
|
|
93
|
+
index += 1;
|
|
94
|
+
}
|
|
95
|
+
return options;
|
|
96
|
+
}
|
|
97
|
+
async function main() {
|
|
98
|
+
const args = parseArgs(process.argv.slice(2));
|
|
99
|
+
if (args.help) {
|
|
100
|
+
printHelp();
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
if (typeof args.api === "string") {
|
|
104
|
+
process.env.RECALLX_API_URL = args.api;
|
|
105
|
+
}
|
|
106
|
+
if (typeof args.token === "string") {
|
|
107
|
+
process.env.RECALLX_API_TOKEN = args.token;
|
|
108
|
+
}
|
|
109
|
+
if (typeof args["source-label"] === "string") {
|
|
110
|
+
process.env.RECALLX_MCP_SOURCE_LABEL = args["source-label"];
|
|
111
|
+
}
|
|
112
|
+
if (typeof args["tool-name"] === "string") {
|
|
113
|
+
process.env.RECALLX_MCP_TOOL_NAME = args["tool-name"];
|
|
114
|
+
}
|
|
115
|
+
const readObservabilityState = createObservabilityStateReader();
|
|
116
|
+
const server = createRecallXMcpServer({
|
|
117
|
+
serverVersion: RECALLX_VERSION,
|
|
118
|
+
observabilityState: await readObservabilityState(),
|
|
119
|
+
getObservabilityState: readObservabilityState
|
|
120
|
+
});
|
|
121
|
+
const transport = new StdioServerTransport();
|
|
122
|
+
await server.connect(transport);
|
|
123
|
+
console.error(`RecallX MCP connected over stdio -> ${process.env.RECALLX_API_URL ?? "http://127.0.0.1:8787/api/v1"}`);
|
|
124
|
+
}
|
|
125
|
+
main().catch((error) => {
|
|
126
|
+
console.error("RecallX MCP failed to start:", error);
|
|
127
|
+
process.exit(1);
|
|
128
|
+
});
|