@edda-business/mcp 0.75.0 → 0.76.0

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 +67 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@edda-business/mcp",
3
- "version": "0.75.0",
3
+ "version": "0.76.0",
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
@@ -33,9 +33,13 @@
33
33
 
34
34
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
35
35
  import { z } from "zod";
36
+ import { createRequire } from "node:module";
36
37
  import { get, post, patch, postRaw, isReadOnly } from "./client.js";
37
38
 
38
- export const SERVER_VERSION = "0.66.0";
39
+ // Read from package.json, never retyped here. The constant said 0.66.0 for nine releases: every
40
+ // handshake, `--version` and /health answered with a version that had not shipped since June, which
41
+ // is the one number support asks for first.
42
+ export const SERVER_VERSION = createRequire(import.meta.url)("../package.json").version;
39
43
 
40
44
  // ─── helpers ──────────────────────────────────────────────────────────────────
41
45
 
@@ -218,6 +222,9 @@ export function createServer() {
218
222
  const label = String(c.predicate ?? "").replace(/[_:]/g, " ");
219
223
  lines.push(`[claim] ${c.subject ? `${c.subject} · ` : ""}${label}${mark}${infer}: ${c.object}`);
220
224
  lines.push(` as of ${String(c.as_of ?? "").slice(0, 10)} · evidence ${c.source?.kind}:${c.source?.id}`);
225
+ // The id is what opens the evidence. Without it the agent can repeat what the company
226
+ // believes and cannot say why — the gap explain_claim exists to close.
227
+ if (c.id) lines.push(` claim id: ${c.id} ← pass this to explain_claim`);
221
228
  }
222
229
  lines.push("");
223
230
  }
@@ -241,6 +248,65 @@ export function createServer() {
241
248
  },
242
249
  );
243
250
 
251
+ // ===========================================================================
252
+ // TOOL: explain_claim — GET /v2/company/claims/:id/evidence
253
+ // "Why does Edda believe this?", for an agent. The browser has had this since EDDA-153; a seat
254
+ // could only repeat the claim.
255
+ // ===========================================================================
256
+ server.tool(
257
+ "explain_claim",
258
+ "WHY the company believes a claim: how firm it is, when it became true vs when Edda recorded " +
259
+ "it, the sources you may open, how it was approved, and what it replaced. Take the `claim id` " +
260
+ "from a search result. " +
261
+ "Use it before repeating a company fact that carries weight — a figure in a document you are " +
262
+ "drafting, anything a person will act on, or when a claim looks stale or surprising. " +
263
+ "Say what it says: if it reports no excerpt, the wording has NOT been checked against the " +
264
+ "source, and you must not present the claim as quoted. Never invent supporting text.",
265
+ { claim_id: z.string().describe("The claim id, printed as `claim id:` in search results.") },
266
+ async ({ claim_id }) => {
267
+ try {
268
+ const r = await get(`/v2/company/claims/${encodeURIComponent(claim_id)}/evidence`);
269
+ const c = r?.claim ?? {};
270
+ const day = (d) => (d ? String(d).slice(0, 10) : "—");
271
+ const lines = [
272
+ `${c.subject?.name ? `${c.subject.name} · ` : ""}${String(c.predicate ?? "").replace(/[_:]/g, " ")}: ${c.object}`,
273
+ `standing: ${c.standing}${c.confidence === "inferred" ? " · inferred (read between the lines, not stated)" : ""} · ${c.state === "superseded" ? "NO LONGER CURRENT" : "current"}`,
274
+ `true as of ${day(c.as_of)} · recorded by Edda ${day(c.recorded_at)}`,
275
+ "",
276
+ ];
277
+ lines.push(r?.excerpt?.text
278
+ ? `Supporting passage (checked word for word against the source):\n "${r.excerpt.text}"`
279
+ : "Supporting passage: NONE RECORDED — the source below is where it came from, but nobody checked that it says this in these words. Do not quote it.");
280
+ if (r?.excerpt_rejected && !r?.excerpt?.text) lines.push(" (a quotation was proposed and was not found in the document, so it was dropped)");
281
+ lines.push("");
282
+ const sources = r?.sources ?? [];
283
+ lines.push(sources.length ? "Sources:" : "Sources: none you can open.");
284
+ for (const s of sources) {
285
+ if (s.available === false && s.reason === "missing") { lines.push(" · a cited document that no longer exists"); continue; }
286
+ lines.push(` · ${s.name}${s.primary ? " (primary)" : ""}${s.changed_since === true ? " — EDITED since this claim was recorded" : ""}`);
287
+ if (s.id) lines.push(` id: ${s.id} ← pass this to read`);
288
+ }
289
+ const a = r?.approval ?? {};
290
+ const who = (p) => p?.name || (p?.id ? "a team member" : null);
291
+ lines.push("", a.kind === "inbox_approval"
292
+ ? `Approved in the Inbox${a.auto_filed ? " automatically" : ` by ${who(a.by) ?? "an admin"}`} on ${day(a.at)}${who(a.submitted_by) ? `, proposed by ${who(a.submitted_by)}` : ""}.`
293
+ : a.kind === "direct_write"
294
+ ? `Written directly into the project's knowledge by ${who(a.by) ?? "an admin"} on ${day(a.at)}.`
295
+ : `Recorded ${day(a.at)}. No approval record exists.`);
296
+ const h = r?.history ?? {};
297
+ if (h.replaced?.length) lines.push(`Replaced: ${h.replaced.map((x) => `"${x.object}"`).join(", ")}`);
298
+ if (h.replaced_by?.length) lines.push(`Replaced by: ${h.replaced_by.map((x) => `"${x.object}"`).join(" → ")}`);
299
+ if (h.alongside?.length) lines.push(`Also current at a different standing: ${h.alongside.map((x) => `"${x.object}" (${x.standing})`).join(", ")}`);
300
+ return text(lines.join("\n"));
301
+ } catch (e) {
302
+ const msg = String(e?.message || e);
303
+ if (msg.includes("(404)")) return text("No such claim, or it is not one this seat may read. Claims about people and companies are admin-only for now.");
304
+ if (msg.includes("(403)")) return text("This seat cannot read company claims.");
305
+ throw e;
306
+ }
307
+ },
308
+ );
309
+
244
310
  // ===========================================================================
245
311
  // TOOL: read — GET /v2/company/pages/:id
246
312
  // Read one full wiki page. Take the `path:` from a search result, or an id.