Quellcode durchsuchen

Fix MCP double-encoding and schema mismatches

- Update MCP queries to use new schema (path, collection instead of display_path, filepath)
- Fix resource list to construct virtual paths properly: qmd://collection/path
- Fix resource read handler to parse virtual paths and join with content table
- Prevents double-encoding issues like qmd://qmd%3A//archive/...
Tobi Lutke vor 5 Monaten
Ursprung
Commit
d32ec1991b
3 geänderte Dateien mit 35 neuen und 15 gelöschten Zeilen
  1. 1 1
      src/cli.test.ts
  2. 30 11
      src/mcp.ts
  3. 4 3
      src/store.test.ts

+ 1 - 1
src/cli.test.ts

@@ -458,7 +458,7 @@ describe("CLI Context Management", () => {
       "Global system context",
     ], { dbPath: localDbPath });
     expect(exitCode).toBe(0);
-    expect(stdout).toContain("✓ Added global context");
+    expect(stdout).toContain("✓ Set global context");
     expect(stdout).toContain("Global system context");
   });
 

+ 30 - 11
src/mcp.ts

@@ -97,18 +97,18 @@ export async function startMcpServer(): Promise<void> {
       list: async () => {
         // List all indexed documents
         const docs = store.db.prepare(`
-          SELECT display_path, title
+          SELECT path, title, collection
           FROM documents
           WHERE active = 1
           ORDER BY modified_at DESC
           LIMIT 1000
-        `).all() as { display_path: string; title: string }[];
+        `).all() as { path: string; title: string; collection: string }[];
 
         return {
           resources: docs.map(doc => ({
-            uri: `qmd://${encodeQmdPath(doc.display_path)}`,
-            name: doc.display_path,
-            title: doc.title || doc.display_path,
+            uri: `qmd://${doc.collection}/${encodeQmdPath(doc.path)}`,
+            name: `${doc.collection}/${doc.path}`,
+            title: doc.title || doc.path,
             mimeType: "text/markdown",
           })),
         };
@@ -123,30 +123,49 @@ export async function startMcpServer(): Promise<void> {
       // Decode URL-encoded path (MCP clients send encoded URIs)
       const decodedPath = decodeURIComponent(path);
 
-      // Find document by display_path
-      let doc = store.db.prepare(`SELECT filepath, display_path, title, body FROM documents WHERE display_path = ? AND active = 1`).get(decodedPath) as { filepath: string; display_path: string; title: string; body: string } | null;
+      // Parse virtual path: collection/relative/path
+      const parts = decodedPath.split('/');
+      const collection = parts[0];
+      const relativePath = parts.slice(1).join('/');
+
+      // Find document by collection and path, join with content table
+      let doc = store.db.prepare(`
+        SELECT d.collection, d.path, d.title, c.doc as body
+        FROM documents d
+        JOIN content c ON c.hash = d.hash
+        WHERE d.collection = ? AND d.path = ? AND d.active = 1
+      `).get(collection, relativePath) as { collection: string; path: string; title: string; body: string } | null;
 
       // Try suffix match if exact match fails
       if (!doc) {
-        doc = store.db.prepare(`SELECT filepath, display_path, title, body FROM documents WHERE display_path LIKE ? AND active = 1 LIMIT 1`).get(`%${decodedPath}`) as { filepath: string; display_path: string; title: string; body: string } | null;
+        doc = store.db.prepare(`
+          SELECT d.collection, d.path, d.title, c.doc as body
+          FROM documents d
+          JOIN content c ON c.hash = d.hash
+          WHERE d.path LIKE ? AND d.active = 1
+          LIMIT 1
+        `).get(`%${relativePath}`) as { collection: string; path: string; title: string; body: string } | null;
       }
 
       if (!doc) {
         return { contents: [{ uri: uri.href, text: `Document not found: ${decodedPath}` }] };
       }
 
-      const context = store.getContextForFile(doc.filepath);
+      // Construct virtual path for context lookup
+      const virtualPath = `qmd://${doc.collection}/${doc.path}`;
+      const context = store.getContextForFile(virtualPath);
 
       let text = doc.body;
       if (context) {
         text = `<!-- Context: ${context} -->\n\n` + text;
       }
 
+      const displayName = `${doc.collection}/${doc.path}`;
       return {
         contents: [{
           uri: uri.href,
-          name: doc.display_path,
-          title: doc.title || doc.display_path,
+          name: displayName,
+          title: doc.title || doc.path,
           mimeType: "text/markdown",
           text,
         }],

+ 4 - 3
src/store.test.ts

@@ -678,8 +678,8 @@ describe("FTS Search", () => {
 
     const results = store.searchFTS("fox", 10);
     expect(results.length).toBeGreaterThan(0);
-    // displayPath now uses virtual path format
-    expect(results[0].displayPath).toBe(`qmd://${collectionName}/test/doc1.md`);
+    expect(results[0].displayPath).toBe("test/doc1.md");
+    expect(results[0].filepath).toBe(`qmd://${collectionName}/test/doc1.md`);
     expect(results[0].source).toBe("fts");
 
     await cleanupTestDb(store);
@@ -800,7 +800,8 @@ describe("FTS Search", () => {
 
     const results = store.searchFTS("findme", 10);
     expect(results).toHaveLength(1);
-    expect(results[0].displayPath).toBe(`qmd://${collectionName}/test/active.md`);
+    expect(results[0].displayPath).toBe("test/active.md");
+    expect(results[0].filepath).toBe(`qmd://${collectionName}/test/active.md`);
 
     await cleanupTestDb(store);
   });