@gmickel/gno 1.16.0 → 1.18.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 +34 -18
- package/assets/skill/SKILL.md +25 -6
- package/assets/skill/mcp-reference.md +21 -0
- package/package.json +2 -2
- package/src/app/context-agent-projection.ts +303 -0
- package/src/app/context-format.ts +249 -0
- package/src/app/context-runtime-contract.ts +325 -0
- package/src/app/context-runtime-input.ts +362 -0
- package/src/app/context-runtime-types.ts +65 -0
- package/src/app/context-runtime.ts +170 -0
- package/src/app/context-surface.ts +145 -0
- package/src/cli/commands/context-build.ts +149 -0
- package/src/cli/commands/context-verify.ts +90 -0
- package/src/cli/commands/daemon.ts +69 -2
- package/src/cli/commands/models/pull.ts +13 -3
- package/src/cli/commands/status.ts +2 -0
- package/src/cli/detach.ts +37 -20
- package/src/cli/options.ts +4 -0
- package/src/cli/program.ts +252 -27
- package/src/config/index.ts +3 -0
- package/src/config/types.ts +37 -0
- package/src/core/context-budget.ts +461 -0
- package/src/core/context-capsule-index-schema.ts +15 -0
- package/src/core/context-capsule-retrieval-schema.ts +81 -0
- package/src/core/context-capsule-schema.ts +473 -0
- package/src/core/context-capsule-validation.ts +416 -0
- package/src/core/context-capsule-verification.ts +218 -0
- package/src/core/context-capsule.ts +439 -0
- package/src/core/context-compiler.ts +513 -0
- package/src/core/context-evidence-metadata.ts +33 -0
- package/src/core/context-evidence.ts +495 -0
- package/src/core/context-facets.ts +163 -0
- package/src/core/context-guidance.ts +69 -0
- package/src/core/context-scope.ts +32 -0
- package/src/core/context-verifier-canonical.ts +90 -0
- package/src/core/context-verifier-input.ts +66 -0
- package/src/core/context-verifier.ts +447 -0
- package/src/core/job-manager.ts +19 -0
- package/src/core/mutation-generations.ts +33 -0
- package/src/core/sections.ts +63 -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 +28 -82
- 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 +230 -0
- package/src/mcp/tools/embed.ts +62 -52
- package/src/mcp/tools/index-cmd.ts +88 -74
- package/src/mcp/tools/index.ts +49 -2
- package/src/mcp/tools/remove-collection.ts +2 -0
- package/src/mcp/tools/status.ts +11 -0
- package/src/mcp/tools/sync.ts +16 -14
- package/src/mcp/tools/workspace-write.ts +7 -3
- package/src/pipeline/chunk-lookup.ts +33 -0
- package/src/pipeline/hybrid.ts +79 -57
- package/src/pipeline/types.ts +14 -0
- package/src/sdk/client.ts +68 -6
- package/src/sdk/index.ts +21 -0
- package/src/sdk/types.ts +24 -0
- package/src/serve/background-runtime.ts +12 -211
- package/src/serve/context-capsule.ts +136 -0
- package/src/serve/context.ts +10 -1
- 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/components/HealthCenter.tsx +74 -1
- package/src/serve/public/globals.built.css +1 -1
- package/src/serve/public/pages/Dashboard.tsx +1 -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/routes/api.ts +265 -167
- package/src/serve/routes/mcp.ts +69 -0
- package/src/serve/server.ts +212 -35
- package/src/serve/status-model.ts +51 -0
- package/src/serve/status.ts +5 -0
- package/src/store/sqlite/adapter.ts +64 -29
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
/** MCP Context Capsule tools over the shared application runtime. */
|
|
2
|
+
|
|
3
|
+
import type { ModelLease } from "../../llm/nodeLlamaCpp/lifecycle";
|
|
4
|
+
import type { EmbeddingPort, RerankPort } from "../../llm/types";
|
|
5
|
+
import type { VectorIndexPort } from "../../store/vector";
|
|
6
|
+
import type { ToolContext } from "../server";
|
|
7
|
+
import type { ToolResult } from "./index";
|
|
8
|
+
|
|
9
|
+
import { formatContextCapsuleAgentJson } from "../../app/context-agent-projection";
|
|
10
|
+
import { formatContextCapsuleVerificationMarkdown } from "../../app/context-format";
|
|
11
|
+
import {
|
|
12
|
+
buildContextCapsule,
|
|
13
|
+
canonicalVerifiedContextCapsuleJson,
|
|
14
|
+
validateContextCapsuleBuildInput,
|
|
15
|
+
verifyContextCapsuleRuntime,
|
|
16
|
+
} from "../../app/context-runtime";
|
|
17
|
+
import {
|
|
18
|
+
contextSurfaceError,
|
|
19
|
+
parseContextBuildSurfaceInput,
|
|
20
|
+
parseContextVerifySurfaceInput,
|
|
21
|
+
} from "../../app/context-surface";
|
|
22
|
+
import { createNonTtyProgressRenderer } from "../../cli/progress";
|
|
23
|
+
import { LlmAdapter } from "../../llm/nodeLlamaCpp/adapter";
|
|
24
|
+
import { resolveDownloadPolicy } from "../../llm/policy";
|
|
25
|
+
import { resolveModelUri } from "../../llm/registry";
|
|
26
|
+
import { createVectorIndexPort } from "../../store/vector";
|
|
27
|
+
|
|
28
|
+
interface ContextToolResultData {
|
|
29
|
+
structuredContent: Record<string, unknown>;
|
|
30
|
+
text: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const asToolResult = (data: ContextToolResultData): ToolResult => ({
|
|
34
|
+
content: [{ type: "text", text: data.text }],
|
|
35
|
+
structuredContent: data.structuredContent,
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
const asToolError = (error: unknown): ToolResult => {
|
|
39
|
+
const publicError = contextSurfaceError(error);
|
|
40
|
+
return {
|
|
41
|
+
isError: true,
|
|
42
|
+
content: [
|
|
43
|
+
{
|
|
44
|
+
type: "text",
|
|
45
|
+
text: `Error [${publicError.code}]: ${publicError.message}`,
|
|
46
|
+
},
|
|
47
|
+
],
|
|
48
|
+
structuredContent: {
|
|
49
|
+
error: publicError.code,
|
|
50
|
+
message: publicError.message,
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const runContextTool = async (
|
|
56
|
+
context: ToolContext,
|
|
57
|
+
operation: () => Promise<ContextToolResultData>
|
|
58
|
+
): Promise<ToolResult> => {
|
|
59
|
+
if (context.isShuttingDown()) {
|
|
60
|
+
return asToolError(
|
|
61
|
+
Object.assign(new Error("Server is shutting down"), {
|
|
62
|
+
code: "runtime_error",
|
|
63
|
+
})
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
const release = await context.toolMutex.acquire();
|
|
67
|
+
try {
|
|
68
|
+
const data = await (context.runWithSnapshot?.(operation) ?? operation());
|
|
69
|
+
return asToolResult(data);
|
|
70
|
+
} catch (error) {
|
|
71
|
+
return asToolError(error);
|
|
72
|
+
} finally {
|
|
73
|
+
release();
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
interface McpModelPorts {
|
|
78
|
+
embedPort: EmbeddingPort | null;
|
|
79
|
+
rerankPort: RerankPort | null;
|
|
80
|
+
vectorIndex: VectorIndexPort | null;
|
|
81
|
+
dispose(): Promise<void>;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
interface McpModelPortFactory {
|
|
85
|
+
createEmbeddingPort: LlmAdapter["createEmbeddingPort"];
|
|
86
|
+
createRerankPort: LlmAdapter["createRerankPort"];
|
|
87
|
+
acquireModelLease?: LlmAdapter["acquireModelLease"];
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export const disposeContextModelOwners = async (
|
|
91
|
+
portOwners: readonly { dispose(): Promise<void> }[],
|
|
92
|
+
lease?: ModelLease
|
|
93
|
+
): Promise<void> => {
|
|
94
|
+
await Promise.allSettled(
|
|
95
|
+
portOwners.map((owner) => Promise.resolve().then(() => owner.dispose()))
|
|
96
|
+
);
|
|
97
|
+
lease?.release();
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
export const createMcpModelPorts = async (
|
|
101
|
+
context: ToolContext,
|
|
102
|
+
collection?: string,
|
|
103
|
+
factoryOverride?: McpModelPortFactory
|
|
104
|
+
): Promise<McpModelPorts> => {
|
|
105
|
+
const llm = new LlmAdapter(context.config);
|
|
106
|
+
const factory = factoryOverride ?? llm;
|
|
107
|
+
const lease = factory.acquireModelLease?.();
|
|
108
|
+
const policy = resolveDownloadPolicy(process.env, {});
|
|
109
|
+
const progress = createNonTtyProgressRenderer();
|
|
110
|
+
const embedUri = resolveModelUri(
|
|
111
|
+
context.config,
|
|
112
|
+
"embed",
|
|
113
|
+
undefined,
|
|
114
|
+
collection
|
|
115
|
+
);
|
|
116
|
+
let embedPort: EmbeddingPort | null = null;
|
|
117
|
+
let ownedEmbedPort: EmbeddingPort | null = null;
|
|
118
|
+
let rerankPort: RerankPort | null = null;
|
|
119
|
+
let vectorIndex: VectorIndexPort | null = null;
|
|
120
|
+
try {
|
|
121
|
+
const embedResult = await factory.createEmbeddingPort(embedUri, {
|
|
122
|
+
policy,
|
|
123
|
+
onProgress: (value) => progress("embed", value),
|
|
124
|
+
});
|
|
125
|
+
if (embedResult.ok) {
|
|
126
|
+
// Take ownership before init: init failures must not leak the port.
|
|
127
|
+
ownedEmbedPort = embedResult.value;
|
|
128
|
+
const initialized = await ownedEmbedPort.init();
|
|
129
|
+
if (initialized.ok) {
|
|
130
|
+
embedPort = ownedEmbedPort;
|
|
131
|
+
const vectorResult = await createVectorIndexPort(
|
|
132
|
+
context.store.getRawDb(),
|
|
133
|
+
{ model: embedUri, dimensions: ownedEmbedPort.dimensions() }
|
|
134
|
+
);
|
|
135
|
+
if (vectorResult.ok) vectorIndex = vectorResult.value;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
const rerankResult = await factory.createRerankPort(
|
|
139
|
+
resolveModelUri(context.config, "rerank", undefined, collection),
|
|
140
|
+
{
|
|
141
|
+
policy,
|
|
142
|
+
onProgress: (value) => progress("rerank", value),
|
|
143
|
+
}
|
|
144
|
+
);
|
|
145
|
+
if (rerankResult.ok) rerankPort = rerankResult.value;
|
|
146
|
+
return {
|
|
147
|
+
embedPort,
|
|
148
|
+
rerankPort,
|
|
149
|
+
vectorIndex,
|
|
150
|
+
async dispose() {
|
|
151
|
+
await disposeContextModelOwners(
|
|
152
|
+
[ownedEmbedPort, rerankPort].filter(
|
|
153
|
+
(port): port is EmbeddingPort | RerankPort => port !== null
|
|
154
|
+
),
|
|
155
|
+
lease
|
|
156
|
+
);
|
|
157
|
+
},
|
|
158
|
+
};
|
|
159
|
+
} catch (error) {
|
|
160
|
+
await disposeContextModelOwners(
|
|
161
|
+
[ownedEmbedPort, rerankPort].filter(
|
|
162
|
+
(port): port is EmbeddingPort | RerankPort => port !== null
|
|
163
|
+
),
|
|
164
|
+
lease
|
|
165
|
+
);
|
|
166
|
+
throw error;
|
|
167
|
+
}
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
export const handleContext = (
|
|
171
|
+
args: unknown,
|
|
172
|
+
context: ToolContext
|
|
173
|
+
): Promise<ToolResult> =>
|
|
174
|
+
runContextTool(context, async () => {
|
|
175
|
+
const parsed = parseContextBuildSurfaceInput(args, context.indexName);
|
|
176
|
+
// This guard is intentionally before any model construction/download.
|
|
177
|
+
validateContextCapsuleBuildInput(
|
|
178
|
+
parsed.input,
|
|
179
|
+
context.indexName,
|
|
180
|
+
context.config.collections.map((collection) => collection.name)
|
|
181
|
+
);
|
|
182
|
+
const useModels = parsed.input.depthPolicy !== "fast";
|
|
183
|
+
const modelPorts = useModels
|
|
184
|
+
? await createMcpModelPorts(
|
|
185
|
+
context,
|
|
186
|
+
parsed.input.collections?.length === 1
|
|
187
|
+
? parsed.input.collections[0]
|
|
188
|
+
: undefined
|
|
189
|
+
)
|
|
190
|
+
: null;
|
|
191
|
+
try {
|
|
192
|
+
const capsule = await buildContextCapsule(parsed.input, {
|
|
193
|
+
store: context.store,
|
|
194
|
+
config: context.config,
|
|
195
|
+
indexName: context.indexName,
|
|
196
|
+
vectorIndex: modelPorts?.vectorIndex ?? null,
|
|
197
|
+
embedPort: modelPorts?.embedPort ?? null,
|
|
198
|
+
rerankPort: modelPorts?.rerankPort ?? null,
|
|
199
|
+
});
|
|
200
|
+
return {
|
|
201
|
+
structuredContent: capsule as unknown as Record<string, unknown>,
|
|
202
|
+
// MCP model context receives this projection exactly once. The full
|
|
203
|
+
// canonical capsule remains available to application clients through
|
|
204
|
+
// structuredContent and is deliberately not duplicated in text.
|
|
205
|
+
text: formatContextCapsuleAgentJson(capsule),
|
|
206
|
+
};
|
|
207
|
+
} finally {
|
|
208
|
+
await modelPorts?.dispose();
|
|
209
|
+
}
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
export const handleContextVerify = (
|
|
213
|
+
args: unknown,
|
|
214
|
+
context: ToolContext
|
|
215
|
+
): Promise<ToolResult> =>
|
|
216
|
+
runContextTool(context, async () => {
|
|
217
|
+
const parsed = parseContextVerifySurfaceInput(args);
|
|
218
|
+
const receipt = await verifyContextCapsuleRuntime(parsed.capsule, {
|
|
219
|
+
store: context.store,
|
|
220
|
+
config: context.config,
|
|
221
|
+
indexName: context.indexName,
|
|
222
|
+
});
|
|
223
|
+
return {
|
|
224
|
+
structuredContent: receipt as unknown as Record<string, unknown>,
|
|
225
|
+
text:
|
|
226
|
+
parsed.format === "md"
|
|
227
|
+
? formatContextCapsuleVerificationMarkdown(receipt)
|
|
228
|
+
: canonicalVerifiedContextCapsuleJson(receipt),
|
|
229
|
+
};
|
|
230
|
+
});
|
package/src/mcp/tools/embed.ts
CHANGED
|
@@ -9,6 +9,7 @@ import type { ToolContext } from "../server";
|
|
|
9
9
|
import { MCP_ERRORS } from "../../core/errors";
|
|
10
10
|
import { acquireWriteLock, type WriteLockHandle } from "../../core/file-lock";
|
|
11
11
|
import { JobError } from "../../core/job-manager";
|
|
12
|
+
import { recordIndexMutation } from "../../core/mutation-generations";
|
|
12
13
|
import { embedBacklog } from "../../embed";
|
|
13
14
|
import { LlmAdapter } from "../../llm/nodeLlamaCpp/adapter";
|
|
14
15
|
import { resolveModelUri } from "../../llm/registry";
|
|
@@ -81,64 +82,73 @@ export function handleEmbed(
|
|
|
81
82
|
"embed",
|
|
82
83
|
lock,
|
|
83
84
|
async () => {
|
|
84
|
-
|
|
85
|
-
const llm = new LlmAdapter(ctx.config);
|
|
86
|
-
const embedResult = await llm.createEmbeddingPort(modelUri, {
|
|
87
|
-
policy: { offline: true, allowDownload: false },
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
if (!embedResult.ok) {
|
|
91
|
-
throw new Error(
|
|
92
|
-
`MODEL_NOT_FOUND: Embedding model not cached. ` +
|
|
93
|
-
`Model: ${modelUri}. ` +
|
|
94
|
-
`Run 'gno models pull embed' first.`
|
|
95
|
-
);
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
const embedPort = embedResult.value;
|
|
99
|
-
|
|
85
|
+
const lease = ctx.acquireModelLease?.();
|
|
100
86
|
try {
|
|
101
|
-
//
|
|
102
|
-
const
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
}
|
|
106
|
-
const dimensions = embedPort.dimensions();
|
|
107
|
-
|
|
108
|
-
// Create vector index port
|
|
109
|
-
const db = ctx.store.getRawDb();
|
|
110
|
-
const vectorResult = await createVectorIndexPort(db, {
|
|
111
|
-
model: modelUri,
|
|
112
|
-
dimensions,
|
|
113
|
-
});
|
|
114
|
-
if (!vectorResult.ok) {
|
|
115
|
-
throw new Error(vectorResult.error.message);
|
|
116
|
-
}
|
|
117
|
-
const vectorIndex = vectorResult.value;
|
|
118
|
-
|
|
119
|
-
// Create stats port for backlog
|
|
120
|
-
const statsPort = createVectorStatsPort(db);
|
|
121
|
-
|
|
122
|
-
// Run embedding
|
|
123
|
-
const result = await embedBacklog({
|
|
124
|
-
statsPort,
|
|
125
|
-
embedPort,
|
|
126
|
-
vectorIndex,
|
|
127
|
-
collection: collection?.name,
|
|
128
|
-
modelUri,
|
|
129
|
-
batchSize: 32,
|
|
87
|
+
// Create LLM adapter with offline policy (fail-fast, no download)
|
|
88
|
+
const llm = new LlmAdapter(ctx.config);
|
|
89
|
+
const embedResult = await llm.createEmbeddingPort(modelUri, {
|
|
90
|
+
policy: { offline: true, allowDownload: false },
|
|
130
91
|
});
|
|
131
92
|
|
|
132
|
-
if (!
|
|
133
|
-
throw new Error(
|
|
93
|
+
if (!embedResult.ok) {
|
|
94
|
+
throw new Error(
|
|
95
|
+
`MODEL_NOT_FOUND: Embedding model not cached. ` +
|
|
96
|
+
`Model: ${modelUri}. ` +
|
|
97
|
+
`Run 'gno models pull embed' first.`
|
|
98
|
+
);
|
|
134
99
|
}
|
|
135
100
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
101
|
+
const embedPort = embedResult.value;
|
|
102
|
+
|
|
103
|
+
try {
|
|
104
|
+
// Initialize and get dimensions from port interface
|
|
105
|
+
const initResult = await embedPort.init();
|
|
106
|
+
if (!initResult.ok) {
|
|
107
|
+
throw new Error(initResult.error.message);
|
|
108
|
+
}
|
|
109
|
+
const dimensions = embedPort.dimensions();
|
|
110
|
+
|
|
111
|
+
// Create vector index port
|
|
112
|
+
const db = ctx.store.getRawDb();
|
|
113
|
+
const vectorResult = await createVectorIndexPort(db, {
|
|
114
|
+
model: modelUri,
|
|
115
|
+
dimensions,
|
|
116
|
+
});
|
|
117
|
+
if (!vectorResult.ok) {
|
|
118
|
+
throw new Error(vectorResult.error.message);
|
|
119
|
+
}
|
|
120
|
+
const vectorIndex = vectorResult.value;
|
|
121
|
+
|
|
122
|
+
// Create stats port for backlog
|
|
123
|
+
const statsPort = createVectorStatsPort(db);
|
|
124
|
+
|
|
125
|
+
// Run embedding
|
|
126
|
+
const result = await embedBacklog({
|
|
127
|
+
statsPort,
|
|
128
|
+
embedPort,
|
|
129
|
+
vectorIndex,
|
|
130
|
+
collection: collection?.name,
|
|
131
|
+
modelUri,
|
|
132
|
+
batchSize: 32,
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
if (!result.ok) {
|
|
136
|
+
throw new Error(result.error.message);
|
|
137
|
+
}
|
|
138
|
+
recordIndexMutation(
|
|
139
|
+
result.value.embedded,
|
|
140
|
+
ctx.markIndexMutation
|
|
141
|
+
);
|
|
142
|
+
|
|
143
|
+
return {
|
|
144
|
+
kind: "embed" as const,
|
|
145
|
+
value: result.value,
|
|
146
|
+
};
|
|
147
|
+
} finally {
|
|
148
|
+
await embedPort.dispose();
|
|
149
|
+
}
|
|
140
150
|
} finally {
|
|
141
|
-
|
|
151
|
+
lease?.release();
|
|
142
152
|
}
|
|
143
153
|
}
|
|
144
154
|
);
|
|
@@ -10,6 +10,10 @@ import type { ToolContext } from "../server";
|
|
|
10
10
|
import { MCP_ERRORS } from "../../core/errors";
|
|
11
11
|
import { acquireWriteLock, type WriteLockHandle } from "../../core/file-lock";
|
|
12
12
|
import { JobError } from "../../core/job-manager";
|
|
13
|
+
import {
|
|
14
|
+
recordContentMutation,
|
|
15
|
+
recordIndexMutation,
|
|
16
|
+
} from "../../core/mutation-generations";
|
|
13
17
|
import { normalizeCollectionName } from "../../core/validation";
|
|
14
18
|
import { embedBacklog } from "../../embed";
|
|
15
19
|
import { defaultSyncService, withContentTypeRules } from "../../ingestion";
|
|
@@ -115,86 +119,96 @@ export function handleIndex(
|
|
|
115
119
|
"index",
|
|
116
120
|
lock,
|
|
117
121
|
async () => {
|
|
118
|
-
|
|
119
|
-
const syncResult = collection
|
|
120
|
-
? await defaultSyncService
|
|
121
|
-
.syncCollection(collection, ctx.store, options)
|
|
122
|
-
.then((r) => ({
|
|
123
|
-
collections: [r],
|
|
124
|
-
totalDurationMs: r.durationMs,
|
|
125
|
-
totalFilesProcessed: r.filesProcessed,
|
|
126
|
-
totalFilesAdded: r.filesAdded,
|
|
127
|
-
totalFilesUpdated: r.filesUpdated,
|
|
128
|
-
totalFilesErrored: r.filesErrored,
|
|
129
|
-
totalFilesSkipped: r.filesSkipped,
|
|
130
|
-
}))
|
|
131
|
-
: await defaultSyncService.syncAll(
|
|
132
|
-
ctx.collections,
|
|
133
|
-
ctx.store,
|
|
134
|
-
options
|
|
135
|
-
);
|
|
136
|
-
|
|
137
|
-
// Phase 2: Embed
|
|
138
|
-
const llm = new LlmAdapter(ctx.config);
|
|
139
|
-
const embedResult = await llm.createEmbeddingPort(modelUri, {
|
|
140
|
-
policy: { offline: true, allowDownload: false },
|
|
141
|
-
});
|
|
142
|
-
|
|
143
|
-
if (!embedResult.ok) {
|
|
144
|
-
throw new Error(
|
|
145
|
-
`MODEL_NOT_FOUND: Embedding model not cached. ` +
|
|
146
|
-
`Model: ${modelUri}. ` +
|
|
147
|
-
`Run 'gno models pull embed' first.`
|
|
148
|
-
);
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
const embedPort = embedResult.value;
|
|
152
|
-
|
|
122
|
+
const lease = ctx.acquireModelLease?.();
|
|
153
123
|
try {
|
|
154
|
-
//
|
|
155
|
-
const
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
const
|
|
177
|
-
|
|
178
|
-
embedPort,
|
|
179
|
-
vectorIndex,
|
|
180
|
-
collection: collection?.name,
|
|
181
|
-
modelUri,
|
|
182
|
-
batchSize: 32,
|
|
124
|
+
// Phase 1: Sync
|
|
125
|
+
const syncResult = collection
|
|
126
|
+
? await defaultSyncService
|
|
127
|
+
.syncCollection(collection, ctx.store, options)
|
|
128
|
+
.then((r) => ({
|
|
129
|
+
collections: [r],
|
|
130
|
+
totalDurationMs: r.durationMs,
|
|
131
|
+
totalFilesProcessed: r.filesProcessed,
|
|
132
|
+
totalFilesAdded: r.filesAdded,
|
|
133
|
+
totalFilesUpdated: r.filesUpdated,
|
|
134
|
+
totalFilesErrored: r.filesErrored,
|
|
135
|
+
totalFilesSkipped: r.filesSkipped,
|
|
136
|
+
}))
|
|
137
|
+
: await defaultSyncService.syncAll(
|
|
138
|
+
ctx.collections,
|
|
139
|
+
ctx.store,
|
|
140
|
+
options
|
|
141
|
+
);
|
|
142
|
+
recordContentMutation(syncResult, ctx.markContentMutation);
|
|
143
|
+
|
|
144
|
+
// Phase 2: Embed
|
|
145
|
+
const llm = new LlmAdapter(ctx.config);
|
|
146
|
+
const embedResult = await llm.createEmbeddingPort(modelUri, {
|
|
147
|
+
policy: { offline: true, allowDownload: false },
|
|
183
148
|
});
|
|
184
149
|
|
|
185
|
-
if (!
|
|
186
|
-
throw new Error(
|
|
150
|
+
if (!embedResult.ok) {
|
|
151
|
+
throw new Error(
|
|
152
|
+
`MODEL_NOT_FOUND: Embedding model not cached. ` +
|
|
153
|
+
`Model: ${modelUri}. ` +
|
|
154
|
+
`Run 'gno models pull embed' first.`
|
|
155
|
+
);
|
|
187
156
|
}
|
|
188
157
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
158
|
+
const embedPort = embedResult.value;
|
|
159
|
+
|
|
160
|
+
try {
|
|
161
|
+
// Initialize and get dimensions from port interface
|
|
162
|
+
const initResult = await embedPort.init();
|
|
163
|
+
if (!initResult.ok) {
|
|
164
|
+
throw new Error(initResult.error.message);
|
|
165
|
+
}
|
|
166
|
+
const dimensions = embedPort.dimensions();
|
|
167
|
+
|
|
168
|
+
// Create vector index port
|
|
169
|
+
const db = ctx.store.getRawDb();
|
|
170
|
+
const vectorResult = await createVectorIndexPort(db, {
|
|
171
|
+
model: modelUri,
|
|
172
|
+
dimensions,
|
|
173
|
+
});
|
|
174
|
+
if (!vectorResult.ok) {
|
|
175
|
+
throw new Error(vectorResult.error.message);
|
|
176
|
+
}
|
|
177
|
+
const vectorIndex = vectorResult.value;
|
|
178
|
+
|
|
179
|
+
// Create stats port for backlog
|
|
180
|
+
const statsPort = createVectorStatsPort(db);
|
|
181
|
+
|
|
182
|
+
// Run embedding
|
|
183
|
+
const backlogResult = await embedBacklog({
|
|
184
|
+
statsPort,
|
|
185
|
+
embedPort,
|
|
186
|
+
vectorIndex,
|
|
187
|
+
collection: collection?.name,
|
|
188
|
+
modelUri,
|
|
189
|
+
batchSize: 32,
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
if (!backlogResult.ok) {
|
|
193
|
+
throw new Error(backlogResult.error.message);
|
|
194
|
+
}
|
|
195
|
+
recordIndexMutation(
|
|
196
|
+
backlogResult.value.embedded,
|
|
197
|
+
ctx.markIndexMutation
|
|
198
|
+
);
|
|
199
|
+
|
|
200
|
+
return {
|
|
201
|
+
kind: "index" as const,
|
|
202
|
+
value: {
|
|
203
|
+
sync: syncResult,
|
|
204
|
+
embed: backlogResult.value,
|
|
205
|
+
},
|
|
206
|
+
};
|
|
207
|
+
} finally {
|
|
208
|
+
await embedPort.dispose();
|
|
209
|
+
}
|
|
196
210
|
} finally {
|
|
197
|
-
|
|
211
|
+
lease?.release();
|
|
198
212
|
}
|
|
199
213
|
}
|
|
200
214
|
);
|
package/src/mcp/tools/index.ts
CHANGED
|
@@ -10,12 +10,17 @@ import { z } from "zod";
|
|
|
10
10
|
|
|
11
11
|
import type { ToolContext } from "../server";
|
|
12
12
|
|
|
13
|
+
import {
|
|
14
|
+
contextBuildSurfaceSchema,
|
|
15
|
+
contextVerifySurfaceSchema,
|
|
16
|
+
} from "../../app/context-surface";
|
|
13
17
|
import { CAPTURE_MAX_TEXT_BYTES } from "../../core/capture";
|
|
14
18
|
import { NOTE_PRESETS, type NotePresetId } from "../../core/note-presets";
|
|
15
19
|
import { normalizeTag } from "../../core/tags";
|
|
16
20
|
import { handleAddCollection } from "./add-collection";
|
|
17
21
|
import { handleCapture } from "./capture";
|
|
18
22
|
import { handleClearCollectionEmbeddings } from "./clear-collection-embeddings";
|
|
23
|
+
import { handleContext, handleContextVerify } from "./context";
|
|
19
24
|
import { handleEmbed } from "./embed";
|
|
20
25
|
import { handleGet } from "./get";
|
|
21
26
|
import { handleIndex } from "./index-cmd";
|
|
@@ -72,8 +77,27 @@ export const MCP_TOOL_DESCRIPTIONS = {
|
|
|
72
77
|
"Retrieve multiple documents by refs array or glob pattern. Use after gno_search/gno_query to batch top result URIs/docids; set maxBytes and lineNumbers to control context size.",
|
|
73
78
|
status:
|
|
74
79
|
"Get index health: collection count, document count, chunk count, embedding backlog, and per-collection stats. Check first when vector/hybrid results look stale or unavailable.",
|
|
80
|
+
context:
|
|
81
|
+
"Compile one deterministic, budgeted, extractive evidence Capsule with exact line spans, coverage gaps, omissions, provenance, and verification fingerprints. Raw search/get tools remain available for manual retrieval.",
|
|
82
|
+
contextVerify:
|
|
83
|
+
"Verify a saved Context Capsule without rebuilding or mutating it. Reports unchanged, stale, missing, reranked, and fingerprint drift states against the active index.",
|
|
75
84
|
} as const;
|
|
76
85
|
|
|
86
|
+
/** Tool names whose execution mutates disk, config, or index state. */
|
|
87
|
+
export const MCP_WRITE_TOOL_NAMES = new Set([
|
|
88
|
+
"gno_capture",
|
|
89
|
+
"gno_add_collection",
|
|
90
|
+
"gno_sync",
|
|
91
|
+
"gno_embed",
|
|
92
|
+
"gno_index",
|
|
93
|
+
"gno_remove_collection",
|
|
94
|
+
"gno_clear_collection_embeddings",
|
|
95
|
+
"gno_create_folder",
|
|
96
|
+
"gno_rename_note",
|
|
97
|
+
"gno_move_note",
|
|
98
|
+
"gno_duplicate_note",
|
|
99
|
+
]);
|
|
100
|
+
|
|
77
101
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
78
102
|
// Shared Input Schemas
|
|
79
103
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -820,8 +844,9 @@ export async function runTool<T>(
|
|
|
820
844
|
|
|
821
845
|
// Sequential execution via mutex
|
|
822
846
|
const release = await ctx.toolMutex.acquire();
|
|
847
|
+
const modelLease = ctx.acquireModelLease?.();
|
|
823
848
|
try {
|
|
824
|
-
const data = await fn();
|
|
849
|
+
const data = await (ctx.runWithSnapshot?.(fn) ?? fn());
|
|
825
850
|
return {
|
|
826
851
|
content: [{ type: "text", text: formatText(data) }],
|
|
827
852
|
structuredContent: data as { [x: string]: unknown },
|
|
@@ -837,6 +862,7 @@ export async function runTool<T>(
|
|
|
837
862
|
structuredContent: parsedError,
|
|
838
863
|
};
|
|
839
864
|
} finally {
|
|
865
|
+
modelLease?.release();
|
|
840
866
|
release();
|
|
841
867
|
}
|
|
842
868
|
}
|
|
@@ -860,8 +886,9 @@ export async function runToolNoMutex<T>(
|
|
|
860
886
|
};
|
|
861
887
|
}
|
|
862
888
|
|
|
889
|
+
const modelLease = ctx.acquireModelLease?.();
|
|
863
890
|
try {
|
|
864
|
-
const data = await fn();
|
|
891
|
+
const data = await (ctx.runWithSnapshot?.(fn) ?? fn());
|
|
865
892
|
return {
|
|
866
893
|
content: [{ type: "text", text: formatText(data) }],
|
|
867
894
|
structuredContent: data as { [x: string]: unknown },
|
|
@@ -876,6 +903,8 @@ export async function runToolNoMutex<T>(
|
|
|
876
903
|
content: [{ type: "text", text: `Error: ${message}` }],
|
|
877
904
|
structuredContent: parsedError,
|
|
878
905
|
};
|
|
906
|
+
} finally {
|
|
907
|
+
modelLease?.release();
|
|
879
908
|
}
|
|
880
909
|
}
|
|
881
910
|
|
|
@@ -899,6 +928,24 @@ function parseErrorMessage(message: string): { [x: string]: unknown } {
|
|
|
899
928
|
|
|
900
929
|
export function registerTools(server: McpServer, ctx: ToolContext): void {
|
|
901
930
|
// Tool IDs use underscores (MCP pattern: ^[a-zA-Z0-9_-]{1,64}$)
|
|
931
|
+
server.registerTool(
|
|
932
|
+
"gno_context",
|
|
933
|
+
{
|
|
934
|
+
description: MCP_TOOL_DESCRIPTIONS.context,
|
|
935
|
+
inputSchema: contextBuildSurfaceSchema,
|
|
936
|
+
},
|
|
937
|
+
(args) => handleContext(args, ctx)
|
|
938
|
+
);
|
|
939
|
+
|
|
940
|
+
server.registerTool(
|
|
941
|
+
"gno_context_verify",
|
|
942
|
+
{
|
|
943
|
+
description: MCP_TOOL_DESCRIPTIONS.contextVerify,
|
|
944
|
+
inputSchema: contextVerifySurfaceSchema,
|
|
945
|
+
},
|
|
946
|
+
(args) => handleContextVerify(args, ctx)
|
|
947
|
+
);
|
|
948
|
+
|
|
902
949
|
server.tool(
|
|
903
950
|
"gno_search",
|
|
904
951
|
MCP_TOOL_DESCRIPTIONS.search,
|
|
@@ -89,6 +89,8 @@ export function handleRemoveCollection(
|
|
|
89
89
|
if (!mutationResult.ok) {
|
|
90
90
|
throw mapConfigError(mutationResult.code, mutationResult.error);
|
|
91
91
|
}
|
|
92
|
+
ctx.markContentMutation?.();
|
|
93
|
+
ctx.markIndexMutation?.();
|
|
92
94
|
|
|
93
95
|
const result: RemoveCollectionResult = {
|
|
94
96
|
removed: true,
|