embarsy-qdrant-mcp 0.1.4 → 0.1.5

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.
package/dist/indexer.js CHANGED
@@ -3,7 +3,7 @@ import path from "node:path";
3
3
  import { embedBatch } from "./embeddings.js";
4
4
  import { chunkFile } from "./chunker.js";
5
5
  import { discoverFiles } from "./walk.js";
6
- import { ensureCollection, upsertPoints, deleteByFilePath, scrollFileHashes, } from "./qdrant.js";
6
+ import { ensureCollection, upsertPoints, deleteByFilePath, scrollFileHashes, setWorkspacePayload, } from "./qdrant.js";
7
7
  import { sha256, pointId, languageForExtension } from "./util.js";
8
8
  /** Crude binary guard: a NUL byte early in the file means it isn't source text. */
9
9
  function looksBinary(s) {
@@ -29,10 +29,31 @@ function maxLineLength(s) {
29
29
  }
30
30
  return cur > max ? cur : max;
31
31
  }
32
+ /** Directories that hold source but never name a project. */
33
+ const CONTAINER_DIRS = new Set([
34
+ "src", "source", "sources", "lib", "libs", "app", "apps",
35
+ "internal", "cmd", "pkg", "test", "tests",
36
+ ]);
37
+ /** Project name to store with every chunk — the indexed folder's name, except that
38
+ * indexing `~/code/myproj/src` should still say `myproj` and not `src`. Only the NAME
39
+ * travels: the absolute path would put the user's home directory into every point. */
40
+ export function workspaceNameFor(rootAbs) {
41
+ let current = rootAbs;
42
+ for (;;) {
43
+ const name = path.basename(current);
44
+ const parent = path.dirname(current);
45
+ if (!name || parent === current)
46
+ return path.basename(rootAbs);
47
+ if (!CONTAINER_DIRS.has(name.toLowerCase()))
48
+ return name;
49
+ current = parent;
50
+ }
51
+ }
32
52
  /** Full/incremental index of a directory into the configured Qdrant collection. */
33
53
  export async function indexRepo(root, cfg, opts = {}) {
34
54
  const log = opts.onProgress ?? (() => { });
35
55
  const rootAbs = path.resolve(root);
56
+ const workspace = workspaceNameFor(rootAbs);
36
57
  await ensureCollection(cfg);
37
58
  const files = await discoverFiles(rootAbs, cfg);
38
59
  log(`Found ${files.length} indexable files in ${rootAbs}`);
@@ -87,6 +108,7 @@ export async function indexRepo(root, cfg, opts = {}) {
87
108
  text: `${file.rel}\n\n${c.text}`, // path gives the embedder useful context
88
109
  payload: {
89
110
  file_path: file.rel,
111
+ workspace,
90
112
  language,
91
113
  start_line: c.startLine,
92
114
  end_line: c.endLine,
@@ -110,5 +132,9 @@ export async function indexRepo(root, cfg, opts = {}) {
110
132
  result.removed++;
111
133
  }
112
134
  }
135
+ // Backfills points this run skipped as unchanged, and points indexed by an older bridge
136
+ // that never wrote the field — without it the name would stay missing until every file
137
+ // in the project happened to change.
138
+ await setWorkspacePayload(cfg, workspace);
113
139
  return result;
114
140
  }
package/dist/qdrant.js CHANGED
@@ -58,6 +58,20 @@ export async function deleteByFilePath(cfg, filePath) {
58
58
  body: JSON.stringify({ filter: { must: [{ key: "file_path", match: { value: filePath } }] } }),
59
59
  }, { label: "delete points" });
60
60
  }
61
+ /** Stamp `workspace` onto EVERY point in the collection.
62
+ *
63
+ * Setting it only on freshly embedded points would leave it off almost everything: an
64
+ * index run skips unchanged files, so on the second run nearly no point is rewritten.
65
+ * This is one payload write over the whole collection — no re-embedding — which also
66
+ * backfills collections indexed before the field existed. */
67
+ export async function setWorkspacePayload(cfg, workspace) {
68
+ await requestJSON(`${base(cfg)}/points/payload?wait=true`, {
69
+ method: "POST",
70
+ headers: headers(cfg),
71
+ // An empty filter selects every point in the collection.
72
+ body: JSON.stringify({ payload: { workspace }, filter: {} }),
73
+ }, { label: "set workspace payload" });
74
+ }
61
75
  /** Map of file_path -> file_hash for every indexed file, used to skip unchanged files. */
62
76
  export async function scrollFileHashes(cfg) {
63
77
  const out = new Map();
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "embarsy-qdrant-mcp",
3
- "version": "0.1.4",
4
- "description": "Codebase-indexing MCP server for Embarsy — semantic code search over a local Qdrant + OpenAI-compatible embeddings stack. Routes embeddings and Qdrant through Embarsy's proxy so auth and Monitoring counters just work.",
3
+ "version": "0.1.5",
4
+ "description": "Codebase-indexing MCP server for Embarsy \u2014 semantic code search over a local Qdrant + OpenAI-compatible embeddings stack. Routes embeddings and Qdrant through Embarsy's proxy so auth and Monitoring counters just work.",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "embarsy-index": "dist/bin/index.js",