@gmickel/gno 1.17.0 → 1.19.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 +18 -4
- package/assets/skill/SKILL.md +44 -1
- package/assets/skill/mcp-reference.md +21 -0
- package/package.json +3 -2
- package/spec/AGENTS.md +83 -0
- package/spec/CLAUDE.md +83 -0
- package/spec/bench-fixture.schema.json +137 -0
- package/spec/cli.md +2894 -0
- package/spec/db/schema.sql +442 -0
- package/spec/evals-agentic.md +510 -0
- package/spec/evals.md +1106 -0
- package/spec/mcp.md +2229 -0
- package/spec/output-schemas/activation-verification.schema.json +515 -0
- package/spec/output-schemas/ask.schema.json +366 -0
- package/spec/output-schemas/backlinks.schema.json +131 -0
- package/spec/output-schemas/bench-result.schema.json +120 -0
- package/spec/output-schemas/capture-receipt.schema.json +143 -0
- package/spec/output-schemas/collection-list.schema.json +45 -0
- package/spec/output-schemas/context-capsule-v1.schema.json +691 -0
- package/spec/output-schemas/context-capsule-verification.schema.json +1338 -0
- package/spec/output-schemas/context-list.schema.json +21 -0
- package/spec/output-schemas/doctor.schema.json +313 -0
- package/spec/output-schemas/error.schema.json +30 -0
- package/spec/output-schemas/expansion.schema.json +37 -0
- package/spec/output-schemas/get.schema.json +140 -0
- package/spec/output-schemas/graph-query.schema.json +99 -0
- package/spec/output-schemas/graph.schema.json +371 -0
- package/spec/output-schemas/links-list.schema.json +186 -0
- package/spec/output-schemas/mcp-add-collection-result.schema.json +23 -0
- package/spec/output-schemas/mcp-capture-result.schema.json +152 -0
- package/spec/output-schemas/mcp-http-error.schema.json +30 -0
- package/spec/output-schemas/mcp-job-list.schema.json +58 -0
- package/spec/output-schemas/mcp-job-status.schema.json +224 -0
- package/spec/output-schemas/mcp-remove-result.schema.json +39 -0
- package/spec/output-schemas/mcp-sync-result.schema.json +41 -0
- package/spec/output-schemas/mcp-tag-result.schema.json +33 -0
- package/spec/output-schemas/models-list.schema.json +93 -0
- package/spec/output-schemas/multi-get.schema.json +103 -0
- package/spec/output-schemas/process-status.schema.json +119 -0
- package/spec/output-schemas/query-diagnose.schema.json +123 -0
- package/spec/output-schemas/resident-status.schema.json +154 -0
- package/spec/output-schemas/retrieval-trace-common.schema.json +492 -0
- package/spec/output-schemas/retrieval-trace-delete.schema.json +16 -0
- package/spec/output-schemas/retrieval-trace-export.schema.json +61 -0
- package/spec/output-schemas/retrieval-trace-filters.schema.json +139 -0
- package/spec/output-schemas/retrieval-trace-judgment.schema.json +15 -0
- package/spec/output-schemas/retrieval-trace-list.schema.json +18 -0
- package/spec/output-schemas/retrieval-trace-payloads.schema.json +178 -0
- package/spec/output-schemas/retrieval-trace-purge.schema.json +31 -0
- package/spec/output-schemas/retrieval-trace-qrels.schema.json +303 -0
- package/spec/output-schemas/retrieval-trace-replay.schema.json +286 -0
- package/spec/output-schemas/retrieval-trace-show.schema.json +69 -0
- package/spec/output-schemas/retrieval-trace-summary.schema.json +65 -0
- package/spec/output-schemas/search-result.schema.json +154 -0
- package/spec/output-schemas/search-results.schema.json +338 -0
- package/spec/output-schemas/similar.schema.json +84 -0
- package/spec/output-schemas/status.schema.json +676 -0
- package/spec/output-schemas/tags-list.schema.json +48 -0
- package/src/app/context-runtime-types.ts +3 -0
- package/src/app/context-runtime.ts +15 -1
- package/src/cli/commands/ask.ts +106 -36
- package/src/cli/commands/context-build.ts +56 -9
- package/src/cli/commands/daemon.ts +69 -2
- package/src/cli/commands/get.ts +64 -3
- package/src/cli/commands/models/pull.ts +13 -3
- package/src/cli/commands/query.ts +62 -23
- package/src/cli/commands/replay.ts +140 -0
- package/src/cli/commands/search.ts +48 -3
- package/src/cli/commands/shared.ts +3 -1
- package/src/cli/commands/status.ts +2 -0
- package/src/cli/commands/trace.ts +200 -0
- package/src/cli/commands/vsearch.ts +75 -53
- package/src/cli/detach.ts +37 -20
- package/src/cli/program.ts +329 -27
- package/src/config/index.ts +12 -0
- package/src/config/retrieval-traces.ts +56 -0
- package/src/config/types.ts +41 -0
- package/src/core/context-compiler.ts +11 -4
- package/src/core/job-manager.ts +19 -0
- package/src/core/mutation-generations.ts +33 -0
- package/src/core/retrieval-qrels.ts +405 -0
- package/src/core/retrieval-replay-candidate.ts +368 -0
- package/src/core/retrieval-replay-types.ts +109 -0
- package/src/core/retrieval-replay-validation.ts +89 -0
- package/src/core/retrieval-replay.ts +441 -0
- package/src/core/retrieval-trace-evidence-origin.ts +175 -0
- package/src/core/retrieval-trace-export.ts +113 -0
- package/src/core/retrieval-trace-filter-normalization.ts +27 -0
- package/src/core/retrieval-trace-filters.ts +19 -0
- package/src/core/retrieval-trace-management-helpers.ts +247 -0
- package/src/core/retrieval-trace-management-types.ts +132 -0
- package/src/core/retrieval-trace-management.ts +422 -0
- package/src/core/retrieval-trace-request.ts +141 -0
- package/src/core/retrieval-trace-session.ts +494 -0
- package/src/core/retrieval-trace.ts +472 -0
- package/src/llm/cache.ts +13 -3
- package/src/llm/nodeLlamaCpp/adapter.ts +10 -1
- package/src/llm/nodeLlamaCpp/lifecycle.ts +71 -0
- package/src/mcp/context.ts +161 -0
- package/src/mcp/http-security.ts +477 -0
- package/src/mcp/http-session.ts +272 -0
- package/src/mcp/http-transport.ts +370 -0
- package/src/mcp/resources/index.ts +141 -134
- package/src/mcp/server.ts +19 -79
- package/src/mcp/tools/add-collection.ts +3 -1
- package/src/mcp/tools/capture.ts +3 -0
- package/src/mcp/tools/clear-collection-embeddings.ts +2 -0
- package/src/mcp/tools/context.ts +68 -16
- package/src/mcp/tools/embed.ts +62 -52
- package/src/mcp/tools/get.ts +35 -1
- package/src/mcp/tools/index-cmd.ts +88 -74
- package/src/mcp/tools/index.ts +96 -2
- package/src/mcp/tools/query.ts +95 -64
- package/src/mcp/tools/remove-collection.ts +2 -0
- package/src/mcp/tools/search.ts +36 -13
- package/src/mcp/tools/status.ts +11 -0
- package/src/mcp/tools/sync.ts +16 -14
- package/src/mcp/tools/trace.ts +143 -0
- package/src/mcp/tools/vsearch.ts +71 -38
- package/src/mcp/tools/workspace-write.ts +7 -3
- package/src/pipeline/answer.ts +167 -26
- package/src/pipeline/graph-retrieval.ts +15 -1
- package/src/pipeline/hybrid.ts +151 -43
- package/src/pipeline/search.ts +36 -3
- package/src/pipeline/trace-metadata.ts +47 -0
- package/src/pipeline/types.ts +43 -0
- package/src/pipeline/vsearch.ts +101 -38
- package/src/sdk/client.ts +380 -71
- package/src/sdk/documents.ts +48 -1
- package/src/sdk/index.ts +17 -0
- package/src/sdk/types.ts +28 -0
- package/src/serve/background-runtime.ts +12 -212
- package/src/serve/context-capsule.ts +67 -8
- package/src/serve/embed-scheduler.ts +74 -43
- package/src/serve/index.ts +9 -0
- package/src/serve/jobs.ts +78 -80
- package/src/serve/public/app.tsx +12 -1
- package/src/serve/public/components/HealthCenter.tsx +74 -1
- package/src/serve/public/globals.built.css +1 -1
- package/src/serve/public/lib/workspace-tabs.ts +2 -0
- package/src/serve/public/pages/Dashboard.tsx +11 -0
- package/src/serve/public/pages/TraceHistory.tsx +478 -0
- package/src/serve/public/pages/trace-history-detail.tsx +224 -0
- package/src/serve/resident-admission.ts +159 -0
- package/src/serve/resident-background-work.ts +39 -0
- package/src/serve/resident-request.ts +55 -0
- package/src/serve/resident-runtime.ts +490 -0
- package/src/serve/resident-status.ts +96 -0
- package/src/serve/retrieval-trace.ts +28 -0
- package/src/serve/routes/api.ts +629 -239
- package/src/serve/routes/mcp.ts +69 -0
- package/src/serve/routes/traces.ts +156 -0
- package/src/serve/server.ts +276 -37
- package/src/serve/status-model.ts +51 -0
- package/src/serve/status.ts +5 -0
- package/src/store/index.ts +31 -0
- package/src/store/migrations/014-retrieval-traces.ts +303 -0
- package/src/store/migrations/index.ts +2 -0
- package/src/store/retrieval-trace-codec.ts +384 -0
- package/src/store/sqlite/adapter.ts +179 -10
- package/src/store/sqlite/retrieval-trace-management-store.ts +341 -0
- package/src/store/sqlite/retrieval-trace-retention.ts +349 -0
- package/src/store/sqlite/retrieval-trace-rows.ts +267 -0
- package/src/store/sqlite/retrieval-trace-store.ts +515 -0
- package/src/store/types.ts +297 -0
- package/src/store/vector/sqlite-vec.ts +76 -1
- package/src/store/vector/types.ts +1 -1
|
@@ -92,6 +92,9 @@ function formatResourceContent(
|
|
|
92
92
|
* Register gno:// resources with the MCP server.
|
|
93
93
|
*/
|
|
94
94
|
export function registerResources(server: McpServer, ctx: ToolContext): void {
|
|
95
|
+
const withSnapshot = <T>(operation: () => Promise<T>): Promise<T> =>
|
|
96
|
+
ctx.runWithSnapshot?.(operation) ?? operation();
|
|
97
|
+
|
|
95
98
|
// Resource template for gno://{collection}/{path} URIs
|
|
96
99
|
const template = new ResourceTemplate(`${URI_PREFIX}{collection}/{+path}`, {
|
|
97
100
|
list: async () => {
|
|
@@ -113,101 +116,103 @@ export function registerResources(server: McpServer, ctx: ToolContext): void {
|
|
|
113
116
|
});
|
|
114
117
|
|
|
115
118
|
// Register the template-based resource handler
|
|
116
|
-
server.resource("gno-document", template, {},
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
// Serialize resource reads same as tools (prevent concurrent DB access + shutdown race)
|
|
123
|
-
const release = await ctx.toolMutex.acquire();
|
|
124
|
-
try {
|
|
125
|
-
// Use parseUri for proper URL decoding (handles %20, etc.)
|
|
126
|
-
const parsed = parseUri(uri.href);
|
|
127
|
-
if (!parsed) {
|
|
128
|
-
throw new Error(`Invalid gno:// URI: ${uri.href}`);
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
const resolution = resolveEffectiveIndex([uri.href], ctx.indexName);
|
|
132
|
-
if (!resolution.ok) {
|
|
133
|
-
throw new Error(resolution.error);
|
|
119
|
+
server.resource("gno-document", template, {}, (uri, _variables) =>
|
|
120
|
+
withSnapshot(async () => {
|
|
121
|
+
// Check shutdown before acquiring mutex
|
|
122
|
+
if (ctx.isShuttingDown()) {
|
|
123
|
+
throw new Error("Server is shutting down");
|
|
134
124
|
}
|
|
135
|
-
const scoped = await openScopedIndexStore({
|
|
136
|
-
activeStore: ctx.store,
|
|
137
|
-
activeIndexName: ctx.indexName,
|
|
138
|
-
requestedIndexName: resolution.value.indexName,
|
|
139
|
-
config: ctx.config,
|
|
140
|
-
configPath: ctx.actualConfigPath,
|
|
141
|
-
});
|
|
142
125
|
|
|
126
|
+
// Serialize resource reads same as tools (prevent concurrent DB access + shutdown race)
|
|
127
|
+
const release = await ctx.toolMutex.acquire();
|
|
143
128
|
try {
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
(c) => c.name === collection
|
|
149
|
-
);
|
|
150
|
-
if (!collectionExists) {
|
|
151
|
-
throw new Error(`Collection not found: ${collection}`);
|
|
129
|
+
// Use parseUri for proper URL decoding (handles %20, etc.)
|
|
130
|
+
const parsed = parseUri(uri.href);
|
|
131
|
+
if (!parsed) {
|
|
132
|
+
throw new Error(`Invalid gno:// URI: ${uri.href}`);
|
|
152
133
|
}
|
|
153
134
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
throw new Error(
|
|
158
|
-
`Failed to lookup document: ${docResult.error.message}`
|
|
159
|
-
);
|
|
135
|
+
const resolution = resolveEffectiveIndex([uri.href], ctx.indexName);
|
|
136
|
+
if (!resolution.ok) {
|
|
137
|
+
throw new Error(resolution.error);
|
|
160
138
|
}
|
|
139
|
+
const scoped = await openScopedIndexStore({
|
|
140
|
+
activeStore: ctx.store,
|
|
141
|
+
activeIndexName: ctx.indexName,
|
|
142
|
+
requestedIndexName: resolution.value.indexName,
|
|
143
|
+
config: ctx.config,
|
|
144
|
+
configPath: ctx.actualConfigPath,
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
try {
|
|
148
|
+
const { collection, path } = parsed;
|
|
149
|
+
|
|
150
|
+
// Validate collection exists
|
|
151
|
+
const collectionExists = ctx.collections.some(
|
|
152
|
+
(c) => c.name === collection
|
|
153
|
+
);
|
|
154
|
+
if (!collectionExists) {
|
|
155
|
+
throw new Error(`Collection not found: ${collection}`);
|
|
156
|
+
}
|
|
161
157
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
158
|
+
// Look up document (path is properly decoded by parseUri)
|
|
159
|
+
const docResult = await scoped.store.getDocument(collection, path);
|
|
160
|
+
if (!docResult.ok) {
|
|
161
|
+
throw new Error(
|
|
162
|
+
`Failed to lookup document: ${docResult.error.message}`
|
|
163
|
+
);
|
|
164
|
+
}
|
|
166
165
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
166
|
+
const doc = docResult.value;
|
|
167
|
+
if (!doc) {
|
|
168
|
+
throw new Error(`Document not found: ${uri.href}`);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Get content
|
|
172
|
+
if (!doc.mirrorHash) {
|
|
173
|
+
throw new Error(`Document has no indexed content: ${uri.href}`);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const contentResult = await scoped.store.getContent(doc.mirrorHash);
|
|
177
|
+
if (!contentResult.ok) {
|
|
178
|
+
throw new Error(
|
|
179
|
+
`Failed to read content: ${contentResult.error.message}`
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const content = contentResult.value ?? "";
|
|
171
184
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
185
|
+
// Format with header and line numbers
|
|
186
|
+
const formattedContent = formatResourceContent(
|
|
187
|
+
doc,
|
|
188
|
+
content,
|
|
189
|
+
ctx,
|
|
190
|
+
scoped.indexName
|
|
191
|
+
);
|
|
192
|
+
|
|
193
|
+
// Build canonical URI
|
|
194
|
+
const canonicalUri = decorateUriForIndex(
|
|
195
|
+
buildUri(collection, path),
|
|
196
|
+
scoped.indexName
|
|
176
197
|
);
|
|
177
|
-
}
|
|
178
198
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
buildUri(collection, path),
|
|
192
|
-
scoped.indexName
|
|
193
|
-
);
|
|
194
|
-
|
|
195
|
-
return {
|
|
196
|
-
contents: [
|
|
197
|
-
{
|
|
198
|
-
uri: canonicalUri,
|
|
199
|
-
mimeType: "text/markdown",
|
|
200
|
-
text: formattedContent,
|
|
201
|
-
},
|
|
202
|
-
],
|
|
203
|
-
};
|
|
199
|
+
return {
|
|
200
|
+
contents: [
|
|
201
|
+
{
|
|
202
|
+
uri: canonicalUri,
|
|
203
|
+
mimeType: "text/markdown",
|
|
204
|
+
text: formattedContent,
|
|
205
|
+
},
|
|
206
|
+
],
|
|
207
|
+
};
|
|
208
|
+
} finally {
|
|
209
|
+
await scoped.close();
|
|
210
|
+
}
|
|
204
211
|
} finally {
|
|
205
|
-
|
|
212
|
+
release();
|
|
206
213
|
}
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
}
|
|
210
|
-
});
|
|
214
|
+
})
|
|
215
|
+
);
|
|
211
216
|
|
|
212
217
|
// Register gno://tags resource for listing tags
|
|
213
218
|
// Use ResourceTemplate with RFC6570 query expansion for proper routing
|
|
@@ -231,65 +236,67 @@ export function registerResources(server: McpServer, ctx: ToolContext): void {
|
|
|
231
236
|
"gno-tags",
|
|
232
237
|
tagsTemplate,
|
|
233
238
|
{ mimeType: "application/json" },
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
const release = await ctx.toolMutex.acquire();
|
|
241
|
-
try {
|
|
242
|
-
// Parse query params from URI
|
|
243
|
-
const url = new URL(uri.href);
|
|
244
|
-
const collectionParam = url.searchParams.get("collection") || undefined;
|
|
245
|
-
const prefixParam = url.searchParams.get("prefix") || undefined;
|
|
246
|
-
|
|
247
|
-
// Normalize and validate collection (case-insensitive)
|
|
248
|
-
let collection: string | undefined;
|
|
249
|
-
if (collectionParam) {
|
|
250
|
-
collection = normalizeCollectionName(collectionParam);
|
|
251
|
-
const exists = ctx.collections.some(
|
|
252
|
-
(c) => c.name.toLowerCase() === collection
|
|
253
|
-
);
|
|
254
|
-
if (!exists) {
|
|
255
|
-
throw new Error(
|
|
256
|
-
`${MCP_ERRORS.NOT_FOUND.code}: Collection not found: ${collectionParam}`
|
|
257
|
-
);
|
|
258
|
-
}
|
|
239
|
+
(uri) =>
|
|
240
|
+
withSnapshot(async () => {
|
|
241
|
+
// Check shutdown before acquiring mutex
|
|
242
|
+
if (ctx.isShuttingDown()) {
|
|
243
|
+
throw new Error("Server is shutting down");
|
|
259
244
|
}
|
|
260
245
|
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
const
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
246
|
+
const release = await ctx.toolMutex.acquire();
|
|
247
|
+
try {
|
|
248
|
+
// Parse query params from URI
|
|
249
|
+
const url = new URL(uri.href);
|
|
250
|
+
const collectionParam =
|
|
251
|
+
url.searchParams.get("collection") || undefined;
|
|
252
|
+
const prefixParam = url.searchParams.get("prefix") || undefined;
|
|
253
|
+
|
|
254
|
+
// Normalize and validate collection (case-insensitive)
|
|
255
|
+
let collection: string | undefined;
|
|
256
|
+
if (collectionParam) {
|
|
257
|
+
collection = normalizeCollectionName(collectionParam);
|
|
258
|
+
const exists = ctx.collections.some(
|
|
259
|
+
(c) => c.name.toLowerCase() === collection
|
|
260
|
+
);
|
|
261
|
+
if (!exists) {
|
|
268
262
|
throw new Error(
|
|
269
|
-
`${MCP_ERRORS.
|
|
263
|
+
`${MCP_ERRORS.NOT_FOUND.code}: Collection not found: ${collectionParam}`
|
|
270
264
|
);
|
|
271
265
|
}
|
|
272
266
|
}
|
|
273
|
-
}
|
|
274
267
|
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
268
|
+
// Normalize and validate prefix
|
|
269
|
+
let prefix: string | undefined;
|
|
270
|
+
if (prefixParam) {
|
|
271
|
+
const trimmed = prefixParam.trim().replace(/\/+$/, "");
|
|
272
|
+
if (trimmed.length > 0) {
|
|
273
|
+
prefix = normalizeTag(trimmed);
|
|
274
|
+
if (!validateTag(prefix)) {
|
|
275
|
+
throw new Error(
|
|
276
|
+
`${MCP_ERRORS.INVALID_INPUT.code}: Invalid tag prefix "${prefixParam}"`
|
|
277
|
+
);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
}
|
|
280
281
|
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
282
|
+
// Get tag counts
|
|
283
|
+
const result = await ctx.store.getTagCounts({ collection, prefix });
|
|
284
|
+
if (!result.ok) {
|
|
285
|
+
throw new Error(`Failed to get tags: ${result.error.message}`);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
return {
|
|
289
|
+
contents: [
|
|
290
|
+
{
|
|
291
|
+
uri: uri.href,
|
|
292
|
+
mimeType: "application/json",
|
|
293
|
+
text: formatTagsContent(result.value, collection, prefix),
|
|
294
|
+
},
|
|
295
|
+
],
|
|
296
|
+
};
|
|
297
|
+
} finally {
|
|
298
|
+
release();
|
|
299
|
+
}
|
|
300
|
+
})
|
|
294
301
|
);
|
|
295
302
|
}
|
package/src/mcp/server.ts
CHANGED
|
@@ -5,14 +5,10 @@
|
|
|
5
5
|
* @module src/mcp/server
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
9
8
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
10
9
|
// node:path for join/dirname (no Bun path utils)
|
|
11
10
|
import { dirname, join } from "node:path";
|
|
12
11
|
|
|
13
|
-
import type { Collection, Config } from "../config/types";
|
|
14
|
-
import type { SqliteAdapter } from "../store/sqlite/adapter";
|
|
15
|
-
|
|
16
12
|
import {
|
|
17
13
|
DEFAULT_INDEX_NAME,
|
|
18
14
|
MCP_SERVER_NAME,
|
|
@@ -23,55 +19,14 @@ import { canonicalizeIndexName } from "../app/index-name";
|
|
|
23
19
|
import { JobManager } from "../core/job-manager";
|
|
24
20
|
import { envIsSet } from "../llm/policy";
|
|
25
21
|
import { MCP_ACTIVATION_VERIFICATION_ENV } from "./activation-verification-mode";
|
|
22
|
+
import {
|
|
23
|
+
createMcpServerSurface,
|
|
24
|
+
createToolContext,
|
|
25
|
+
Mutex,
|
|
26
|
+
type ToolContext,
|
|
27
|
+
} from "./context";
|
|
26
28
|
|
|
27
|
-
|
|
28
|
-
// Simple Promise Mutex (avoids async-mutex dependency)
|
|
29
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
30
|
-
|
|
31
|
-
class Mutex {
|
|
32
|
-
#locked = false;
|
|
33
|
-
readonly #queue: Array<() => void> = [];
|
|
34
|
-
|
|
35
|
-
acquire(): Promise<() => void> {
|
|
36
|
-
return new Promise((resolve) => {
|
|
37
|
-
const tryAcquire = () => {
|
|
38
|
-
if (this.#locked) {
|
|
39
|
-
this.#queue.push(tryAcquire);
|
|
40
|
-
} else {
|
|
41
|
-
this.#locked = true;
|
|
42
|
-
resolve(() => this.#release());
|
|
43
|
-
}
|
|
44
|
-
};
|
|
45
|
-
tryAcquire();
|
|
46
|
-
});
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
#release(): void {
|
|
50
|
-
this.#locked = false;
|
|
51
|
-
const next = this.#queue.shift();
|
|
52
|
-
if (next) {
|
|
53
|
-
next();
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
59
|
-
// Tool Context
|
|
60
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
61
|
-
|
|
62
|
-
export interface ToolContext {
|
|
63
|
-
store: SqliteAdapter;
|
|
64
|
-
config: Config;
|
|
65
|
-
collections: Collection[];
|
|
66
|
-
actualConfigPath: string;
|
|
67
|
-
indexName: string;
|
|
68
|
-
toolMutex: Mutex;
|
|
69
|
-
jobManager: JobManager;
|
|
70
|
-
serverInstanceId: string;
|
|
71
|
-
writeLockPath: string;
|
|
72
|
-
enableWrite: boolean;
|
|
73
|
-
isShuttingDown: () => boolean;
|
|
74
|
-
}
|
|
29
|
+
export type { ToolContext } from "./context";
|
|
75
30
|
|
|
76
31
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
77
32
|
// Server Options
|
|
@@ -136,21 +91,7 @@ export async function startMcpServer(options: McpServerOptions): Promise<void> {
|
|
|
136
91
|
process.exit(1);
|
|
137
92
|
}
|
|
138
93
|
|
|
139
|
-
const { store, config,
|
|
140
|
-
|
|
141
|
-
// Create MCP server
|
|
142
|
-
const server = new McpServer(
|
|
143
|
-
{
|
|
144
|
-
name: MCP_SERVER_NAME,
|
|
145
|
-
version: VERSION,
|
|
146
|
-
},
|
|
147
|
-
{
|
|
148
|
-
capabilities: {
|
|
149
|
-
tools: { listChanged: false },
|
|
150
|
-
resources: { subscribe: false, listChanged: false },
|
|
151
|
-
},
|
|
152
|
-
}
|
|
153
|
-
);
|
|
94
|
+
const { store, config, actualConfigPath } = init;
|
|
154
95
|
|
|
155
96
|
// Sequential execution mutex
|
|
156
97
|
const toolMutex = new Mutex();
|
|
@@ -173,10 +114,13 @@ export async function startMcpServer(options: McpServerOptions): Promise<void> {
|
|
|
173
114
|
let shuttingDown = false;
|
|
174
115
|
|
|
175
116
|
// Tool context (passed to all handlers)
|
|
176
|
-
|
|
117
|
+
let currentConfig = config;
|
|
118
|
+
const ctx: ToolContext = createToolContext({
|
|
177
119
|
store,
|
|
178
|
-
|
|
179
|
-
|
|
120
|
+
getConfig: () => currentConfig,
|
|
121
|
+
setConfig: (nextConfig) => {
|
|
122
|
+
currentConfig = nextConfig;
|
|
123
|
+
},
|
|
180
124
|
actualConfigPath,
|
|
181
125
|
indexName: canonicalizeIndexName(options.indexName ?? DEFAULT_INDEX_NAME),
|
|
182
126
|
toolMutex,
|
|
@@ -185,15 +129,11 @@ export async function startMcpServer(options: McpServerOptions): Promise<void> {
|
|
|
185
129
|
writeLockPath,
|
|
186
130
|
enableWrite,
|
|
187
131
|
isShuttingDown: () => shuttingDown,
|
|
188
|
-
};
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
// Register resources (T10.3)
|
|
195
|
-
const { registerResources } = await import("./resources/index.js");
|
|
196
|
-
registerResources(server, ctx);
|
|
132
|
+
});
|
|
133
|
+
const server = createMcpServerSurface(ctx, {
|
|
134
|
+
name: MCP_SERVER_NAME,
|
|
135
|
+
version: VERSION,
|
|
136
|
+
});
|
|
197
137
|
|
|
198
138
|
if (options.verbose) {
|
|
199
139
|
console.error(
|
|
@@ -14,6 +14,7 @@ import { applyConfigChange } from "../../core/config-mutation";
|
|
|
14
14
|
import { MCP_ERRORS } from "../../core/errors";
|
|
15
15
|
import { acquireWriteLock, type WriteLockHandle } from "../../core/file-lock";
|
|
16
16
|
import { JobError } from "../../core/job-manager";
|
|
17
|
+
import { recordContentMutation } from "../../core/mutation-generations";
|
|
17
18
|
import {
|
|
18
19
|
normalizeCollectionName,
|
|
19
20
|
validateCollectionRoot,
|
|
@@ -152,9 +153,10 @@ export function handleAddCollection(
|
|
|
152
153
|
gitPull: args.gitPull ?? false,
|
|
153
154
|
runUpdateCmd: false,
|
|
154
155
|
},
|
|
155
|
-
|
|
156
|
+
mutationResult.config
|
|
156
157
|
)
|
|
157
158
|
);
|
|
159
|
+
recordContentMutation(result, ctx.markContentMutation);
|
|
158
160
|
|
|
159
161
|
return {
|
|
160
162
|
collections: [result],
|
package/src/mcp/tools/capture.ts
CHANGED
|
@@ -210,6 +210,9 @@ export function handleCapture(
|
|
|
210
210
|
serverInstanceId: ctx.serverInstanceId,
|
|
211
211
|
}) as McpCaptureResult;
|
|
212
212
|
}
|
|
213
|
+
if (syncResult.status === "added" || syncResult.status === "updated") {
|
|
214
|
+
ctx.markContentMutation?.();
|
|
215
|
+
}
|
|
213
216
|
|
|
214
217
|
let docid = syncResult.docid;
|
|
215
218
|
let documentId: number | undefined;
|
|
@@ -8,6 +8,7 @@ import type { ToolContext } from "../server";
|
|
|
8
8
|
|
|
9
9
|
import { MCP_ERRORS } from "../../core/errors";
|
|
10
10
|
import { withWriteLock } from "../../core/file-lock";
|
|
11
|
+
import { recordIndexMutation } from "../../core/mutation-generations";
|
|
11
12
|
import { resolveModelUri } from "../../llm/registry";
|
|
12
13
|
import { runTool, type ToolResult } from "./index";
|
|
13
14
|
|
|
@@ -81,6 +82,7 @@ export function handleClearCollectionEmbeddings(
|
|
|
81
82
|
if (!result.ok) {
|
|
82
83
|
throw new Error(`${result.error.code}: ${result.error.message}`);
|
|
83
84
|
}
|
|
85
|
+
recordIndexMutation(result.value.deletedVectors, ctx.markIndexMutation);
|
|
84
86
|
|
|
85
87
|
return {
|
|
86
88
|
...result.value,
|
package/src/mcp/tools/context.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
/** MCP Context Capsule tools over the shared application runtime. */
|
|
2
2
|
|
|
3
|
+
import type { RetrievalTraceSession } from "../../core/retrieval-trace-session";
|
|
4
|
+
import type { ModelLease } from "../../llm/nodeLlamaCpp/lifecycle";
|
|
3
5
|
import type { EmbeddingPort, RerankPort } from "../../llm/types";
|
|
4
6
|
import type { VectorIndexPort } from "../../store/vector";
|
|
5
7
|
import type { ToolContext } from "../server";
|
|
@@ -19,6 +21,10 @@ import {
|
|
|
19
21
|
parseContextVerifySurfaceInput,
|
|
20
22
|
} from "../../app/context-surface";
|
|
21
23
|
import { createNonTtyProgressRenderer } from "../../cli/progress";
|
|
24
|
+
import {
|
|
25
|
+
finishRetrievalTraceAfterError,
|
|
26
|
+
startRetrievalTraceRequest,
|
|
27
|
+
} from "../../core/retrieval-trace-request";
|
|
22
28
|
import { LlmAdapter } from "../../llm/nodeLlamaCpp/adapter";
|
|
23
29
|
import { resolveDownloadPolicy } from "../../llm/policy";
|
|
24
30
|
import { resolveModelUri } from "../../llm/registry";
|
|
@@ -27,11 +33,15 @@ import { createVectorIndexPort } from "../../store/vector";
|
|
|
27
33
|
interface ContextToolResultData {
|
|
28
34
|
structuredContent: Record<string, unknown>;
|
|
29
35
|
text: string;
|
|
36
|
+
traceId?: string;
|
|
30
37
|
}
|
|
31
38
|
|
|
32
39
|
const asToolResult = (data: ContextToolResultData): ToolResult => ({
|
|
33
40
|
content: [{ type: "text", text: data.text }],
|
|
34
41
|
structuredContent: data.structuredContent,
|
|
42
|
+
...(data.traceId
|
|
43
|
+
? { _meta: { gno: { retrievalTrace: { traceId: data.traceId } } } }
|
|
44
|
+
: {}),
|
|
35
45
|
});
|
|
36
46
|
|
|
37
47
|
const asToolError = (error: unknown): ToolResult => {
|
|
@@ -64,7 +74,8 @@ const runContextTool = async (
|
|
|
64
74
|
}
|
|
65
75
|
const release = await context.toolMutex.acquire();
|
|
66
76
|
try {
|
|
67
|
-
|
|
77
|
+
const data = await (context.runWithSnapshot?.(operation) ?? operation());
|
|
78
|
+
return asToolResult(data);
|
|
68
79
|
} catch (error) {
|
|
69
80
|
return asToolError(error);
|
|
70
81
|
} finally {
|
|
@@ -82,19 +93,17 @@ interface McpModelPorts {
|
|
|
82
93
|
interface McpModelPortFactory {
|
|
83
94
|
createEmbeddingPort: LlmAdapter["createEmbeddingPort"];
|
|
84
95
|
createRerankPort: LlmAdapter["createRerankPort"];
|
|
85
|
-
|
|
96
|
+
acquireModelLease?: LlmAdapter["acquireModelLease"];
|
|
86
97
|
}
|
|
87
98
|
|
|
88
99
|
export const disposeContextModelOwners = async (
|
|
89
100
|
portOwners: readonly { dispose(): Promise<void> }[],
|
|
90
|
-
|
|
101
|
+
lease?: ModelLease
|
|
91
102
|
): Promise<void> => {
|
|
92
103
|
await Promise.allSettled(
|
|
93
104
|
portOwners.map((owner) => Promise.resolve().then(() => owner.dispose()))
|
|
94
105
|
);
|
|
95
|
-
|
|
96
|
-
Promise.resolve().then(() => managerOwner.dispose()),
|
|
97
|
-
]);
|
|
106
|
+
lease?.release();
|
|
98
107
|
};
|
|
99
108
|
|
|
100
109
|
export const createMcpModelPorts = async (
|
|
@@ -104,6 +113,7 @@ export const createMcpModelPorts = async (
|
|
|
104
113
|
): Promise<McpModelPorts> => {
|
|
105
114
|
const llm = new LlmAdapter(context.config);
|
|
106
115
|
const factory = factoryOverride ?? llm;
|
|
116
|
+
const lease = factory.acquireModelLease?.();
|
|
107
117
|
const policy = resolveDownloadPolicy(process.env, {});
|
|
108
118
|
const progress = createNonTtyProgressRenderer();
|
|
109
119
|
const embedUri = resolveModelUri(
|
|
@@ -151,7 +161,7 @@ export const createMcpModelPorts = async (
|
|
|
151
161
|
[ownedEmbedPort, rerankPort].filter(
|
|
152
162
|
(port): port is EmbeddingPort | RerankPort => port !== null
|
|
153
163
|
),
|
|
154
|
-
|
|
164
|
+
lease
|
|
155
165
|
);
|
|
156
166
|
},
|
|
157
167
|
};
|
|
@@ -160,7 +170,7 @@ export const createMcpModelPorts = async (
|
|
|
160
170
|
[ownedEmbedPort, rerankPort].filter(
|
|
161
171
|
(port): port is EmbeddingPort | RerankPort => port !== null
|
|
162
172
|
),
|
|
163
|
-
|
|
173
|
+
lease
|
|
164
174
|
);
|
|
165
175
|
throw error;
|
|
166
176
|
}
|
|
@@ -179,15 +189,50 @@ export const handleContext = (
|
|
|
179
189
|
context.config.collections.map((collection) => collection.name)
|
|
180
190
|
);
|
|
181
191
|
const useModels = parsed.input.depthPolicy !== "fast";
|
|
182
|
-
const
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
192
|
+
const collection =
|
|
193
|
+
parsed.input.collections?.length === 1
|
|
194
|
+
? parsed.input.collections[0]
|
|
195
|
+
: undefined;
|
|
196
|
+
const modelUris = useModels
|
|
197
|
+
? [
|
|
198
|
+
resolveModelUri(context.config, "embed", undefined, collection),
|
|
199
|
+
resolveModelUri(context.config, "rerank", undefined, collection),
|
|
200
|
+
]
|
|
201
|
+
: [];
|
|
202
|
+
let modelPorts: Awaited<ReturnType<typeof createMcpModelPorts>> | null =
|
|
203
|
+
null;
|
|
204
|
+
let traceSession: RetrievalTraceSession | undefined;
|
|
190
205
|
try {
|
|
206
|
+
const traceStart = await startRetrievalTraceRequest({
|
|
207
|
+
store: context.store,
|
|
208
|
+
config: context.config,
|
|
209
|
+
query: parsed.input.query ?? parsed.input.goal,
|
|
210
|
+
goal: parsed.input.goal,
|
|
211
|
+
filters: {
|
|
212
|
+
limit: parsed.input.limit,
|
|
213
|
+
collection,
|
|
214
|
+
collections: [...(parsed.input.collections ?? [])].sort(),
|
|
215
|
+
lang: parsed.input.lang,
|
|
216
|
+
tagsAll: parsed.input.tagsAll,
|
|
217
|
+
tagsAny: parsed.input.tagsAny,
|
|
218
|
+
since: parsed.input.since,
|
|
219
|
+
until: parsed.input.until,
|
|
220
|
+
categories: parsed.input.categories,
|
|
221
|
+
author: parsed.input.author,
|
|
222
|
+
graph: parsed.input.graph,
|
|
223
|
+
candidateLimit: parsed.input.candidateLimit,
|
|
224
|
+
queryModes: parsed.input.queryModes,
|
|
225
|
+
uriPrefix: parsed.input.uriPrefix ?? undefined,
|
|
226
|
+
},
|
|
227
|
+
pipeline: "context",
|
|
228
|
+
indexName: context.indexName,
|
|
229
|
+
modelUris,
|
|
230
|
+
});
|
|
231
|
+
if (!traceStart.ok) throw new Error(traceStart.error.message);
|
|
232
|
+
traceSession = traceStart.value ?? undefined;
|
|
233
|
+
modelPorts = useModels
|
|
234
|
+
? await createMcpModelPorts(context, collection)
|
|
235
|
+
: null;
|
|
191
236
|
const capsule = await buildContextCapsule(parsed.input, {
|
|
192
237
|
store: context.store,
|
|
193
238
|
config: context.config,
|
|
@@ -195,14 +240,21 @@ export const handleContext = (
|
|
|
195
240
|
vectorIndex: modelPorts?.vectorIndex ?? null,
|
|
196
241
|
embedPort: modelPorts?.embedPort ?? null,
|
|
197
242
|
rerankPort: modelPorts?.rerankPort ?? null,
|
|
243
|
+
traceSession,
|
|
198
244
|
});
|
|
245
|
+
const finalized = await traceSession?.finish("completed");
|
|
246
|
+
if (finalized && !finalized.ok) throw new Error(finalized.error.message);
|
|
199
247
|
return {
|
|
200
248
|
structuredContent: capsule as unknown as Record<string, unknown>,
|
|
201
249
|
// MCP model context receives this projection exactly once. The full
|
|
202
250
|
// canonical capsule remains available to application clients through
|
|
203
251
|
// structuredContent and is deliberately not duplicated in text.
|
|
204
252
|
text: formatContextCapsuleAgentJson(capsule),
|
|
253
|
+
traceId: traceSession?.metadata()?.traceId,
|
|
205
254
|
};
|
|
255
|
+
} catch (cause) {
|
|
256
|
+
await finishRetrievalTraceAfterError(traceSession, cause);
|
|
257
|
+
throw cause;
|
|
206
258
|
} finally {
|
|
207
259
|
await modelPorts?.dispose();
|
|
208
260
|
}
|