@breaknorm_hu/mcp-server 0.1.2 → 0.1.4

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 +73 -30
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -61,13 +61,14 @@ function str(val) {
61
61
  var tools = [
62
62
  {
63
63
  name: "search_contacts",
64
- description: "Search the Breaknorm contact database. Returns contacts matching filters. Use get_search_filters first to discover available filter values. Results paginated (max 25/page). For page 2+, pass searchToken from page 1.",
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!",
65
65
  schema: z.object({
66
66
  q: z.string().optional().describe("Free text search (name, title, company)"),
67
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"),
68
69
  city: z.array(z.string()).optional().describe("Filter by city names"),
69
- seniority: z.array(z.string()).optional().describe("Filter by seniority: c_level, vp, director, manager, head, senior, founder, partner"),
70
- department: z.array(z.string()).optional().describe("Filter by department: sales, marketing, it, hr, finance, engineering, c_suite"),
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"),
71
72
  employee_min: z.number().int().optional().describe("Min company employees"),
72
73
  employee_max: z.number().int().optional().describe("Max company employees"),
73
74
  has_email: z.enum(["yes", "maybe", "no"]).optional().describe("Filter by email availability"),
@@ -79,6 +80,7 @@ var tools = [
79
80
  return client.get("/contacts/search", {
80
81
  q: str(args.q),
81
82
  industry: arrayParam(args.industry),
83
+ naicsCodes: arrayParam(args.naicsCodes),
82
84
  city: arrayParam(args.city),
83
85
  seniority: arrayParam(args.seniority),
84
86
  department: arrayParam(args.department),
@@ -93,10 +95,11 @@ var tools = [
93
95
  },
94
96
  {
95
97
  name: "search_companies",
96
- description: "Search companies in the Breaknorm database.",
98
+ description: "Search companies with filters. Use suggest_industry_codes FIRST for industry filtering. NEVER search one by one \u2014 use filters!",
97
99
  schema: z.object({
98
100
  q: z.string().optional().describe("Company name search"),
99
101
  industry: z.array(z.string()).optional(),
102
+ naicsCodes: z.array(z.string()).optional().describe("NAICS codes from suggest_industry_codes \u2014 BEST way to filter by industry"),
100
103
  city: z.array(z.string()).optional(),
101
104
  employee_min: z.number().int().optional(),
102
105
  employee_max: z.number().int().optional(),
@@ -110,6 +113,7 @@ var tools = [
110
113
  return client.get("/companies/search", {
111
114
  q: str(args.q),
112
115
  industry: arrayParam(args.industry),
116
+ naicsCodes: arrayParam(args.naicsCodes),
113
117
  city: arrayParam(args.city),
114
118
  employee_min: str(args.employee_min),
115
119
  employee_max: str(args.employee_max),
@@ -123,9 +127,47 @@ var tools = [
123
127
  },
124
128
  {
125
129
  name: "get_search_filters",
126
- description: "Get all available filter values with counts (industries, cities, seniorities, departments). Call this FIRST before searching.",
127
- schema: z.object({}),
128
- handler: async (client) => client.get("/search/filters")
130
+ description: "Get available filter values with counts. Call this FIRST before searching. Returns top values per category (max 50 each to keep response small).",
131
+ schema: z.object({
132
+ category: z.enum(["all", "industries", "cities", "counties", "seniorities", "departments", "companyTypes"]).default("all").optional().describe("Which filter category to fetch. Use 'all' for overview (top 20 each) or a specific category for full list (top 50).")
133
+ }),
134
+ handler: async (client, args) => {
135
+ const result = await client.get("/search/filters");
136
+ const data = result.data;
137
+ const cat = args.category || "all";
138
+ const limit = cat === "all" ? 20 : 50;
139
+ if (cat !== "all" && data[cat]) {
140
+ return { data: { [cat]: data[cat].slice(0, limit) } };
141
+ }
142
+ const trimmed = {};
143
+ for (const [key, values] of Object.entries(data)) {
144
+ if (Array.isArray(values)) {
145
+ trimmed[key] = values.slice(0, limit);
146
+ if (values.length > limit) {
147
+ trimmed[`${key}_total`] = values.length;
148
+ trimmed[`${key}_note`] = `Showing top ${limit} of ${values.length}. Use category="${key}" for more.`;
149
+ }
150
+ } else {
151
+ trimmed[key] = values;
152
+ }
153
+ }
154
+ return { data: trimmed };
155
+ }
156
+ },
157
+ {
158
+ name: "suggest_industry_codes",
159
+ description: "IMPORTANT: Use this BEFORE searching by industry. Converts a natural language industry description (e.g. 'marketing \xFCgyn\xF6ks\xE9gek', 'IT szolg\xE1ltat\xF3k') into NAICS codes that the search endpoints understand. Pass the returned codes as the naicsCodes parameter in search_contacts or search_companies. This is how industry filtering works \u2014 do NOT search by company name one by one!",
160
+ schema: z.object({
161
+ description: z.string().describe("Industry description in natural language, e.g. 'rekl\xE1m \xE9s m\xE9dia \xFCgyn\xF6ks\xE9gek' or 'IT consulting'"),
162
+ precision: z.enum(["strict", "balanced", "broad"]).default("balanced").optional().describe("strict = exact match, balanced = recommended, broad = wider results")
163
+ }),
164
+ handler: async (client, args) => {
165
+ const result = await client.post("/search/naics-suggest", {
166
+ description: args.description,
167
+ precision: args.precision || "balanced"
168
+ });
169
+ return result;
170
+ }
129
171
  },
130
172
  {
131
173
  name: "get_contact",
@@ -263,33 +305,34 @@ var SERVER_NAME = "breaknorm";
263
305
  var SERVER_VERSION = "0.1.0";
264
306
  var INSTRUCTIONS = `Breaknorm MCP Server \u2014 B2B contact database for the Hungarian market.
265
307
 
266
- IMPORTANT RULES:
267
- 1. Before ANY paid operation (reveal_contact, bulk_reveal_contacts, score_companies),
268
- ALWAYS call estimate_cost first and present the cost to the user for approval.
269
- Never execute paid operations without explicit user confirmation.
308
+ CRITICAL RULES \u2014 READ CAREFULLY:
309
+
310
+ 1. NEVER search by company name one by one! ALWAYS use filters to get bulk results.
311
+ Wrong: search_contacts(q: "Company A"), search_contacts(q: "Company B"), ...
312
+ Right: suggest_industry_codes("marketing \xFCgyn\xF6ks\xE9gek") \u2192 search_contacts(naicsCodes: [...], seniority: ["c_level"])
270
313
 
271
- 2. Recommended workflow for building a lead list:
272
- a. get_credits \u2014 check current balance
273
- b. get_search_filters \u2014 discover available filter values
274
- c. search_companies \u2014 find target companies matching criteria
275
- d. search_contacts \u2014 find people at those companies
276
- e. estimate_cost \u2014 calculate total cost for reveals
277
- f. [Wait for user approval of the cost]
278
- g. bulk_reveal_contacts \u2014 get email/phone data
279
- h. create_list + add_contacts_to_list \u2014 organize results
280
- i. export_list \u2014 export as CSV if needed
314
+ 2. INDUSTRY SEARCH WORKFLOW \u2014 this is how it works:
315
+ a. suggest_industry_codes("rekl\xE1m \xE9s m\xE9dia \xFCgyn\xF6ks\xE9gek") \u2192 returns NAICS codes
316
+ b. search_companies(naicsCodes: [codes from step a], employee_min: 5) \u2192 returns ALL matching companies at once
317
+ c. search_contacts(naicsCodes: [same codes], seniority: ["c_level", "director"]) \u2192 returns ALL decision-makers at once
318
+ This gives you hundreds of results in 2-3 API calls instead of searching one by one!
281
319
 
282
- 3. Credit costs:
283
- - Email reveal: 1 credit per contact
284
- - Phone reveal: 10 credits per contact
285
- - Both (email + phone): 11 credits per contact
286
- - ICP company scoring: 1 credit per company
320
+ 3. Before ANY paid operation (reveal, scoring), call estimate_cost first and get user approval.
287
321
 
288
- 4. Search results are paginated (max 25 per page). For page 2+,
289
- you must pass the searchToken from the page 1 response.
322
+ 4. Recommended workflow for building a lead list:
323
+ a. get_credits \u2014 check balance
324
+ b. suggest_industry_codes \u2014 convert industry description to NAICS codes
325
+ c. search_companies \u2014 find companies with naicsCodes + size + location filters
326
+ d. search_contacts \u2014 find decision-makers with naicsCodes + seniority + department filters
327
+ e. estimate_cost \u2014 calculate cost
328
+ f. [Wait for user approval]
329
+ g. bulk_reveal_contacts \u2014 get emails (up to 1000 at once!)
330
+ h. create_list + add_contacts_to_list \u2014 organize
331
+ i. export_list \u2014 CSV export
290
332
 
291
- 5. Bulk operations (reveal, ICP scoring) are async. They return a jobId.
292
- Use check_job_status to poll for completion.`;
333
+ 5. Credit costs: email=1, phone=10, both=11, ICP scoring=1 per company
334
+ 6. Search is FREE and paginated (25/page). Use searchToken for page 2+.
335
+ 7. Bulk operations are async \u2014 poll with check_job_status.`;
293
336
  async function main() {
294
337
  const apiKey = process.env.BREAKNORM_API_KEY;
295
338
  if (!apiKey) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@breaknorm_hu/mcp-server",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "Breaknorm MCP Server — AI agent interface for B2B contact database",
5
5
  "type": "module",
6
6
  "bin": {