@edda-business/mcp 0.57.0 → 0.58.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 +35 -53
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@edda-business/mcp",
3
- "version": "0.57.0",
3
+ "version": "0.58.0",
4
4
  "description": "Edda — the company data layer for AI agents.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
package/src/server.js CHANGED
@@ -26,7 +26,7 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
26
26
  import { z } from "zod";
27
27
  import { get, post, del } from "./client.js";
28
28
 
29
- export const SERVER_VERSION = "0.57.0";
29
+ export const SERVER_VERSION = "0.58.0";
30
30
 
31
31
  // ─── helpers ──────────────────────────────────────────────────────────────────
32
32
 
@@ -91,13 +91,12 @@ export function createServer() {
91
91
  server.tool(
92
92
  "get_context",
93
93
  "Get engineered context for a specific task about a person or company. Pass their email (or " +
94
- "entity id) and the intent. Returns a focused, ranked context block: the facts that matter for " +
95
- "that task each with a confidence and a freshness the durable FACTS we've learned about them " +
96
- "(their atomic memory: budget, authority, pain, stack, plans), the recent timeline, the buying-group " +
97
- "stakeholders, open predictions, and the account's ICP fit score (0-100 + why). Call this before " +
98
- "drafting outreach, preparing for a meeting, " +
99
- "or making any decision about a person. A fact's freshness tells you whether to trust it: 'fresh' " +
100
- "act on it, 'suspect'/'expired' verify first.",
94
+ "entity id) and the intent. Returns a focused, ranked context block: the durable FACTS we've " +
95
+ "learned about them (each with a confidence and a freshness), the recent timeline, the other " +
96
+ "people at their company and how they relate, and the relevant notes and records. Call this before " +
97
+ "a meeting or any decision about a person or company, so you act on what we actually know rather " +
98
+ "than generic guesses. A fact's freshness tells you whether to trust it: 'fresh' act on it, " +
99
+ "'suspect'/'expired' verify first.",
101
100
  {
102
101
  focus: z.string().describe("Who to look up — an email, a LinkedIn URL, a domain, an entity UUID, or a name. A name may match several people; you'll get candidates to choose from."),
103
102
  intent: z.enum(["follow_up", "meeting_prep", "call_prep", "account_review"])
@@ -131,11 +130,6 @@ export function createServer() {
131
130
  }
132
131
  lines.push("");
133
132
  }
134
- if (ctx.workspace?.length) {
135
- lines.push("YOUR CONTEXT (ICP / product / positioning):");
136
- for (const w of ctx.workspace) lines.push(` ${w.property}: ${fmtVal(w.value)}`);
137
- lines.push("");
138
- }
139
133
  if (ctx.timeline?.length) {
140
134
  lines.push("TIMELINE:");
141
135
  for (const t of ctx.timeline) {
@@ -157,23 +151,18 @@ export function createServer() {
157
151
  lines.push("");
158
152
  }
159
153
  if (ctx.stakeholders?.length) {
160
- // The buying committee as a STRUCTURE: who's at the account, their role,
161
- // whether we've engaged them, and how they relate so the agent works the
162
- // whole committee, not one person.
154
+ // The people connected to this company and how they relate — the local map, so
155
+ // the agent sees the whole picture, not one person in isolation.
163
156
  const c = ctx.committee;
164
- lines.push(c?.company ? `BUYING COMMITTEE ${c.company}:` : "STAKEHOLDERS:");
157
+ lines.push(c?.company ? `PEOPLE AT ${c.company}:` : "RELATED PEOPLE:");
165
158
  for (const s of ctx.stakeholders) {
166
159
  if (s.role === "company") continue; // the company is the header
167
160
  const bits = [];
168
- if (s.committee_role && s.committee_role !== "contact") bits.push(s.committee_role.replace(/_/g, " "));
169
161
  if (s.role) bits.push(s.role);
170
- bits.push(s.engaged ? "engaged" : "not yet engaged");
171
162
  if (s.confirmed === false) bits.push("mentioned, unconfirmed");
172
163
  const rel = s.relationships?.length ? ` — ${s.relationships.join("; ")}` : "";
173
- lines.push(` ${s.name ?? "—"} (${bits.join(", ")})${rel}`);
164
+ lines.push(` ${s.name ?? "—"}${bits.length ? ` (${bits.join(", ")})` : ""}${rel}`);
174
165
  }
175
- if (c?.champion) lines.push(` champion: ${c.champion}`);
176
- if (c?.gaps?.length) for (const g of c.gaps) lines.push(` ⚠ ${g}`);
177
166
  lines.push("");
178
167
  }
179
168
  return {
@@ -188,8 +177,8 @@ export function createServer() {
188
177
  // ===========================================================================
189
178
  server.tool(
190
179
  "get_account",
191
- "Get the full account record for a person or company — the durable FACTS we've learned about them " +
192
- "(their atomic memory: budget, authority, pain, stack, plans), every attribute (claim) with its " +
180
+ "Get the full record for a person or company — the durable FACTS we've learned about them (their " +
181
+ "role, company, history, and anything else worth remembering), every attribute (claim) with its " +
193
182
  "confidence and freshness, plus what they actually SAID and did, ranked by how much it tells you. " +
194
183
  "Pass an email or entity UUID, and the intent you're working toward so the record is shaped for it.",
195
184
  {
@@ -243,23 +232,18 @@ export function createServer() {
243
232
  }
244
233
  lines.push("");
245
234
  }
246
- // The buying committee who else is at the account, their role, whether we've
247
- // engaged them, and how they relate. Same structure get_context surfaces.
235
+ // The people connected to this company and how they relate. Same structure get_context surfaces.
248
236
  if (rec.stakeholders?.length) {
249
237
  const c = rec.committee;
250
- lines.push(c?.company ? `BUYING COMMITTEE ${c.company}:` : "STAKEHOLDERS:");
238
+ lines.push(c?.company ? `PEOPLE AT ${c.company}:` : "RELATED PEOPLE:");
251
239
  for (const s of rec.stakeholders) {
252
240
  if (s.role === "company") continue;
253
241
  const bits = [];
254
- if (s.committee_role && s.committee_role !== "contact") bits.push(s.committee_role.replace(/_/g, " "));
255
242
  if (s.role) bits.push(s.role);
256
- bits.push(s.engaged ? "engaged" : "not yet engaged");
257
243
  if (s.confirmed === false) bits.push("mentioned, unconfirmed");
258
244
  const rel = s.relationships?.length ? ` — ${s.relationships.join("; ")}` : "";
259
- lines.push(` ${s.name ?? "—"} (${bits.join(", ")})${rel}`);
245
+ lines.push(` ${s.name ?? "—"}${bits.length ? ` (${bits.join(", ")})` : ""}${rel}`);
260
246
  }
261
- if (c?.champion) lines.push(` champion: ${c.champion}`);
262
- if (c?.gaps?.length) for (const g of c.gaps) lines.push(` ⚠ ${g}`);
263
247
  lines.push("");
264
248
  }
265
249
  const claims = Object.values(rec.claims ?? {});
@@ -421,22 +405,21 @@ export function createServer() {
421
405
  // ===========================================================================
422
406
  server.tool(
423
407
  "query",
424
- "Retrieve and summarise activity across many people. Three powers:\n" +
408
+ "Retrieve and summarise activity across many people and companies. Powers:\n" +
425
409
  " 1. return:'entities' groups results by person/company (one row per entity, ranked by " +
426
- "most-recent matching activity). Use for 'hottest leads', 'who replied this week', " +
427
- "'who's in the negotiation stage'.\n" +
428
- " 2. `without` subtracts entities — 'sent in 5d MINUS replied in 5d' = 'no-reply leads'. " +
429
- "'activity in 30d MINUS activity in 5d' = 'cooled leads'.\n" +
430
- " 3. rollups.by_value appears when scope.kind='state' — counts entities by current value " +
431
- "(use scope.property='stage' for funnel reports).\n" +
410
+ "most-recent matching activity). Use for 'who we talked to this week', 'who replied recently'.\n" +
411
+ " 2. `without` subtracts entities — 'sent in 5d MINUS replied in 5d' = 'wrote to, no reply back'. " +
412
+ "'activity in 30d MINUS activity in 5d' = 'people who've gone quiet'.\n" +
413
+ " 3. rollups.by_value appears when scope.kind='state' counts entities by a current value " +
414
+ "(scope.property='<state property>').\n" +
432
415
  " 4. Scheduled meetings/calls are events with property 'interaction.meeting_scheduled' and a " +
433
416
  "future-dated `when`. For 'what's booked today/this week', set property:'interaction.meeting_scheduled' " +
434
417
  "with from/to bounding the day or week (since_days only looks backward and can't reach them), and " +
435
418
  "order:'asc' to list soonest-first. Meeting rows render the absolute date and time.\n" +
436
- " 5. scope.facts:true + question searches the FACTS corpus (durable atomic facts about accounts) " +
437
- "instead of activity — cross-account semantic fact search like 'which accounts want off Clay' or " +
438
- "'who is hiring'. return:'entities' gives the single best-matching fact per account. (A single " +
439
- "account's facts already come back inline with get_account.)",
419
+ " 5. scope.facts:true + question searches the FACTS corpus (durable facts about people/companies) " +
420
+ "instead of activity — cross-record semantic fact search like 'who is hiring' or 'who mentioned " +
421
+ "switching tools'. return:'entities' gives the single best-matching fact per record. (A single " +
422
+ "record's facts already come back inline with get_account.)",
440
423
  {
441
424
  scope: z.object({
442
425
  kind: z.enum(["event", "state"]).optional(),
@@ -448,7 +431,7 @@ export function createServer() {
448
431
  to: z.string().optional().describe("ISO timestamp — only activity at/before this (absolute upper bound). Combine from+to for a window; future-dated for upcoming meetings"),
449
432
  order: z.enum(["asc", "desc"]).optional().describe("observed_at order (default desc, newest first). Use 'asc' for an upcoming-meeting schedule (soonest first)"),
450
433
  limit: z.number().optional().describe("max items (default 50, cap 200)"),
451
- facts: z.boolean().optional().describe("search the FACTS corpus (durable atomic facts about accounts) instead of activity. Needs `question` — a cross-account semantic fact search, e.g. 'which accounts want off Clay'. return:'entities' = the best matching fact per account."),
434
+ facts: z.boolean().optional().describe("search the FACTS corpus (durable facts about people/companies) instead of activity. Needs `question` — a cross-record semantic fact search, e.g. 'who is hiring'. return:'entities' = the best matching fact per record."),
452
435
  }).describe("Corpus filter"),
453
436
  without: z.object({
454
437
  kind: z.enum(["event", "state"]).optional(),
@@ -537,12 +520,12 @@ export function createServer() {
537
520
  // ===========================================================================
538
521
  server.tool(
539
522
  "verify",
540
- "Re-check a specific fact before you act on it — e.g. an email or a deal stage that looks stale " +
523
+ "Re-check a specific fact before you act on it — e.g. an email or a job title that looks stale " +
541
524
  "in get_context. Pass the person/company and the property name. Returns the fact re-derived from " +
542
525
  "current evidence, and tells you whether it is still unverified.",
543
526
  {
544
527
  focus: z.string().describe("Email, LinkedIn URL, entity UUID, or name"),
545
- property: z.string().describe("The fact to re-check — e.g. 'email', 'job_title', 'pipeline_stage'"),
528
+ property: z.string().describe("The fact to re-check — e.g. 'email', 'job_title', 'company'"),
546
529
  },
547
530
  async ({ focus, property }) => {
548
531
  const r = await post("/v2/verify", { focus, property });
@@ -761,14 +744,13 @@ export function createServer() {
761
744
  // ===========================================================================
762
745
  server.tool(
763
746
  "connect_integration",
764
- "Connect a key-based integration for the useran enrichment, CRM, or sequencer provider that " +
765
- "authenticates with an API key or token (e.g. Apollo, Prospeo, Instantly, HubSpot private-app " +
766
- "token, Pipedrive, Attio, Smartlead, HeyReach). Ask the user for the provider's API key, then " +
767
- "call this; it verifies the credentials before saving. Providers that use a browser sign-in " +
768
- "(OAuth, e.g. Gmail) can't be connected this way — for those, point the user to the Integrations " +
769
- "page. After connecting an enrichment provider, the account record starts filling in.",
747
+ "Connect a data source that feeds the knowledge base a provider that authenticates with an API " +
748
+ "key or token. Ask the user for the provider's key, then call this; it verifies the credentials " +
749
+ "before saving. Providers that use a browser sign-in (OAuth, e.g. Gmail or calendar) can't be " +
750
+ "connected this way for those, point the user to the Integrations page. Once connected, the " +
751
+ "source starts feeding the knowledge base.",
770
752
  {
771
- provider: z.string().describe("Provider name, lowercase — e.g. 'apollo', 'prospeo', 'instantly', 'hubspot', 'pipedrive', 'attio'."),
753
+ provider: z.string().describe("Provider name, lowercase."),
772
754
  credentials: z.record(z.string()).describe("The provider's credentials as key/value, e.g. { api_key: '...' } or { access_token: '...' }."),
773
755
  name: z.string().optional().describe("Optional label for the connection."),
774
756
  },