版本: 1.0.0 用途: Server 实现者参考
编码 qmd:// URI 路径
function encodeQmdPath(path: string): string {
return path.split('/').map(segment => encodeURIComponent(segment)).join('/');
}- 用途: 编码 qmd:// URI 路径
- 规则: 每段单独编码,保留斜杠
- 示例:
"notes/2025 meeting.md"→"notes%2F2025%20meeting.md"
添加行号到文本
function addLineNumbers(text: string, startLine: number = 1): string {
const lines = text.split('\n');
return lines.map((line, i) => `${startLine + i}: ${line}`).join('\n');
}- 用途: 添加行号到文本
- 格式:
{lineNum}: {content} - 示例:
"First line\nSecond line"→"1: First line\n2: Second line"
格式化搜索结果摘要
function formatSearchSummary(results: SearchResultItem[], query: string): string {
if (results.length === 0) {
return `No results found for "${query}"`;
}
const lines = [`Found ${results.length} result${results.length === 1 ? '' : 's'} for "${query}":\n`];
for (const r of results) {
lines.push(`${r.docid} ${Math.round(r.score * 100)}% ${r.file} - ${r.title}`);
}
return lines.join('\n');
}- 用途: 格式化搜索结果摘要
- 格式:
#abc123 85% path/to/file.md - Title
type SearchResultItem = {
docid: string; // 短 ID (#abc123)
file: string; // 显示路径
title: string; // 文档标题
score: number; // 相关性分数 0-1
context: string | null; // 上下文描述
snippet: string; // 带行号的摘录
};type StatusResult = {
totalDocuments: number;
needsEmbedding: number;
hasVectorIndex: boolean;
collections: {
name: string;
path: string;
pattern: string;
documents: number;
lastUpdated: string; // ISO date string
}[];
};算法: 使用 Levenshtein 距离计算相似度
实现:
from Levenshtein import distance
def suggest_similar_files(target: str, candidates: List[str], top_n: int = 3):
scored = [(f, distance(target.lower(), f.lower())) for f in candidates]
scored.sort(key=lambda x: x[1])
return [f for f, _ in scored[:top_n]]输出格式:
Did you mean one of these?
- file1.md
- file2.md
| Tool | 行为 |
|---|---|
| vsearch | 返回 isError: true,错误消息:"Vector index not found. Run 'qmd embed' first" |
| query | 降级到仅 FTS 搜索(不报错) |
实现:
def has_vector_index(db) -> bool:
cursor = db.execute("SELECT COUNT(*) FROM content_vectors")
return cursor.fetchone()[0] > 0流程:
- 使用 LLM 生成 3-5 个查询变体
- 并行执行所有查询
- 合并去重结果(取最高分数)
实现示例:
async def expand_query(query: str, llm) -> List[str]:
prompt = f"Generate 3-5 search query variations for: {query}"
variations = await llm.generate(prompt)
return [query] + variations.split('\n')输入:
{"query": "meeting", "limit": 5, "minScore": 0.5}预期输出:
{
"content": [{"type": "text", "text": "Found 2 results for \"meeting\":..."}],
"structuredContent": {"results": [{...}, {...}]}
}输入:
{"file": "notes/meeting.md:120", "maxLines": 20}预期行为:
fromLine = 120file = "notes/meeting.md"- 返回第 120-140 行,带行号
输入:
{"pattern": "journals/2025-*.md", "maxBytes": 5120}预期行为:
- 匹配所有
journals/2025-开头的.md文件 - 跳过大于 5KB 的文件
输入: qmd://notes%2F2025%2F%20meeting.md
预期解析:
collection:"notes"path:"2025/ meeting.md"- 解码后:
notes/2025/ meeting.md
规范: {lineNum}: {content}
示例:
1: First line
2: Second line
100: Hundredth line
实现: 确保冒号后有空格
规范: #{hash} (短 hash 前缀)
示例: #abc123
实现: 使用内容 hash 的前 6-7 个字符
编码: 每个路径段单独编码,保留 / 分隔符
函数: 见 encodeQmdPath 辅助函数
实现: 使用 encodeURIComponent(JavaScript)或 urllib.parse.quote(Python)
原则: 返回有意义的错误消息,包含解决建议
示例:
{
"content": [{"type": "text", "text": "Vector index not found. Run 'qmd embed' first to create embeddings."}],
"isError": true
}建议:
- 使用连接池(数据库、HTTP)
- 实现查询缓存(可选)
- 批量操作限制(如
embed最多 1000 个文本) - 异步处理(使用
asyncio)
建议:
- 验证所有输入参数
- 限制文件访问范围(防止路径遍历攻击)
- 限制资源使用(内存、CPU)
- 记录关键操作(审计日志)
- MCP 规范: Model Context Protocol
- 原始实现:
D:\MoneyProjects\qmd\src\mcp.ts(627 lines) - 测试文件:
D:\MoneyProjects\qmd\src\mcp.test.ts - 配置文档:
D:\MoneyProjects\qmd\skills\qmd\references\mcp-setup.md
最后更新: 2026-02-17 维护者: QMD-Python Team