Forked from dirty-data/rag-v2
Project Files
packages / mcp-server / src / contracts.ts
import { z } from "zod";
export const inlineDocumentSchema = z.object({
id: z.string().min(1),
name: z.string().min(1),
content: z.string(),
metadata: z.record(z.string(), z.unknown()).optional(),
});
export const prechunkedCandidateSchema = z.object({
sourceId: z.string().min(1),
sourceName: z.string().min(1),
content: z.string().min(1),
score: z.number().finite().nonnegative(),
metadata: z.record(z.string(), z.unknown()).optional(),
});
export const ragModeSchema = z.enum([
"auto",
"full-context",
"retrieval",
"corrective",
]);
export const groundingModeSchema = z.enum([
"off",
"warn-on-weak-evidence",
"require-evidence",
]);
export const policyOptionsSchema = z
.object({
groundingMode: groundingModeSchema.optional(),
answerabilityGateEnabled: z.boolean().optional(),
answerabilityGateThreshold: z.number().min(0).max(1).optional(),
ambiguousQueryBehavior: z
.enum(["proceed", "ask-for-clarification", "warn"])
.optional(),
})
.optional();
export const routingOptionsSchema = z
.object({
requestedRoute: z
.enum(["auto", "no-retrieval", "full-context", "retrieval", "corrective"])
.optional(),
fullContextTokenLimit: z.number().int().min(1).optional(),
activeModelContextTokens: z.number().int().min(1).optional(),
correctiveEnabled: z.boolean().optional(),
correctiveMaxAttempts: z.number().int().min(0).max(4).optional(),
})
.optional();
export const retrievalOptionsSchema = z
.object({
multiQueryEnabled: z.boolean().optional(),
multiQueryCount: z.number().int().min(1).max(8).optional(),
fusionMethod: z.enum(["reciprocal-rank-fusion", "max-score"]).optional(),
hybridEnabled: z.boolean().optional(),
maxCandidates: z.number().int().min(1).max(32).optional(),
maxEvidenceBlocks: z.number().int().min(1).max(20).optional(),
minScore: z.number().min(0).max(1).optional(),
dedupeSimilarityThreshold: z.number().min(0).max(1).optional(),
})
.optional();
export const rerankOptionsSchema = z
.object({
enabled: z.boolean().optional(),
strategy: z.enum(["heuristic-v1", "heuristic-then-llm"]).optional(),
topK: z.number().int().min(1).max(20).optional(),
modelSource: z.enum(["active-chat-model", "auto-detect", "manual-model-id"]).optional(),
modelId: z.string().min(1).optional(),
})
.optional();
export const safetyOptionsSchema = z
.object({
sanitizeRetrievedText: z.boolean().optional(),
stripInstructionalSpans: z.boolean().optional(),
requireEvidence: z.boolean().optional(),
})
.optional();
export const groupedRequestOptionsSchema = z
.object({
policy: policyOptionsSchema,
routing: routingOptionsSchema,
retrieval: retrievalOptionsSchema,
rerank: rerankOptionsSchema,
safety: safetyOptionsSchema,
})
.optional();
export const retrievalOverridesSchema = z
.object({
multiQueryEnabled: z.boolean().optional(),
multiQueryCount: z.number().int().min(1).max(8).optional(),
fusionMethod: z.enum(["reciprocal-rank-fusion", "max-score"]).optional(),
hybridEnabled: z.boolean().optional(),
rerankEnabled: z.boolean().optional(),
rerankTopK: z.number().int().min(1).max(20).optional(),
rerankModelSource: z.enum(["active-chat-model", "auto-detect", "manual-model-id"]).optional(),
rerankModelId: z.string().min(1).optional(),
maxEvidenceBlocks: z.number().int().min(1).max(20).optional(),
})
.optional();
export const corpusInputBaseSchema = z.object({
documents: z.array(inlineDocumentSchema).optional(),
paths: z.array(z.string().min(1)).optional(),
chunks: z.array(prechunkedCandidateSchema).optional(),
});
export const corpusInputSchema = corpusInputBaseSchema.refine(
(value) =>
(value.documents?.length ?? 0) > 0 ||
(value.paths?.length ?? 0) > 0 ||
(value.chunks?.length ?? 0) > 0,
{
message: "Provide at least one of documents, paths, or chunks.",
}
);
export const ragAnswerInputSchema = corpusInputBaseSchema
.extend({
query: z.string().min(1),
mode: ragModeSchema.default("auto"),
groundingMode: groundingModeSchema.default("warn-on-weak-evidence"),
options: groupedRequestOptionsSchema,
retrieval: retrievalOverridesSchema,
})
.refine(
(value) =>
(value.documents?.length ?? 0) > 0 ||
(value.paths?.length ?? 0) > 0 ||
(value.chunks?.length ?? 0) > 0,
{
message: "Provide at least one of documents, paths, or chunks.",
}
);
export const ragSearchInputSchema = corpusInputBaseSchema
.extend({
query: z.string().min(1),
options: groupedRequestOptionsSchema,
retrieval: retrievalOverridesSchema,
})
.refine(
(value) =>
(value.documents?.length ?? 0) > 0 ||
(value.paths?.length ?? 0) > 0 ||
(value.chunks?.length ?? 0) > 0,
{
message: "Provide at least one of documents, paths, or chunks.",
}
);
export const ragPreparePromptInputSchema = corpusInputBaseSchema
.extend({
query: z.string().min(1),
mode: ragModeSchema.default("auto"),
groundingMode: groundingModeSchema.default("warn-on-weak-evidence"),
options: groupedRequestOptionsSchema,
retrieval: retrievalOverridesSchema,
})
.refine(
(value) =>
(value.documents?.length ?? 0) > 0 ||
(value.paths?.length ?? 0) > 0 ||
(value.chunks?.length ?? 0) > 0,
{
message: "Provide at least one of documents, paths, or chunks.",
}
);
export const corpusInspectInputSchema = corpusInputSchema;
export const rerankOnlyInputSchema = z.object({
query: z.string().min(1),
candidates: z.array(prechunkedCandidateSchema).min(1),
topK: z.number().int().min(1).max(20).default(5),
});
export const filesystemBrowseInputSchema = z.object({
path: z.string().min(1),
recursive: z.boolean().optional(),
maxDepth: z.number().int().min(0).max(32).optional(),
maxEntries: z.number().int().min(1).max(5000).optional(),
includeHidden: z.boolean().optional(),
});
export const fileInfoInputSchema = z.object({
path: z.string().min(1),
});
export const readFileInputSchema = z.object({
path: z.string().min(1),
startLine: z.number().int().min(0).optional(),
maxLines: z.number().int().min(1).max(2000).optional(),
maxChars: z.number().int().min(1).max(200000).optional(),
});
export const ragEvidenceBlockSchema = z.object({
label: z.string(),
fileName: z.string(),
content: z.string(),
score: z.number().finite(),
});
export const ragAnswerOutputSchema = z.object({
answer: z.string(),
route: z.string(),
confidence: z.number().min(0).max(1).optional(),
evidence: z.array(ragEvidenceBlockSchema),
unsupportedClaimWarnings: z.array(z.string()),
});
export const ragSearchOutputSchema = z.object({
candidates: z.array(prechunkedCandidateSchema),
route: z.string().optional(),
});
export const ragPreparePromptOutputSchema = z.object({
route: z.string(),
preparedPrompt: z.string(),
evidence: z.array(ragEvidenceBlockSchema),
diagnostics: z.object({
route: z.string(),
retrievalQueries: z.array(z.string()).optional(),
notes: z.array(z.string()).optional(),
degraded: z.boolean().optional(),
runtimeCapabilities: z.array(z.string()).optional(),
}),
unsupportedClaimWarnings: z.array(z.string()),
});
export const corpusInspectOutputSchema = z.object({
fileCount: z.number().int().nonnegative(),
chunkCount: z.number().int().nonnegative().optional(),
estimatedTokens: z.number().int().nonnegative().optional(),
recommendedRoute: z.string(),
fullContextViable: z.boolean(),
retrievalRecommended: z.boolean(),
});
export const rerankOnlyOutputSchema = z.object({
candidates: z.array(prechunkedCandidateSchema),
reasons: z.array(z.string()).optional(),
});
export const filesystemBrowseEntrySchema = z.object({
path: z.string(),
name: z.string(),
type: z.enum(["file", "directory"]),
sizeBytes: z.number().int().nonnegative().optional(),
extension: z.string().optional(),
});
export const fileExtensionCountSchema = z.object({
extension: z.string(),
count: z.number().int().nonnegative(),
});
export const filesystemBrowseOutputSchema = z.object({
requestedPath: z.string(),
resolvedPath: z.string(),
cwd: z.string(),
exists: z.boolean(),
type: z.enum(["file", "directory"]).optional(),
entries: z.array(filesystemBrowseEntrySchema),
truncated: z.boolean(),
directoryCount: z.number().int().nonnegative().optional(),
fileCount: z.number().int().nonnegative().optional(),
topExtensions: z.array(fileExtensionCountSchema).optional(),
errors: z.array(z.string()).optional(),
});
export const fileInfoOutputSchema = z.object({
requestedPath: z.string(),
resolvedPath: z.string(),
cwd: z.string(),
exists: z.boolean(),
type: z.enum(["file", "directory"]).optional(),
sizeBytes: z.number().int().nonnegative().optional(),
modifiedTimeMs: z.number().nonnegative().optional(),
extension: z.string().optional(),
textLike: z.boolean().optional(),
childCount: z.number().int().nonnegative().optional(),
directoryCount: z.number().int().nonnegative().optional(),
fileCount: z.number().int().nonnegative().optional(),
topExtensions: z.array(fileExtensionCountSchema).optional(),
errors: z.array(z.string()).optional(),
});
export const readFileOutputSchema = z.object({
requestedPath: z.string(),
resolvedPath: z.string(),
cwd: z.string(),
exists: z.boolean(),
startLine: z.number().int().nonnegative().optional(),
endLine: z.number().int().nonnegative().optional(),
content: z.string().optional(),
truncated: z.boolean(),
errors: z.array(z.string()).optional(),
});
export type RagAnswerInput = z.infer<typeof ragAnswerInputSchema>;
export type RagSearchInput = z.infer<typeof ragSearchInputSchema>;
export type RagPreparePromptInput = z.infer<typeof ragPreparePromptInputSchema>;
export type CorpusInspectInput = z.infer<typeof corpusInspectInputSchema>;
export type RerankOnlyInput = z.infer<typeof rerankOnlyInputSchema>;
export type FileSystemBrowseInput = z.infer<typeof filesystemBrowseInputSchema>;
export type FileInfoInput = z.infer<typeof fileInfoInputSchema>;
export type ReadFileInput = z.infer<typeof readFileInputSchema>;
export type RagAnswerOutput = z.infer<typeof ragAnswerOutputSchema>;
export type RagSearchOutput = z.infer<typeof ragSearchOutputSchema>;
export type RagPreparePromptOutput = z.infer<typeof ragPreparePromptOutputSchema>;
export type CorpusInspectOutput = z.infer<typeof corpusInspectOutputSchema>;
export type RerankOnlyOutput = z.infer<typeof rerankOnlyOutputSchema>;
export type FileSystemBrowseOutput = z.infer<typeof filesystemBrowseOutputSchema>;
export type FileInfoOutput = z.infer<typeof fileInfoOutputSchema>;
export type ReadFileOutput = z.infer<typeof readFileOutputSchema>;