@agifyai/leadify-mcp 7.1.0 → 7.1.1

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.
@@ -249,27 +249,37 @@ async function mergeAndPut(organizationId, dry_run, patcher, validationHint) {
249
249
  // organization_id. The backend resolves the org server-side.
250
250
  export function registerDataRoomTools(server) {
251
251
  // ── get_data_room ──────────────────────────────────────────────────────
252
- server.tool("get_data_room", "Retrieve the organization's data room: company info (v2 schema) and all documents. " +
253
- "Use this as the entry point when you need to understand the company's context " +
254
- "before writing campaigns or messages. " +
255
- "By default, `personas[]` is NO LONGER included in the response (slimed down to " +
256
- "keep the LLM context small — the data room itself was 158k chars). " +
257
- "Pass `include_personas=true` to restore the legacy full payload (escape hatch). " +
258
- "For per-persona queries, prefer the `list_personas` tool.", {
252
+ server.tool("get_data_room", "Retrieve compact data-room metadata by default. Documents are paginated and their content " +
253
+ "is omitted unless explicitly requested for named document IDs.", {
259
254
  organization_id: z.string().describe(ORG_ID_DESC),
260
- include_personas: z
261
- .boolean()
262
- .optional()
263
- .default(false)
264
- .describe("If true, include the full `personas[]` array in the response (legacy " +
265
- "behaviour, ~150k chars). If false (default), personas are omitted use " +
266
- "`list_personas` for per-persona queries."),
267
- }, async ({ organization_id, include_personas }) => {
255
+ page: z.number().int().positive().optional().default(1).describe("Document page, starting at 1."),
256
+ limit: z.number().int().positive().max(50).optional().default(20).describe("Documents per page, max 50."),
257
+ category: z.string().optional().describe("Filter documents by category."),
258
+ search: z.string().optional().describe("Search document title and metadata."),
259
+ include_document_content_for_ids: z.array(z.string()).max(10).optional().describe("Explicit document IDs whose content is required."),
260
+ include_personas: z.boolean().optional().default(false).describe("Include personas only when explicitly required."),
261
+ }, async ({ organization_id, page, limit, category, search, include_document_content_for_ids, include_personas }) => {
268
262
  try {
269
- const data = await fetchDataRoom(organization_id, {
270
- includePersonas: include_personas === true,
263
+ const state = await fetchDataRoom(organization_id, { includePersonas: include_personas === true });
264
+ const room = state.dataRoom && typeof state.dataRoom === "object" ? state.dataRoom : state;
265
+ const documents = Array.isArray(room.documents) ? room.documents : [];
266
+ const term = search?.toLowerCase();
267
+ const filtered = documents.filter((document) => {
268
+ const value = document && typeof document === "object" ? document : {};
269
+ if (category && value.category !== category)
270
+ return false;
271
+ return !term || JSON.stringify({ title: value.title, name: value.name, category: value.category }).toLowerCase().includes(term);
271
272
  });
272
- return toolResult(data);
273
+ const start = (page - 1) * limit;
274
+ const requested = new Set(include_document_content_for_ids ?? []);
275
+ const items = filtered.slice(start, start + limit).map((document) => {
276
+ const value = document && typeof document === "object" ? document : {};
277
+ const compact = { id: value.id, title: value.title ?? value.name, category: value.category, createdAt: value.createdAt, updatedAt: value.updatedAt };
278
+ if (requested.has(String(value.id)))
279
+ compact.content = value.content;
280
+ return compact;
281
+ });
282
+ return toolResult({ company_info: room.companyInfo, documents: items, pagination: { page, limit, total: filtered.length, totalPages: Math.ceil(filtered.length / limit) } });
273
283
  }
274
284
  catch (error) {
275
285
  return handleToolError(error);
@@ -1,6 +1,24 @@
1
1
  import { z } from "zod";
2
2
  import { getClient } from "../client.js";
3
3
  import { toolResult, handleToolError } from "../types.js";
4
+ const DEFAULT_LEAD_FIELDS = ["firstName", "lastName", "email", "company", "jobTitle", "linkedin"];
5
+ function projectLead(lead, fields) {
6
+ const source = lead && typeof lead === "object" ? lead : {};
7
+ const data = source.data && typeof source.data === "object" ? source.data : {};
8
+ const projected = { id: source.id };
9
+ for (const field of fields)
10
+ if (data[field] !== undefined)
11
+ projected[field] = data[field];
12
+ return projected;
13
+ }
14
+ function compactLeadsResponse(data, fields, includeSchema) {
15
+ const source = data && typeof data === "object" ? data : {};
16
+ const leads = Array.isArray(source.leads) ? source.leads : [];
17
+ const result = { leads: leads.map((lead) => projectLead(lead, fields)), pagination: source.pagination };
18
+ if (includeSchema)
19
+ result.schema = source.schema;
20
+ return result;
21
+ }
4
22
  export function registerLeadTools(server) {
5
23
  // ── add_leads ──────────────────────────────────────────────────────────
6
24
  server.tool("add_leads", "Add one or more leads to a specific lead group in Leadify. Provide the group ID " +
@@ -42,11 +60,9 @@ export function registerLeadTools(server) {
42
60
  });
43
61
  // ── get_leads ──────────────────────────────────────────────────────────
44
62
  server.tool("get_leads", "Search and list leads from a Leadify lead group with optional filtering, search, " +
45
- "and pagination. Supports field-level filters with operators (equals, not_equals, " +
46
- "contains, not_contains, gte, lte, is_true, is_false, is_null, is_not_null), " +
47
- "full-text search, and saved views. Returns paginated results with lead data and " +
48
- "the group schema. Use this to browse, search, or filter leads. For retrieving " +
49
- "a single lead by its ID, use get_lead instead.", {
63
+ "full-text search, saved views, and compact field projections. Returns lead ID plus " +
64
+ "common identity fields by default. Pass `fields` for a workflow-specific projection " +
65
+ "and `include_schema=true` only when the schema is explicitly needed.", {
50
66
  group_id: z.string().describe("ID of the lead group to query."),
51
67
  view_id: z
52
68
  .string()
@@ -69,6 +85,8 @@ export function registerLeadTools(server) {
69
85
  .string()
70
86
  .optional()
71
87
  .describe("Full-text search term applied across all fields."),
88
+ fields: z.array(z.string()).min(1).max(30).optional().describe("Lead data fields to return. Defaults to a compact identity projection."),
89
+ include_schema: z.boolean().optional().default(false).describe("Include the complete group schema only when explicitly needed."),
72
90
  filters: z
73
91
  .record(z.record(z.string()))
74
92
  .optional()
@@ -76,7 +94,7 @@ export function registerLeadTools(server) {
76
94
  "operator to value. Operators: equals, not_equals, contains, not_contains, " +
77
95
  "gte, lte, is_true, is_false, is_null, is_not_null. " +
78
96
  'Example: {"industry": {"contains": "tech"}, "company": {"is_not_null": "true"}}'),
79
- }, async ({ group_id, view_id, page, limit, search, filters }) => {
97
+ }, async ({ group_id, view_id, page, limit, search, fields, include_schema, filters }) => {
80
98
  try {
81
99
  const params = new URLSearchParams();
82
100
  params.set("groupId", group_id);
@@ -96,29 +114,32 @@ export function registerLeadTools(server) {
96
114
  }
97
115
  }
98
116
  const data = await getClient().get("/get-leads", params);
99
- return toolResult(data);
117
+ return toolResult(compactLeadsResponse(data, fields ?? DEFAULT_LEAD_FIELDS, include_schema));
100
118
  }
101
119
  catch (error) {
102
120
  return handleToolError(error);
103
121
  }
104
122
  });
105
123
  // ── get_lead ───────────────────────────────────────────────────────────
106
- server.tool("get_lead", "Retrieve full details for a single lead by its ID, including all data fields, " +
107
- "group membership, creation/update timestamps, related leads (if relations are " +
108
- "configured), and the group schema. Use this when you already have a lead ID " +
109
- "and need its complete information. For searching or browsing multiple leads, " +
110
- "use get_leads instead. " +
111
- "Note: the group schema lives at `lead.group.schema` (no top-level `schema` " +
112
- "field). System-level `select` field options (qualification_level, " +
113
- "qualification_status, seniority, institution_type) are reconciled against the " +
114
- "canonical registry, so all legitimate values are listed.", {
124
+ server.tool("get_lead", "Retrieve a compact projection for a single lead. Defaults to identity fields only; " +
125
+ "request fields for workflow-specific data and include_schema only when needed.", {
115
126
  id: z.string().describe("The unique ID of the lead to retrieve."),
116
- }, async ({ id }) => {
127
+ fields: z.array(z.string()).min(1).max(30).optional().describe("Lead data fields to return."),
128
+ include_schema: z.boolean().optional().default(false).describe("Include the group schema only when explicitly needed."),
129
+ }, async ({ id, fields, include_schema }) => {
117
130
  try {
118
131
  const params = new URLSearchParams();
119
132
  params.set("id", id);
120
133
  const data = await getClient().get("/get-lead", params);
121
- return toolResult(data);
134
+ const source = data && typeof data === "object" ? data : {};
135
+ const lead = source.lead;
136
+ const result = { lead: projectLead(lead, fields ?? DEFAULT_LEAD_FIELDS) };
137
+ if (include_schema && lead && typeof lead === "object") {
138
+ const group = lead.group;
139
+ if (group && typeof group === "object")
140
+ result.schema = group.schema;
141
+ }
142
+ return toolResult(result);
122
143
  }
123
144
  catch (error) {
124
145
  return handleToolError(error);
@@ -124,6 +124,7 @@ export function registerLogTools(server) {
124
124
  .max(100)
125
125
  .optional()
126
126
  .describe("Results per page. Defaults to 50, max 100."),
127
+ include_message_for_log_ids: z.array(z.string()).max(10).optional().describe("Explicit log IDs whose complete message is required."),
127
128
  sort_by: z
128
129
  .enum(["createdAt", "level", "action", "campaignSlug"])
129
130
  .optional()
@@ -158,7 +159,17 @@ export function registerLogTools(server) {
158
159
  if (params.sort_order)
159
160
  qp.set("sort_order", params.sort_order);
160
161
  const data = await getClient().get("/get-lead-logs", qp);
161
- return toolResult(data);
162
+ const source = data && typeof data === "object" ? data : {};
163
+ const requested = new Set(params.include_message_for_log_ids ?? []);
164
+ const logs = Array.isArray(source.data) ? source.data : [];
165
+ const compact = logs.map((log) => {
166
+ const value = log && typeof log === "object" ? log : {};
167
+ const item = { id: value.id, leadId: value.leadId, campaignSlug: value.campaignSlug, campaignName: value.campaignName, action: value.action, level: value.level, createdAt: value.createdAt, errorCode: value.errorCode, retryCount: value.retryCount };
168
+ if (requested.has(String(value.id)))
169
+ item.message = value.message;
170
+ return item;
171
+ });
172
+ return toolResult({ logs: compact, pagination: source.pagination });
162
173
  }
163
174
  catch (error) {
164
175
  return handleToolError(error);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agifyai/leadify-mcp",
3
- "version": "7.1.0",
3
+ "version": "7.1.1",
4
4
  "description": "MCP server for Leadify lead management API",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",