clef-mcp 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,431 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import { z } from 'zod';
3
+
4
+ interface Limits {
5
+ /** Maximum number of questions per clef_decide call. */
6
+ maxQuestions: number;
7
+ /** Maximum serialized size of `state`, in bytes. */
8
+ maxStateBytes: number;
9
+ /** Maximum characters per question `instructions`. */
10
+ maxInstructionsChars: number;
11
+ /** Maximum number of criteria per question. */
12
+ maxCriteriaPerQuestion: number;
13
+ /** Maximum characters per criterion label/description. */
14
+ maxCriteriaDescChars: number;
15
+ /** Maximum characters per question id (the questions map key). */
16
+ maxQuestionIdChars: number;
17
+ }
18
+ declare const DEFAULT_LIMITS: Limits;
19
+ declare function limitsFromEnv(env?: NodeJS.ProcessEnv): Limits;
20
+
21
+ type LogLevel = 'error' | 'warn' | 'info' | 'debug';
22
+ type RuntimeId = 'llama-cpp';
23
+ interface ClefConfig {
24
+ /** Model id, e.g. "clef-flash". */
25
+ model: string;
26
+ /** Cache/model home. Override with CLEF_HOME. */
27
+ clefHome: string;
28
+ /** Inference runtime. v0.1 ships only llama-cpp. */
29
+ runtime: RuntimeId;
30
+ logLevel: LogLevel;
31
+ /** Explicit path to a llama-server binary (CLEF_LLAMA_BIN). */
32
+ llamaBin?: string;
33
+ /** Pin the llama.cpp release tag used by the managed runtime download. */
34
+ llamaReleaseTag?: string;
35
+ limits: Limits;
36
+ }
37
+ declare function defaultClefHome(): string;
38
+ declare function loadConfig(env?: NodeJS.ProcessEnv): ClefConfig;
39
+
40
+ interface ClefPaths {
41
+ root: string;
42
+ modelsDir: string;
43
+ runtimeDir: string;
44
+ configJson: string;
45
+ }
46
+ declare function pathsFor(clefHome: string): ClefPaths;
47
+ declare function modelQuantDir(modelsDir: string, model: string, quant: string): string;
48
+ declare function manifestPathFor(modelsDir: string, model: string, quant: string): string;
49
+
50
+ type LogSink = (line: string) => void;
51
+ interface Logger {
52
+ error(msg: string, extra?: Record<string, unknown>): void;
53
+ warn(msg: string, extra?: Record<string, unknown>): void;
54
+ info(msg: string, extra?: Record<string, unknown>): void;
55
+ debug(msg: string, extra?: Record<string, unknown>): void;
56
+ }
57
+ declare class StderrLogger implements Logger {
58
+ private readonly level;
59
+ private readonly sink;
60
+ constructor(level: LogLevel, sink?: LogSink);
61
+ private write;
62
+ error(msg: string, extra?: Record<string, unknown>): void;
63
+ warn(msg: string, extra?: Record<string, unknown>): void;
64
+ info(msg: string, extra?: Record<string, unknown>): void;
65
+ debug(msg: string, extra?: Record<string, unknown>): void;
66
+ }
67
+ declare function createLogger(level: LogLevel, sink?: LogSink): Logger;
68
+
69
+ declare const ClefErrorCode: {
70
+ readonly MODEL_NOT_INSTALLED: "MODEL_NOT_INSTALLED";
71
+ readonly MODEL_LOAD_FAILED: "MODEL_LOAD_FAILED";
72
+ readonly RUNTIME_NOT_FOUND: "RUNTIME_NOT_FOUND";
73
+ readonly RUNTIME_INIT_FAILED: "RUNTIME_INIT_FAILED";
74
+ readonly RUNTIME_NOT_SUPPORTED: "RUNTIME_NOT_SUPPORTED";
75
+ readonly INVALID_INPUT: "INVALID_INPUT";
76
+ readonly CLEF_INFERENCE_FAILED: "CLEF_INFERENCE_FAILED";
77
+ readonly UNSUPPORTED_PLATFORM: "UNSUPPORTED_PLATFORM";
78
+ readonly OUT_OF_MEMORY: "OUT_OF_MEMORY";
79
+ readonly CHECKSUM_MISMATCH: "CHECKSUM_MISMATCH";
80
+ readonly DOWNLOAD_FAILED: "DOWNLOAD_FAILED";
81
+ };
82
+ type ClefErrorCode = (typeof ClefErrorCode)[keyof typeof ClefErrorCode];
83
+ declare class ClefError extends Error {
84
+ readonly code: ClefErrorCode;
85
+ readonly hint?: string;
86
+ constructor(code: ClefErrorCode, message: string, hint?: string);
87
+ }
88
+ interface StructuredError {
89
+ error: {
90
+ code: ClefErrorCode;
91
+ message: string;
92
+ hint?: string;
93
+ };
94
+ }
95
+ /** Serialized form used both in MCP tool results and CLI output. */
96
+ declare function errorJson(err: unknown): StructuredError;
97
+ /** Exit codes for CLI commands. */
98
+ declare function exitCodeFor(err: unknown): number;
99
+
100
+ type QuestionType = 'noul' | 'choice' | 'score';
101
+ interface ClefQuestion {
102
+ type: QuestionType;
103
+ instructions: string;
104
+ /**
105
+ * `choice`: map of option id -> description.
106
+ * `score`: ordered list of criterion descriptions (index == score).
107
+ * `noul`: optional map with "true"/"false" descriptions.
108
+ */
109
+ criteria?: string[] | Record<string, string>;
110
+ }
111
+ interface ClefDecideInput {
112
+ state: unknown;
113
+ questions: Record<string, ClefQuestion>;
114
+ model?: string;
115
+ options?: {
116
+ temperature?: number;
117
+ };
118
+ }
119
+ /** Probability distribution over answer options for one question. */
120
+ type ClefDecisionAnswer = Record<string, number>;
121
+ type ClefDecideOutput = {
122
+ model: string;
123
+ decisions: Record<string, {
124
+ answer: ClefDecisionAnswer;
125
+ }>;
126
+ };
127
+
128
+ /**
129
+ * Wire-level types for the SystemOne API (`POST /v1/systemone`), the de-facto
130
+ * interchange format for Clef decision models exposed by llama.cpp and the
131
+ * MLX loader alike.
132
+ */
133
+ interface SystemOneQuestion {
134
+ type: 'noul' | 'choice' | 'score';
135
+ instructions?: string;
136
+ criteria?: string[] | Record<string, string>;
137
+ }
138
+ interface SystemOneRequest {
139
+ model: string;
140
+ state: string;
141
+ questions: Record<string, SystemOneQuestion>;
142
+ }
143
+ interface SystemOneUsage {
144
+ [key: string]: unknown;
145
+ }
146
+ interface SystemOneAnswer {
147
+ choice?: string;
148
+ confidence?: number;
149
+ /** Probability of `true` for noul questions (llama.cpp uses `noul`). */
150
+ noul?: number;
151
+ probability_true?: number;
152
+ probabilities?: Record<string, number> | number[];
153
+ expected_score?: number;
154
+ score?: number;
155
+ legend?: string[] | Record<string, string>;
156
+ [key: string]: unknown;
157
+ }
158
+ interface SystemOneResponse {
159
+ model?: string;
160
+ answers: Record<string, SystemOneAnswer>;
161
+ usage?: SystemOneUsage;
162
+ }
163
+ /** Map a validated clef_decide input to a SystemOne request body. */
164
+ declare function toSystemOneRequest(input: ClefDecideInput, defaultModel: string): SystemOneRequest;
165
+ /** Normalize a SystemOne response into the strict clef_decide output shape. */
166
+ declare function fromSystemOneResponse(response: SystemOneResponse, questions: Record<string, ClefQuestion>, fallbackModel: string): ClefDecideOutput;
167
+
168
+ interface RuntimeHealth {
169
+ status: 'ready' | 'loading' | 'unavailable';
170
+ detail?: string;
171
+ }
172
+ /**
173
+ * Inference runtime abstraction. Implementations load/unload the model and
174
+ * answer SystemOne requests, so the MCP layer never talks to an engine
175
+ * directly. Swapping runtimes (MLX, remote) must not change the MCP API.
176
+ */
177
+ interface ClefRuntime {
178
+ load(): Promise<void>;
179
+ decide(input: SystemOneRequest): Promise<SystemOneResponse>;
180
+ unload(): Promise<void>;
181
+ health(): Promise<RuntimeHealth>;
182
+ }
183
+
184
+ interface LlamaCppRuntimeOptions {
185
+ serverBin: string;
186
+ modelPath: string;
187
+ alias?: string;
188
+ host?: string;
189
+ port?: number;
190
+ /** Max wait for the server to report ready after spawn. */
191
+ loadTimeoutMs?: number;
192
+ /** Per-request timeout for /v1/systemone. */
193
+ requestTimeoutMs?: number;
194
+ log?: Logger;
195
+ }
196
+ declare class LlamaCppRuntime implements ClefRuntime {
197
+ private readonly opts;
198
+ private proc?;
199
+ private resolvedPort?;
200
+ private readonly host;
201
+ private readonly loadTimeoutMs;
202
+ private readonly requestTimeoutMs;
203
+ /** Serializes decide() calls: the llama.cpp clef path handles one sequence per batch. */
204
+ private queue;
205
+ constructor(opts: LlamaCppRuntimeOptions);
206
+ get endpoint(): string;
207
+ get running(): boolean;
208
+ load(): Promise<void>;
209
+ private waitUntilReady;
210
+ decide(input: SystemOneRequest): Promise<SystemOneResponse>;
211
+ private decideOnce;
212
+ health(): Promise<RuntimeHealth>;
213
+ unload(): Promise<void>;
214
+ }
215
+
216
+ interface ResolvedBinary {
217
+ source: 'env' | 'managed' | 'path';
218
+ path: string;
219
+ }
220
+ interface BinaryResolveDeps {
221
+ existsExecutable?(p: string): boolean;
222
+ which?(name: string): string | undefined;
223
+ platform?: string;
224
+ arch?: string;
225
+ }
226
+ /**
227
+ * Resolution order: CLEF_LLAMA_BIN → managed binary under CLEF_HOME → `llama-server` on PATH.
228
+ * Managed wins over PATH because a user's PATH binary may predate clef support.
229
+ */
230
+ declare function resolveLlamaServerBinary(cfg: ClefConfig, deps?: BinaryResolveDeps): ResolvedBinary;
231
+
232
+ type QuantId = 'Q4_K_M' | 'Q8_0' | 'BF16';
233
+ interface QuantSpec {
234
+ id: QuantId;
235
+ file: string;
236
+ bytes: number;
237
+ dirLabel: string;
238
+ /** Minimum total memory (RAM; on macOS unified) needed to run this quant comfortably. */
239
+ minTotalMemBytes: number;
240
+ }
241
+ interface ModelSpec {
242
+ id: string;
243
+ repo: string;
244
+ /** Pinned upstream revision for reproducible downloads + checksums. */
245
+ revision: string;
246
+ licenseId: string;
247
+ licenseUrl: string;
248
+ originUrl: string;
249
+ defaultQuant: QuantId;
250
+ quants: QuantSpec[];
251
+ }
252
+ declare const CLEF_FLASH: ModelSpec;
253
+ declare const MODELS: Record<string, ModelSpec>;
254
+ declare function getModelSpec(model: string): ModelSpec;
255
+ declare function getQuantSpec(spec: ModelSpec, quant: string): QuantSpec;
256
+ /**
257
+ * Auto-pick the largest quantization that fits in memory. The 9B clef-flash
258
+ * model needs headroom: Q4_K_M fits in 16 GB, Q8_0 in 32 GB, BF16 in 64 GB.
259
+ */
260
+ declare function pickQuant(spec: ModelSpec, totalMemBytes: number, requested?: string): QuantSpec;
261
+
262
+ interface ModelManifest {
263
+ schemaVersion: 1;
264
+ model: string;
265
+ quant: string;
266
+ file: string;
267
+ /** Absolute path to the model file. */
268
+ modelPath: string;
269
+ bytes: number;
270
+ /** sha256 of the model file (from the HF xet/LFS hash, verified after download). */
271
+ sha256?: string;
272
+ repo: string;
273
+ revision: string;
274
+ licenseId: string;
275
+ licenseUrl: string;
276
+ installedAt: string;
277
+ clefMcpVersion: string;
278
+ }
279
+ declare function readModelManifest(manifestPath: string): Promise<ModelManifest | undefined>;
280
+ declare function writeModelManifest(manifestPath: string, manifest: ModelManifest): Promise<void>;
281
+ interface ManifestCheck {
282
+ ok: boolean;
283
+ problems: string[];
284
+ }
285
+ /** Cheap structural verification: manifest present, file exists, size matches. */
286
+ declare function verifyModelFiles(manifest: ModelManifest): Promise<ManifestCheck>;
287
+ /** Full sha256 verification of the model file against the manifest hash. */
288
+ declare function deepVerifyChecksum(manifest: ModelManifest): Promise<ManifestCheck>;
289
+ /** Manifest for the newest installed quantization of a model, if any. */
290
+ declare function findInstalledManifest(clefHome: string, spec: ModelSpec): Promise<ModelManifest | undefined>;
291
+
292
+ interface ProbeResult {
293
+ latencyMs: number;
294
+ binarySource: 'env' | 'managed' | 'path';
295
+ binaryPath: string;
296
+ }
297
+ /**
298
+ * Full end-to-end check: start llama-server with the installed model, run one
299
+ * tiny decision, shut down. Used by `install` (verification step) and `doctor`.
300
+ */
301
+ declare function probeInference(cfg: ClefConfig, manifest: ModelManifest, log?: Logger, loadTimeoutMs?: number): Promise<ProbeResult>;
302
+
303
+ interface HfFileInfo {
304
+ downloadUrl: string;
305
+ /** sha256 from the HF xet/LFS hash, when advertised. */
306
+ sha256?: string;
307
+ size?: number;
308
+ }
309
+ /**
310
+ * Resolve download URL + expected checksum. The sha256 arrives as the
311
+ * `x-linked-etag` header on HF's 302, so we probe with redirect: 'manual'.
312
+ */
313
+ declare function fetchFileInfo(repo: string, revision: string, file: string): Promise<HfFileInfo>;
314
+ interface DownloadProgress {
315
+ bytes: number;
316
+ total?: number;
317
+ }
318
+ interface DownloadOptions {
319
+ expectedSha256?: string;
320
+ expectedSize?: number;
321
+ onProgress?(p: DownloadProgress): void;
322
+ signal?: AbortSignal;
323
+ attempts?: number;
324
+ }
325
+ interface DownloadResult {
326
+ bytes: number;
327
+ sha256: string;
328
+ }
329
+ /**
330
+ * Stream a file to dest, hashing while writing. Retries transient failures by
331
+ * restarting the download; checksum/size mismatches fail hard.
332
+ */
333
+ declare function downloadFile(url: string, dest: string, opts?: DownloadOptions): Promise<DownloadResult>;
334
+ interface InstallModelOptions {
335
+ clefHome: string;
336
+ modelSpec: ModelSpec;
337
+ quantSpec: QuantSpec;
338
+ clefMcpVersion: string;
339
+ onProgress?(p: DownloadProgress): void;
340
+ log?: Logger;
341
+ }
342
+ interface InstallModelResult {
343
+ manifest: ModelManifest;
344
+ alreadyInstalled: boolean;
345
+ }
346
+ /**
347
+ * Download a model quantization from its pinned HF revision, verify checksum,
348
+ * and write the manifest. Safe to re-run: an intact install is a no-op.
349
+ */
350
+ declare function installModel(opts: InstallModelOptions): Promise<InstallModelResult>;
351
+
352
+ interface ManagedRuntimeResult extends ResolvedBinary {
353
+ releaseTag: string;
354
+ freshInstall: boolean;
355
+ }
356
+ /**
357
+ * Ensure a llama-server binary exists: resolve env/managed/PATH first, else
358
+ * download the official ggml-org nightly build into CLEF_HOME/runtime.
359
+ */
360
+ declare function ensureLlamaRuntime(cfg: ClefConfig, log?: Logger): Promise<ManagedRuntimeResult>;
361
+ declare function downloadManagedLlama(cfg: ClefConfig, log?: Logger): Promise<ResolvedBinary & {
362
+ releaseTag: string;
363
+ }>;
364
+ /** Remove the managed llama.cpp runtime directory, if present. */
365
+ declare function removeManagedRuntime(cfg: ClefConfig): Promise<boolean>;
366
+
367
+ interface GpuInfo {
368
+ name?: string;
369
+ vramBytes?: number;
370
+ }
371
+ interface PlatformInfo {
372
+ platform: NodeJS.Platform;
373
+ arch: string;
374
+ totalMemBytes: number;
375
+ gpu?: GpuInfo;
376
+ }
377
+ declare function detectPlatform(): Promise<PlatformInfo>;
378
+ /** llama.cpp clef builds ship for these platforms; anything else is rejected early. */
379
+ declare function assertSupportedPlatform(info: Pick<PlatformInfo, 'platform' | 'arch'>): void;
380
+ declare function formatBytes(bytes: number): string;
381
+
382
+ declare const TOOL_NAME = "clef_decide";
383
+ declare const SERVER_INSTRUCTIONS: string;
384
+ interface ClefServerDeps {
385
+ config?: ClefConfig;
386
+ version?: string;
387
+ log?: Logger;
388
+ /** Test seam: override runtime construction (defaults to LlamaCppRuntime). */
389
+ runtimeFactory?: (opts: {
390
+ serverBin: string;
391
+ modelPath: string;
392
+ alias: string;
393
+ log?: Logger;
394
+ }) => ClefRuntime;
395
+ }
396
+ interface RunningClefServer {
397
+ server: McpServer;
398
+ start(): Promise<void>;
399
+ shutdown(): Promise<void>;
400
+ }
401
+ declare function createClefServer(deps?: ClefServerDeps): RunningClefServer;
402
+
403
+ declare function clefDecideInputSchema(limits: Limits): z.ZodObject<{
404
+ state: z.ZodUnknown;
405
+ questions: z.ZodRecord<z.ZodString, z.ZodObject<{
406
+ type: z.ZodEnum<{
407
+ noul: "noul";
408
+ choice: "choice";
409
+ score: "score";
410
+ }>;
411
+ instructions: z.ZodString;
412
+ criteria: z.ZodOptional<z.ZodUnion<readonly [z.ZodArray<z.ZodString>, z.ZodRecord<z.ZodString, z.ZodString>]>>;
413
+ }, z.core.$strip>>;
414
+ model: z.ZodOptional<z.ZodString>;
415
+ options: z.ZodOptional<z.ZodObject<{
416
+ temperature: z.ZodOptional<z.ZodNumber>;
417
+ }, z.core.$strip>>;
418
+ }, z.core.$strip>;
419
+ /** Output schema mirrored by the MCP tool result (structuredContent). */
420
+ declare function clefDecideOutputSchema(): z.ZodObject<{
421
+ model: z.ZodString;
422
+ decisions: z.ZodRecord<z.ZodString, z.ZodObject<{
423
+ answer: z.ZodRecord<z.ZodString, z.ZodNumber>;
424
+ }, z.core.$strip>>;
425
+ }, z.core.$strip>;
426
+ /** `state` cannot be size-checked inside zod (it is `unknown`), so it is validated separately. */
427
+ declare function validateStateSize(state: unknown, limits: Limits): void;
428
+
429
+ declare const VERSION = "0.1.0";
430
+
431
+ export { CLEF_FLASH, type ClefConfig, type ClefDecideInput, type ClefDecideOutput, type ClefDecisionAnswer, ClefError, ClefErrorCode, type ClefQuestion, type ClefRuntime, type ClefServerDeps, DEFAULT_LIMITS, type DownloadProgress, type InstallModelResult, type Limits, LlamaCppRuntime, type LlamaCppRuntimeOptions, type LogLevel, type Logger, MODELS, type ModelManifest, type ModelSpec, type PlatformInfo, type ProbeResult, type QuantSpec, type QuestionType, type ResolvedBinary, type RunningClefServer, type RuntimeHealth, SERVER_INSTRUCTIONS, StderrLogger, type SystemOneRequest, type SystemOneResponse, TOOL_NAME, VERSION, assertSupportedPlatform, clefDecideInputSchema, clefDecideOutputSchema, createClefServer, createLogger, deepVerifyChecksum, defaultClefHome, detectPlatform, downloadFile, downloadManagedLlama, ensureLlamaRuntime, errorJson, exitCodeFor, fetchFileInfo, findInstalledManifest, formatBytes, fromSystemOneResponse, getModelSpec, getQuantSpec, installModel, limitsFromEnv, loadConfig, manifestPathFor, modelQuantDir, pathsFor, pickQuant, probeInference, readModelManifest, removeManagedRuntime, resolveLlamaServerBinary, toSystemOneRequest, validateStateSize, verifyModelFiles, writeModelManifest };