@breaknorm_hu/mcp-server 0.1.6 → 0.2.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.
Files changed (2) hide show
  1. package/dist/index.js +66 -69
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -60,77 +60,75 @@ function str(val) {
60
60
  }
61
61
  var tools = [
62
62
  {
63
- name: "search_contacts",
64
- description: "Search contacts with filters. Use suggest_industry_codes FIRST to convert industry descriptions to NAICS codes, then pass them here. NEVER search by company name one by one \u2014 always use filters to get bulk results!",
63
+ name: "search_leads",
64
+ description: "Search for leads (contacts + company data in one result). This is THE main search tool. Returns contacts with their company info. Use suggest_industry_codes FIRST to get NAICS codes, then pass them here. You can request up to 500 results at once (no need to paginate for small queries). NEVER search by company name one by one!",
65
65
  schema: z.object({
66
- q: z.string().optional().describe("Free text search (name, title, company)"),
67
- industry: z.array(z.string()).optional().describe("Filter by industry names"),
68
- naicsCodes: z.array(z.string()).optional().describe("NAICS codes from suggest_industry_codes \u2014 THIS IS THE BEST WAY TO FILTER BY INDUSTRY"),
66
+ q: z.string().optional().describe("Free text search (name, title, company name)"),
67
+ naicsCodes: z.array(z.string()).optional().describe("NAICS codes from suggest_industry_codes \u2014 THE way to filter by industry"),
68
+ industry: z.array(z.string()).optional().describe("Industry name filter (use naicsCodes instead when possible)"),
69
69
  city: z.array(z.string()).optional().describe("Filter by city names"),
70
- seniority: z.array(z.string()).optional().describe("c_level, vp, director, manager, head, senior, founder, partner"),
71
- department: z.array(z.string()).optional().describe("sales, marketing, it, hr, finance, engineering, c_suite"),
70
+ county: z.array(z.string()).optional().describe("Filter by county (megye)"),
71
+ country: z.string().optional().describe("2-letter country code, default: HU"),
72
+ seniority: z.array(z.string()).optional().describe("c_level, vp, director, manager, head, senior, founder, partner, managing_director"),
73
+ department: z.array(z.string()).optional().describe("sales, marketing, it, hr, finance, engineering, c_suite, operations, legal, product"),
74
+ title: z.string().optional().describe("Job title keyword (e.g. '\xFCgyvezet\u0151', 'CEO', 'igazgat\xF3')"),
72
75
  employee_min: z.number().int().optional().describe("Min company employees"),
73
76
  employee_max: z.number().int().optional().describe("Max company employees"),
74
77
  revenue_min: z.number().optional().describe("Min annual revenue in HUF"),
75
78
  revenue_max: z.number().optional().describe("Max annual revenue in HUF"),
76
79
  has_email: z.enum(["yes", "maybe", "no"]).optional().describe("Filter by email availability"),
77
- page: z.number().int().default(1).describe("Page number"),
78
- limit: z.number().int().max(25).default(25).describe("Results per page (max 25)"),
80
+ has_phone: z.enum(["yes", "maybe", "no"]).optional().describe("Filter by phone availability"),
81
+ is_revealed: z.boolean().optional().describe("Only show already-revealed contacts"),
82
+ icp_score_min: z.number().min(0).max(100).optional().describe("Min ICP score (0-100)"),
83
+ limit: z.number().int().min(1).max(500).default(25).describe("Results to return (max 500, default 25). Use higher values to get more results at once!"),
84
+ page: z.number().int().default(1).describe("Page number (default 1)"),
79
85
  searchToken: z.string().optional().describe("Required for page 2+, from page 1 response")
80
86
  }),
81
87
  handler: async (client, args) => {
82
- return client.get("/contacts/search", {
88
+ const result = await client.get("/leads/search", {
83
89
  q: str(args.q),
84
- industry: arrayParam(args.industry),
85
90
  naicsCodes: arrayParam(args.naicsCodes),
91
+ industry: arrayParam(args.industry),
86
92
  city: arrayParam(args.city),
93
+ county: arrayParam(args.county),
94
+ country: str(args.country),
87
95
  seniority: arrayParam(args.seniority),
88
96
  department: arrayParam(args.department),
97
+ title: str(args.title),
89
98
  employee_min: str(args.employee_min),
90
99
  employee_max: str(args.employee_max),
91
100
  revenue_min: str(args.revenue_min),
92
101
  revenue_max: str(args.revenue_max),
93
102
  has_email: str(args.has_email),
94
- page: str(args.page),
103
+ has_phone: str(args.has_phone),
104
+ is_revealed: str(args.is_revealed),
105
+ icp_score_min: str(args.icp_score_min),
95
106
  limit: str(args.limit),
96
- searchToken: str(args.searchToken)
97
- });
98
- }
99
- },
100
- {
101
- name: "search_companies",
102
- description: "Search companies with filters. Use suggest_industry_codes FIRST for industry filtering. NEVER search one by one \u2014 use filters!",
103
- schema: z.object({
104
- q: z.string().optional().describe("Company name search"),
105
- industry: z.array(z.string()).optional(),
106
- naicsCodes: z.array(z.string()).optional().describe("NAICS codes from suggest_industry_codes \u2014 BEST way to filter by industry"),
107
- city: z.array(z.string()).optional(),
108
- employee_min: z.number().int().optional(),
109
- employee_max: z.number().int().optional(),
110
- revenue_min: z.number().optional().describe("Min annual revenue in HUF"),
111
- revenue_max: z.number().optional().describe("Max annual revenue in HUF"),
112
- has_website: z.boolean().optional(),
113
- page: z.number().int().default(1),
114
- limit: z.number().int().max(25).default(25),
115
- sort: z.enum(["relevance", "name", "employee_count", "revenue", "icp_score"]).default("relevance"),
116
- searchToken: z.string().optional()
117
- }),
118
- handler: async (client, args) => {
119
- return client.get("/companies/search", {
120
- q: str(args.q),
121
- industry: arrayParam(args.industry),
122
- naicsCodes: arrayParam(args.naicsCodes),
123
- city: arrayParam(args.city),
124
- employee_min: str(args.employee_min),
125
- employee_max: str(args.employee_max),
126
- revenue_min: str(args.revenue_min),
127
- revenue_max: str(args.revenue_max),
128
- has_website: str(args.has_website),
129
107
  page: str(args.page),
130
- limit: str(args.limit),
131
- sort: str(args.sort),
132
108
  searchToken: str(args.searchToken)
133
109
  });
110
+ const r = result;
111
+ const leads = (r.data || []).map((lead) => ({
112
+ id: lead.id,
113
+ name: `${lead.firstName} ${lead.lastName}`,
114
+ title: lead.title,
115
+ seniority: lead.seniority,
116
+ email: lead.revealedEmail || (lead.hasEmail ? "available" : null),
117
+ phone: lead.revealedPhone || (lead.hasPhone ? "available" : null),
118
+ company: lead.companyName,
119
+ companyId: lead.companyId,
120
+ industry: lead.industry,
121
+ city: lead.city,
122
+ employees: lead.employeeCount,
123
+ revenue: lead.annualRevenue
124
+ }));
125
+ return {
126
+ leads,
127
+ total: r.pagination?.total,
128
+ page: r.pagination?.page,
129
+ hasMore: r.pagination?.hasMore,
130
+ searchToken: r.pagination?.searchToken
131
+ };
134
132
  }
135
133
  },
136
134
  {
@@ -312,34 +310,33 @@ var SERVER_NAME = "breaknorm";
312
310
  var SERVER_VERSION = "0.1.0";
313
311
  var INSTRUCTIONS = `Breaknorm MCP Server \u2014 B2B contact database for the Hungarian market.
314
312
 
315
- CRITICAL RULES \u2014 READ CAREFULLY:
313
+ CRITICAL RULES:
316
314
 
317
- 1. NEVER search by company name one by one! ALWAYS use filters to get bulk results.
318
- Wrong: search_contacts(q: "Company A"), search_contacts(q: "Company B"), ...
319
- Right: suggest_industry_codes("marketing \xFCgyn\xF6ks\xE9gek") \u2192 search_contacts(naicsCodes: [...], seniority: ["c_level"])
315
+ 1. USE search_leads FOR EVERYTHING \u2014 it returns contacts WITH company data in one call.
316
+ You can request up to 500 results at once (limit: 500). No need to paginate for most queries!
320
317
 
321
- 2. INDUSTRY SEARCH WORKFLOW \u2014 this is how it works:
322
- a. suggest_industry_codes("rekl\xE1m \xE9s m\xE9dia \xFCgyn\xF6ks\xE9gek") \u2192 returns NAICS codes
323
- b. search_companies(naicsCodes: [codes from step a], employee_min: 5) \u2192 returns ALL matching companies at once
324
- c. search_contacts(naicsCodes: [same codes], seniority: ["c_level", "director"]) \u2192 returns ALL decision-makers at once
325
- This gives you hundreds of results in 2-3 API calls instead of searching one by one!
318
+ 2. NEVER search by company name one by one! Use filters:
319
+ Wrong: search_leads(q: "Company A"), search_leads(q: "Company B"), ...
320
+ Right: suggest_industry_codes("marketing") \u2192 search_leads(naicsCodes: [...], seniority: ["c_level"], limit: 200)
326
321
 
327
- 3. Before ANY paid operation (reveal, scoring), call estimate_cost first and get user approval.
322
+ 3. INDUSTRY SEARCH \u2014 always use NAICS codes:
323
+ a. suggest_industry_codes("rekl\xE1m \xE9s m\xE9dia \xFCgyn\xF6ks\xE9gek") \u2192 get NAICS codes ONCE
324
+ b. search_leads(naicsCodes: [codes], employee_min: 5, seniority: ["c_level"], limit: 200)
325
+ c. If you need more pages, reuse the SAME naicsCodes \u2014 NEVER call suggest_industry_codes again!
328
326
 
329
- 4. Recommended workflow for building a lead list:
327
+ 4. WORKFLOW for building a lead list:
330
328
  a. get_credits \u2014 check balance
331
- b. suggest_industry_codes \u2014 convert industry description to NAICS codes
332
- c. search_companies \u2014 find companies with naicsCodes + size + location filters
333
- d. search_contacts \u2014 find decision-makers with naicsCodes + seniority + department filters
334
- e. estimate_cost \u2014 calculate cost
335
- f. [Wait for user approval]
336
- g. bulk_reveal_contacts \u2014 get emails (up to 1000 at once!)
337
- h. create_list + add_contacts_to_list \u2014 organize
338
- i. export_list \u2014 CSV export
329
+ b. suggest_industry_codes \u2014 get NAICS codes (ONCE!)
330
+ c. search_leads \u2014 find leads with all filters, up to 500 at once
331
+ d. estimate_cost \u2014 calculate reveal cost
332
+ e. [Wait for user approval of the cost]
333
+ f. bulk_reveal_contacts \u2014 get emails (up to 1000 at once)
334
+ g. create_list + add_contacts_to_list \u2014 organize
335
+ h. export_list \u2014 CSV export
339
336
 
340
- 5. Credit costs: email=1, phone=10, both=11, ICP scoring=1 per company
341
- 6. Search is FREE and paginated (25/page). Use searchToken for page 2+.
342
- 7. Bulk operations are async \u2014 poll with check_job_status.`;
337
+ 5. Before ANY paid operation (reveal, scoring), call estimate_cost and get user approval.
338
+ 6. Credit costs: email=1, phone=10, both=11, ICP scoring=1 per company.
339
+ 7. Search is FREE. Bulk operations are async \u2014 poll with check_job_status.`;
343
340
  async function main() {
344
341
  const apiKey = process.env.BREAKNORM_API_KEY;
345
342
  if (!apiKey) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@breaknorm_hu/mcp-server",
3
- "version": "0.1.6",
3
+ "version": "0.2.1",
4
4
  "description": "Breaknorm MCP Server — AI agent interface for B2B contact database",
5
5
  "type": "module",
6
6
  "bin": {