@edda-business/mcp 0.75.1 → 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 +62 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@edda-business/mcp",
3
- "version": "0.75.1",
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
@@ -222,6 +222,9 @@ export function createServer() {
222
222
  const label = String(c.predicate ?? "").replace(/[_:]/g, " ");
223
223
  lines.push(`[claim] ${c.subject ? `${c.subject} · ` : ""}${label}${mark}${infer}: ${c.object}`);
224
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`);
225
228
  }
226
229
  lines.push("");
227
230
  }
@@ -245,6 +248,65 @@ export function createServer() {
245
248
  },
246
249
  );
247
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
+
248
310
  // ===========================================================================
249
311
  // TOOL: read — GET /v2/company/pages/:id
250
312
  // Read one full wiki page. Take the `path:` from a search result, or an id.