@breaknorm_hu/mcp-server 0.1.0 → 0.1.2
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.
- package/dist/index.js +94 -184
- package/package.json +3 -2
package/dist/index.js
CHANGED
|
@@ -10,7 +10,7 @@ var BreakNormClient = class {
|
|
|
10
10
|
apiKey;
|
|
11
11
|
constructor(apiKey, baseUrl) {
|
|
12
12
|
this.apiKey = apiKey;
|
|
13
|
-
this.baseUrl = (baseUrl || "https://breaknorm.
|
|
13
|
+
this.baseUrl = (baseUrl || "https://breaknorm.hu").replace(/\/$/, "");
|
|
14
14
|
}
|
|
15
15
|
async request(method, path, options) {
|
|
16
16
|
const url = new URL(`/api/v1${path}`, this.baseUrl);
|
|
@@ -49,6 +49,7 @@ var BreakNormClient = class {
|
|
|
49
49
|
};
|
|
50
50
|
|
|
51
51
|
// src/tools.ts
|
|
52
|
+
import { z } from "zod";
|
|
52
53
|
function arrayParam(val) {
|
|
53
54
|
if (Array.isArray(val) && val.length > 0) return val.join(",");
|
|
54
55
|
return void 0;
|
|
@@ -58,55 +59,32 @@ function str(val) {
|
|
|
58
59
|
return String(val);
|
|
59
60
|
}
|
|
60
61
|
var tools = [
|
|
61
|
-
// ── Search ─────────────────────────────────
|
|
62
62
|
{
|
|
63
63
|
name: "search_contacts",
|
|
64
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.",
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
department: {
|
|
79
|
-
type: "array",
|
|
80
|
-
items: { type: "string", enum: ["sales", "marketing", "it", "hr", "finance", "operations", "legal", "engineering", "product", "customer_success", "c_suite", "design", "other"] }
|
|
81
|
-
},
|
|
82
|
-
employee_min: { type: "integer", description: "Minimum company employee count" },
|
|
83
|
-
employee_max: { type: "integer", description: "Maximum company employee count" },
|
|
84
|
-
revenue_min: { type: "number", description: "Minimum annual revenue (HUF)" },
|
|
85
|
-
revenue_max: { type: "number", description: "Maximum annual revenue (HUF)" },
|
|
86
|
-
has_email: { type: "string", enum: ["yes", "maybe", "no"], description: "Filter by email availability" },
|
|
87
|
-
has_phone: { type: "string", enum: ["yes", "maybe", "no"] },
|
|
88
|
-
title: { type: "string", description: "Job title keyword search" },
|
|
89
|
-
page: { type: "integer", default: 1 },
|
|
90
|
-
limit: { type: "integer", default: 25, maximum: 25 },
|
|
91
|
-
searchToken: { type: "string", description: "Required for page 2+. From page 1 response." }
|
|
92
|
-
}
|
|
93
|
-
},
|
|
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
|
+
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"),
|
|
71
|
+
employee_min: z.number().int().optional().describe("Min company employees"),
|
|
72
|
+
employee_max: z.number().int().optional().describe("Max company employees"),
|
|
73
|
+
has_email: z.enum(["yes", "maybe", "no"]).optional().describe("Filter by email availability"),
|
|
74
|
+
page: z.number().int().default(1).describe("Page number"),
|
|
75
|
+
limit: z.number().int().max(25).default(25).describe("Results per page (max 25)"),
|
|
76
|
+
searchToken: z.string().optional().describe("Required for page 2+, from page 1 response")
|
|
77
|
+
}),
|
|
94
78
|
handler: async (client, args) => {
|
|
95
79
|
return client.get("/contacts/search", {
|
|
96
80
|
q: str(args.q),
|
|
97
81
|
industry: arrayParam(args.industry),
|
|
98
82
|
city: arrayParam(args.city),
|
|
99
|
-
county: arrayParam(args.county),
|
|
100
|
-
country: str(args.country),
|
|
101
83
|
seniority: arrayParam(args.seniority),
|
|
102
84
|
department: arrayParam(args.department),
|
|
103
85
|
employee_min: str(args.employee_min),
|
|
104
86
|
employee_max: str(args.employee_max),
|
|
105
|
-
revenue_min: str(args.revenue_min),
|
|
106
|
-
revenue_max: str(args.revenue_max),
|
|
107
87
|
has_email: str(args.has_email),
|
|
108
|
-
has_phone: str(args.has_phone),
|
|
109
|
-
title: str(args.title),
|
|
110
88
|
page: str(args.page),
|
|
111
89
|
limit: str(args.limit),
|
|
112
90
|
searchToken: str(args.searchToken)
|
|
@@ -115,37 +93,26 @@ var tools = [
|
|
|
115
93
|
},
|
|
116
94
|
{
|
|
117
95
|
name: "search_companies",
|
|
118
|
-
description: "Search companies in the Breaknorm database.
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
has_website: { type: "boolean" },
|
|
132
|
-
page: { type: "integer", default: 1 },
|
|
133
|
-
limit: { type: "integer", default: 25, maximum: 25 },
|
|
134
|
-
sort: { type: "string", enum: ["relevance", "name", "employee_count", "revenue", "icp_score"], default: "relevance" },
|
|
135
|
-
searchToken: { type: "string" }
|
|
136
|
-
}
|
|
137
|
-
},
|
|
96
|
+
description: "Search companies in the Breaknorm database.",
|
|
97
|
+
schema: z.object({
|
|
98
|
+
q: z.string().optional().describe("Company name search"),
|
|
99
|
+
industry: z.array(z.string()).optional(),
|
|
100
|
+
city: z.array(z.string()).optional(),
|
|
101
|
+
employee_min: z.number().int().optional(),
|
|
102
|
+
employee_max: z.number().int().optional(),
|
|
103
|
+
has_website: z.boolean().optional(),
|
|
104
|
+
page: z.number().int().default(1),
|
|
105
|
+
limit: z.number().int().max(25).default(25),
|
|
106
|
+
sort: z.enum(["relevance", "name", "employee_count", "revenue", "icp_score"]).default("relevance"),
|
|
107
|
+
searchToken: z.string().optional()
|
|
108
|
+
}),
|
|
138
109
|
handler: async (client, args) => {
|
|
139
110
|
return client.get("/companies/search", {
|
|
140
111
|
q: str(args.q),
|
|
141
112
|
industry: arrayParam(args.industry),
|
|
142
113
|
city: arrayParam(args.city),
|
|
143
|
-
county: arrayParam(args.county),
|
|
144
|
-
country: str(args.country),
|
|
145
114
|
employee_min: str(args.employee_min),
|
|
146
115
|
employee_max: str(args.employee_max),
|
|
147
|
-
revenue_min: str(args.revenue_min),
|
|
148
|
-
revenue_max: str(args.revenue_max),
|
|
149
116
|
has_website: str(args.has_website),
|
|
150
117
|
page: str(args.page),
|
|
151
118
|
limit: str(args.limit),
|
|
@@ -156,172 +123,119 @@ var tools = [
|
|
|
156
123
|
},
|
|
157
124
|
{
|
|
158
125
|
name: "get_search_filters",
|
|
159
|
-
description: "Get all available filter values with counts (industries, cities,
|
|
160
|
-
|
|
126
|
+
description: "Get all available filter values with counts (industries, cities, seniorities, departments). Call this FIRST before searching.",
|
|
127
|
+
schema: z.object({}),
|
|
161
128
|
handler: async (client) => client.get("/search/filters")
|
|
162
129
|
},
|
|
163
|
-
// ── Contacts ───────────────────────────────
|
|
164
130
|
{
|
|
165
131
|
name: "get_contact",
|
|
166
132
|
description: "Get details of a single contact (public fields, no revealed data).",
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
required: ["contactId"]
|
|
171
|
-
},
|
|
133
|
+
schema: z.object({
|
|
134
|
+
contactId: z.string().describe("Contact UUID")
|
|
135
|
+
}),
|
|
172
136
|
handler: async (client, args) => client.get(`/contacts/${args.contactId}`)
|
|
173
137
|
},
|
|
174
|
-
// ── Cost Estimation ────────────────────────
|
|
175
138
|
{
|
|
176
139
|
name: "estimate_cost",
|
|
177
|
-
description: "IMPORTANT: Call
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
contactIds: { type: "array", items: { type: "string" }, description: "For reveal operations" },
|
|
188
|
-
revealType: { type: "string", enum: ["email", "phone", "all"], description: "For reveal: what to reveal" },
|
|
189
|
-
companyIds: { type: "array", items: { type: "string" }, description: "For icp_score operations" },
|
|
190
|
-
icpProfileId: { type: "string", description: "For icp_score: specific profile ID (optional)" }
|
|
191
|
-
},
|
|
192
|
-
required: ["type"]
|
|
193
|
-
},
|
|
194
|
-
description: "List of operations to estimate"
|
|
195
|
-
}
|
|
196
|
-
},
|
|
197
|
-
required: ["operations"]
|
|
198
|
-
},
|
|
140
|
+
description: "IMPORTANT: Call BEFORE any paid operation (reveal, ICP scoring). Returns exact credit cost, balance, and whether you can afford it. Present cost to user for approval.",
|
|
141
|
+
schema: z.object({
|
|
142
|
+
operations: z.array(z.object({
|
|
143
|
+
type: z.enum(["reveal", "icp_score"]),
|
|
144
|
+
contactIds: z.array(z.string()).optional().describe("For reveal operations"),
|
|
145
|
+
revealType: z.enum(["email", "phone", "all"]).optional().describe("For reveal: what to reveal"),
|
|
146
|
+
companyIds: z.array(z.string()).optional().describe("For icp_score operations"),
|
|
147
|
+
icpProfileId: z.string().optional().describe("For icp_score: specific profile")
|
|
148
|
+
})).describe("List of operations to estimate")
|
|
149
|
+
}),
|
|
199
150
|
handler: async (client, args) => client.post("/estimate", { operations: args.operations })
|
|
200
151
|
},
|
|
201
|
-
// ── Reveal ─────────────────────────────────
|
|
202
152
|
{
|
|
203
153
|
name: "reveal_contact",
|
|
204
|
-
description: "Reveal email
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
type: { type: "string", enum: ["email", "phone", "all"], description: "What to reveal" }
|
|
210
|
-
},
|
|
211
|
-
required: ["contactId", "type"]
|
|
212
|
-
},
|
|
154
|
+
description: "Reveal email/phone for a single contact. Costs credits (email=1, phone=10, all=11). ALWAYS call estimate_cost first.",
|
|
155
|
+
schema: z.object({
|
|
156
|
+
contactId: z.string().describe("Contact UUID"),
|
|
157
|
+
type: z.enum(["email", "phone", "all"]).describe("What to reveal")
|
|
158
|
+
}),
|
|
213
159
|
handler: async (client, args) => client.post(`/contacts/${args.contactId}/reveal`, { type: args.type })
|
|
214
160
|
},
|
|
215
161
|
{
|
|
216
162
|
name: "bulk_reveal_contacts",
|
|
217
|
-
description: "Reveal data for multiple contacts at once
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
idempotencyKey: { type: "string", description: "UUID to prevent duplicate charges on retry" }
|
|
224
|
-
},
|
|
225
|
-
required: ["contactIds", "type"]
|
|
226
|
-
},
|
|
163
|
+
description: "Reveal data for multiple contacts at once (async job). Returns jobId. ALWAYS call estimate_cost first.",
|
|
164
|
+
schema: z.object({
|
|
165
|
+
contactIds: z.array(z.string()).describe("Contact UUIDs (max 1000)"),
|
|
166
|
+
type: z.enum(["email", "phone", "all"]),
|
|
167
|
+
idempotencyKey: z.string().optional().describe("UUID to prevent double charges on retry")
|
|
168
|
+
}),
|
|
227
169
|
handler: async (client, args) => client.post("/contacts/bulk-reveal", {
|
|
228
170
|
contactIds: args.contactIds,
|
|
229
171
|
type: args.type,
|
|
230
172
|
idempotencyKey: args.idempotencyKey
|
|
231
173
|
})
|
|
232
174
|
},
|
|
233
|
-
// ── Jobs ───────────────────────────────────
|
|
234
175
|
{
|
|
235
176
|
name: "check_job_status",
|
|
236
|
-
description: "Check
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
required: ["jobId"]
|
|
241
|
-
},
|
|
177
|
+
description: "Check async job status (bulk reveal, export, ICP scoring). Returns progress and status.",
|
|
178
|
+
schema: z.object({
|
|
179
|
+
jobId: z.string().describe("Job UUID")
|
|
180
|
+
}),
|
|
242
181
|
handler: async (client, args) => client.get(`/jobs/${args.jobId}`)
|
|
243
182
|
},
|
|
244
|
-
// ── Credits ────────────────────────────────
|
|
245
183
|
{
|
|
246
184
|
name: "get_credits",
|
|
247
185
|
description: "Check current credit balance and subscription tier.",
|
|
248
|
-
|
|
186
|
+
schema: z.object({}),
|
|
249
187
|
handler: async (client) => client.get("/credits")
|
|
250
188
|
},
|
|
251
|
-
// ── Lists ──────────────────────────────────
|
|
252
189
|
{
|
|
253
190
|
name: "list_saved_lists",
|
|
254
191
|
description: "List all saved contact lists.",
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
sort: { type: "string", enum: ["name", "created_at", "contact_count"], default: "created_at" }
|
|
261
|
-
}
|
|
262
|
-
},
|
|
192
|
+
schema: z.object({
|
|
193
|
+
page: z.number().int().default(1).optional(),
|
|
194
|
+
limit: z.number().int().max(100).default(20).optional(),
|
|
195
|
+
sort: z.enum(["name", "created_at", "contact_count"]).default("created_at").optional()
|
|
196
|
+
}),
|
|
263
197
|
handler: async (client, args) => client.get("/lists", { page: str(args.page), limit: str(args.limit), sort: str(args.sort) })
|
|
264
198
|
},
|
|
265
199
|
{
|
|
266
200
|
name: "create_list",
|
|
267
201
|
description: "Create a new saved contact list.",
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
description: { type: "string", description: "Optional description (max 1000 chars)" }
|
|
273
|
-
},
|
|
274
|
-
required: ["name"]
|
|
275
|
-
},
|
|
202
|
+
schema: z.object({
|
|
203
|
+
name: z.string().describe("List name (1-200 chars)"),
|
|
204
|
+
description: z.string().optional().describe("Optional description")
|
|
205
|
+
}),
|
|
276
206
|
handler: async (client, args) => client.post("/lists", { name: args.name, description: args.description })
|
|
277
207
|
},
|
|
278
208
|
{
|
|
279
209
|
name: "add_contacts_to_list",
|
|
280
|
-
description: "Add contacts to
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
contactIds: { type: "array", items: { type: "string" }, description: "Contact UUIDs (max 500)" }
|
|
286
|
-
},
|
|
287
|
-
required: ["listId", "contactIds"]
|
|
288
|
-
},
|
|
210
|
+
description: "Add contacts to a list. Duplicates auto-skipped.",
|
|
211
|
+
schema: z.object({
|
|
212
|
+
listId: z.string().describe("List UUID"),
|
|
213
|
+
contactIds: z.array(z.string()).describe("Contact UUIDs (max 500)")
|
|
214
|
+
}),
|
|
289
215
|
handler: async (client, args) => client.post(`/lists/${args.listId}/contacts`, { contactIds: args.contactIds })
|
|
290
216
|
},
|
|
291
217
|
{
|
|
292
218
|
name: "export_list",
|
|
293
|
-
description: "Export a
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
listId: { type: "string", description: "List UUID" }
|
|
298
|
-
},
|
|
299
|
-
required: ["listId"]
|
|
300
|
-
},
|
|
219
|
+
description: "Export a list as CSV (async job). Use check_job_status to poll.",
|
|
220
|
+
schema: z.object({
|
|
221
|
+
listId: z.string().describe("List UUID")
|
|
222
|
+
}),
|
|
301
223
|
handler: async (client, args) => client.post(`/lists/${args.listId}/export`)
|
|
302
224
|
},
|
|
303
|
-
// ── ICP ────────────────────────────────────
|
|
304
225
|
{
|
|
305
226
|
name: "list_icp_profiles",
|
|
306
|
-
description: "List
|
|
307
|
-
|
|
227
|
+
description: "List ICP (Ideal Customer Profile) definitions.",
|
|
228
|
+
schema: z.object({}),
|
|
308
229
|
handler: async (client) => client.get("/icp-profiles")
|
|
309
230
|
},
|
|
310
231
|
{
|
|
311
232
|
name: "create_icp_profile",
|
|
312
|
-
description: "Create an ICP profile from
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
type: "string",
|
|
319
|
-
description: "Detailed ICP description in natural language (1-2000 chars). Example: 'IT \xE9s SaaS c\xE9gek, 10-200 alkalmazott, Budapest, \xE9ves bev\xE9tel 100M-1B HUF, akt\xEDv LinkedIn jelenl\xE9t'"
|
|
320
|
-
},
|
|
321
|
-
isDefault: { type: "boolean", description: "Set as default profile for scoring", default: false }
|
|
322
|
-
},
|
|
323
|
-
required: ["name", "description"]
|
|
324
|
-
},
|
|
233
|
+
description: "Create an ICP profile from natural language description for AI scoring.",
|
|
234
|
+
schema: z.object({
|
|
235
|
+
name: z.string().describe("Profile name"),
|
|
236
|
+
description: z.string().describe("Detailed ICP description (1-2000 chars)"),
|
|
237
|
+
isDefault: z.boolean().default(false).optional()
|
|
238
|
+
}),
|
|
325
239
|
handler: async (client, args) => client.post("/icp-profiles", {
|
|
326
240
|
name: args.name,
|
|
327
241
|
description: args.description,
|
|
@@ -330,16 +244,12 @@ var tools = [
|
|
|
330
244
|
},
|
|
331
245
|
{
|
|
332
246
|
name: "score_companies",
|
|
333
|
-
description: "Score companies against
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
idempotencyKey: { type: "string", description: "UUID to prevent duplicate charges" }
|
|
340
|
-
},
|
|
341
|
-
required: ["companyIds"]
|
|
342
|
-
},
|
|
247
|
+
description: "Score companies against ICP profile using AI (async). 1 credit/company. ALWAYS call estimate_cost first.",
|
|
248
|
+
schema: z.object({
|
|
249
|
+
companyIds: z.array(z.string()).describe("Company UUIDs (max 500)"),
|
|
250
|
+
icpProfileId: z.string().optional().describe("ICP profile UUID (uses default if omitted)"),
|
|
251
|
+
idempotencyKey: z.string().optional()
|
|
252
|
+
}),
|
|
343
253
|
handler: async (client, args) => client.post("/icp-scores/bulk", {
|
|
344
254
|
companyIds: args.companyIds,
|
|
345
255
|
icpProfileId: args.icpProfileId,
|
|
@@ -384,10 +294,10 @@ async function main() {
|
|
|
384
294
|
const apiKey = process.env.BREAKNORM_API_KEY;
|
|
385
295
|
if (!apiKey) {
|
|
386
296
|
console.error("Error: BREAKNORM_API_KEY environment variable is required.");
|
|
387
|
-
console.error("Get your API key at https://breaknorm.
|
|
297
|
+
console.error("Get your API key at https://breaknorm.hu/app/settings/developers");
|
|
388
298
|
process.exit(1);
|
|
389
299
|
}
|
|
390
|
-
const baseUrl = process.env.BREAKNORM_BASE_URL || "https://breaknorm.
|
|
300
|
+
const baseUrl = process.env.BREAKNORM_BASE_URL || "https://breaknorm.hu";
|
|
391
301
|
const client = new BreakNormClient(apiKey, baseUrl);
|
|
392
302
|
const server = new McpServer({
|
|
393
303
|
name: SERVER_NAME,
|
|
@@ -399,7 +309,7 @@ async function main() {
|
|
|
399
309
|
server.tool(
|
|
400
310
|
tool.name,
|
|
401
311
|
tool.description,
|
|
402
|
-
tool.
|
|
312
|
+
tool.schema.shape,
|
|
403
313
|
async (args) => {
|
|
404
314
|
try {
|
|
405
315
|
const result = await tool.handler(client, args);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@breaknorm_hu/mcp-server",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Breaknorm MCP Server — AI agent interface for B2B contact database",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -13,7 +13,8 @@
|
|
|
13
13
|
"start": "node dist/index.js"
|
|
14
14
|
},
|
|
15
15
|
"dependencies": {
|
|
16
|
-
"@modelcontextprotocol/sdk": "^1.12.1"
|
|
16
|
+
"@modelcontextprotocol/sdk": "^1.12.1",
|
|
17
|
+
"zod": "^4.3.6"
|
|
17
18
|
},
|
|
18
19
|
"devDependencies": {
|
|
19
20
|
"tsup": "^8.4.0",
|