@edda-business/mcp 0.69.0 → 0.69.2

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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/server.js +23 -10
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@edda-business/mcp",
3
- "version": "0.69.0",
3
+ "version": "0.69.2",
4
4
  "description": "Edda — the company data layer for AI agents.",
5
5
  "license": "Apache-2.0",
6
6
  "publishConfig": {
package/src/server.js CHANGED
@@ -62,18 +62,26 @@ const norm = (s) => String(s ?? "").trim().toLowerCase().replace(/\.md$/, "");
62
62
  async function resolvePathToId(path) {
63
63
  const list = await get("/v2/company/pages");
64
64
  const pages = list?.pages ?? [];
65
- const base = norm(String(path).split("/").pop());
65
+ const segs = String(path).split("/").map((x) => x.trim()).filter(Boolean);
66
+ const base = norm(segs[segs.length - 1] ?? "");
67
+ const folderSegs = segs.length > 1 ? segs.slice(0, -1).map(norm) : null;
66
68
  let cands = pages.filter((p) => norm(p.name) === base);
67
- if (!cands.length) return { error: `No company page named "${String(path).split("/").pop()}". Use search to find the exact path first.` };
69
+ // EDDA-91: a folder-qualified request MUST honour its folder. The old basename-only match
70
+ // silently substituted an unrelated same-named page from the caller's visible scope when the
71
+ // requested folder was out of scope (or misspelled) — an exact read must return THAT document
72
+ // or a truthful unavailable, never masquerade. The message deliberately does not reveal
73
+ // whether the path exists for someone else.
74
+ if (folderSegs) {
75
+ cands = cands.filter((p) => p.folder && folderSegs.includes(norm(p.folder)));
76
+ }
77
+ if (!cands.length) {
78
+ return { error: folderSegs
79
+ ? `"${String(path)}" is not available here — it either does not exist or is outside what this seat can read. Use search to see what you can access.`
80
+ : `No company page named "${String(path).split("/").pop()}". Use search to find the exact path first.` };
81
+ }
68
82
  if (cands.length > 1) {
69
- // Prefer a candidate whose folder slug appears in the requested path.
70
- const pref = cands.filter((p) => p.folder && norm(path).includes(norm(p.folder)));
71
- if (pref.length === 1) return { id: pref[0].id };
72
- cands = pref.length ? pref : cands;
73
- if (cands.length > 1) {
74
- const opts = cands.map((p) => ` • ${[p.folder, p.name].filter(Boolean).join("/")} (id: ${p.id})`).join("\n");
75
- return { error: `Several pages match "${base}". Pass one of these ids as \`id\`:\n${opts}` };
76
- }
83
+ const opts = cands.map((p) => ` • ${[p.folder, p.name].filter(Boolean).join("/")} (id: ${p.id})`).join("\n");
84
+ return { error: `Several pages match "${base}". Pass one of these ids as \`id\`:\n${opts}` };
77
85
  }
78
86
  return { id: cands[0].id };
79
87
  }
@@ -225,6 +233,11 @@ export function createServer() {
225
233
  for (const pg of recent) lines.push(` · ${[pg.folder, pg.name].filter(Boolean).join("/")} (${String(pg.updated_at).slice(0, 10)})`);
226
234
  }
227
235
  lines.push(`\nInbox: ${r?.inbox_pending ?? 0} item(s) waiting.`);
236
+ const paths = r?.folder_paths ?? [];
237
+ if (paths.length) {
238
+ lines.push(`\nFolder tree (use these EXACT paths as destinations — a bare project name creates a NEW top-level project):`);
239
+ for (const p of paths) lines.push(` ${p}`);
240
+ }
228
241
  return text(lines.join("\n"));
229
242
  } catch (e) {
230
243
  const msg = String(e?.message || e);