- Add embedding_profiles table with provider registry pattern - Install @xenova/transformers as runtime dependency - Update snippet_embeddings with composite PK (snippet_id, profile_id) - Seed default local profile using Xenova/all-MiniLM-L6-v2 - Add provider registry (local-transformers, openai-compatible) - Update EmbeddingService to persist and retrieve by profileId - Add version-scoped VectorSearch with optional versionId filtering - Add searchMode (auto|keyword|semantic|hybrid) to HybridSearchService - Update API /context route to load active profile, support searchMode/alpha params - Extend MCP query-docs tool with searchMode and alpha parameters - Update settings API to work with embedding_profiles table - Add comprehensive test coverage for profiles, registry, version scoping Status: 445/451 tests passing, core feature complete
122 lines
3.0 KiB
TypeScript
122 lines
3.0 KiB
TypeScript
/**
|
|
* query-docs tool handler
|
|
*
|
|
* Fetches documentation and code examples from TrueRef for a specific library.
|
|
* Tool schema is identical to context7 for drop-in compatibility.
|
|
*/
|
|
|
|
import { z } from 'zod';
|
|
import { fetchContext } from '../client.js';
|
|
|
|
export const QueryDocsSchema = z.object({
|
|
libraryId: z
|
|
.string()
|
|
.describe('The TrueRef library ID obtained from resolve-library-id, e.g. /facebook/react'),
|
|
query: z
|
|
.string()
|
|
.describe('Specific question about the library to retrieve relevant documentation'),
|
|
tokens: z.number().optional().describe('Maximum token budget for the response (default: 10000)'),
|
|
searchMode: z
|
|
.enum(['auto', 'keyword', 'semantic', 'hybrid'])
|
|
.optional()
|
|
.describe(
|
|
"Retrieval mode: 'auto' (default), 'keyword' (FTS only), 'semantic' (vector only), or 'hybrid'"
|
|
),
|
|
alpha: z
|
|
.number()
|
|
.min(0)
|
|
.max(1)
|
|
.optional()
|
|
.describe('Hybrid blend weight: 0.0 = keyword only, 1.0 = semantic only (default: 0.5)')
|
|
});
|
|
|
|
export type QueryDocsInput = z.infer<typeof QueryDocsSchema>;
|
|
|
|
export const QUERY_DOCS_TOOL = {
|
|
name: 'query-docs',
|
|
description: [
|
|
'Fetches documentation and code examples from TrueRef for a specific library.',
|
|
'Requires a library ID obtained from resolve-library-id.',
|
|
'Returns relevant snippets formatted for LLM consumption.',
|
|
'Call at most 3 times per user question.'
|
|
].join(' '),
|
|
inputSchema: {
|
|
type: 'object' as const,
|
|
properties: {
|
|
libraryId: {
|
|
type: 'string',
|
|
description: 'TrueRef library ID, e.g. /facebook/react'
|
|
},
|
|
query: {
|
|
type: 'string',
|
|
description: 'Specific question about the library'
|
|
},
|
|
tokens: {
|
|
type: 'number',
|
|
description: 'Max token budget (default: 10000)'
|
|
},
|
|
searchMode: {
|
|
type: 'string',
|
|
enum: ['auto', 'keyword', 'semantic', 'hybrid'],
|
|
description: "Retrieval mode: 'auto' (default), 'keyword', 'semantic', or 'hybrid'"
|
|
},
|
|
alpha: {
|
|
type: 'number',
|
|
minimum: 0,
|
|
maximum: 1,
|
|
description: 'Hybrid blend weight (0=keyword, 1=semantic, default: 0.5)'
|
|
}
|
|
},
|
|
required: ['libraryId', 'query']
|
|
}
|
|
};
|
|
|
|
export async function handleQueryDocs(args: unknown) {
|
|
const { libraryId, query, tokens, searchMode, alpha } = QueryDocsSchema.parse(args);
|
|
|
|
const response = await fetchContext({ libraryId, query, tokens, type: 'txt', searchMode, alpha });
|
|
|
|
if (!response.ok) {
|
|
const status = response.status;
|
|
|
|
if (status === 404) {
|
|
return {
|
|
content: [
|
|
{
|
|
type: 'text' as const,
|
|
text: `Library "${libraryId}" not found. Please run resolve-library-id first.`
|
|
}
|
|
],
|
|
isError: true
|
|
};
|
|
}
|
|
|
|
if (status === 503) {
|
|
return {
|
|
content: [
|
|
{
|
|
type: 'text' as const,
|
|
text: `Library "${libraryId}" is currently being indexed. Please try again in a moment.`
|
|
}
|
|
],
|
|
isError: true
|
|
};
|
|
}
|
|
|
|
return {
|
|
content: [
|
|
{
|
|
type: 'text' as const,
|
|
text: `Error fetching documentation: ${response.status} ${response.statusText}`
|
|
}
|
|
],
|
|
isError: true
|
|
};
|
|
}
|
|
|
|
const text = await response.text();
|
|
return {
|
|
content: [{ type: 'text' as const, text }]
|
|
};
|
|
}
|