@backendkit-labs/mcp-connectors 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/README.md ADDED
@@ -0,0 +1,743 @@
1
+ # @backendkit-labs/mcp-connectors
2
+
3
+ > **Status: `stable`** — Maintained and production-ready. No new features during the pilot phase.
4
+
5
+ Three production-ready MCP (Model Context Protocol) servers that expose enterprise SaaS integrations as agent tools: **HubSpot CRM**, **Jira**, and **Google Calendar**.
6
+
7
+ Each connector ships as a standalone binary that runs in stdio mode (for use with any MCP client) or HTTP mode (for multi-tenant server deployments).
8
+
9
+ [![npm](https://img.shields.io/npm/v/@backendkit-labs/mcp-connectors)](https://www.npmjs.com/package/@backendkit-labs/mcp-connectors)
10
+
11
+ ## Table of contents
12
+
13
+ - [Installation](#installation)
14
+ - [Running the servers](#running-the-servers)
15
+ - [HubSpot CRM connector](#hubspot-crm-connector)
16
+ - [Jira connector](#jira-connector)
17
+ - [Google Calendar connector](#google-calendar-connector)
18
+ - [Using with agent-core MCPClientManager](#using-with-agent-core-mcpclientmanager)
19
+ - [HTTP mode (multi-session)](#http-mode-multi-session)
20
+ - [Wiring all three connectors](#wiring-all-three-connectors)
21
+
22
+ ---
23
+
24
+ ## Installation
25
+
26
+ ```bash
27
+ npm install @backendkit-labs/mcp-connectors
28
+ ```
29
+
30
+ The package ships three binaries:
31
+
32
+ | Binary | MCP server name |
33
+ |--------|----------------|
34
+ | `bk-mcp-hubspot` | `bk-mcp-hubspot` |
35
+ | `bk-mcp-jira` | `bk-mcp-jira` |
36
+ | `bk-mcp-gcal` | `bk-mcp-gcal` |
37
+
38
+ Or use via `npx` without installing:
39
+
40
+ ```bash
41
+ npx @backendkit-labs/mcp-connectors bk-mcp-hubspot
42
+ ```
43
+
44
+ ---
45
+
46
+ ## Running the servers
47
+
48
+ ### Stdio mode (default — for MCP clients like Claude Desktop, agent-core)
49
+
50
+ ```bash
51
+ # HubSpot
52
+ HUBSPOT_TOKEN=pat-xxx bk-mcp-hubspot
53
+
54
+ # Jira
55
+ JIRA_HOST=mycompany.atlassian.net JIRA_EMAIL=me@co.com JIRA_TOKEN=xxx bk-mcp-jira
56
+
57
+ # Google Calendar (service account — recommended)
58
+ GCAL_SERVICE_ACCOUNT_KEY='{ "client_email": "...", "private_key": "..." }' bk-mcp-gcal
59
+
60
+ # Google Calendar (access token — short-lived, for quick testing)
61
+ GCAL_ACCESS_TOKEN=ya29.xxx bk-mcp-gcal
62
+ ```
63
+
64
+ ### HTTP mode (for remote/multi-session deployments)
65
+
66
+ ```bash
67
+ bk-mcp-hubspot --http --port 4001
68
+ bk-mcp-jira --http --port 4002
69
+ bk-mcp-gcal --http --port 4003
70
+ ```
71
+
72
+ ---
73
+
74
+ ## HubSpot CRM connector
75
+
76
+ ### Authentication
77
+
78
+ Create a **Private App** in your HubSpot account (`Settings → Integrations → Private Apps`), grant it the required CRM scopes, and copy the access token:
79
+
80
+ | Environment variable | Required | Description |
81
+ |---------------------|----------|-------------|
82
+ | `HUBSPOT_TOKEN` | Yes | Private App access token |
83
+
84
+ ### Tools
85
+
86
+ #### `crm_search_contacts`
87
+
88
+ Search contacts by name, email, or company name.
89
+
90
+ ```
91
+ Tool: crm_search_contacts
92
+ Args:
93
+ query string Search term — name, email, or company
94
+ limit number? Max results (default 10)
95
+ ```
96
+
97
+ **Example:**
98
+ ```
99
+ search for alice@acme.com
100
+ → crm_search_contacts({ query: "alice@acme.com" })
101
+ → "ID:12345 Alice Johnson <alice@acme.com> Acme Corp +1-555-0100"
102
+ ```
103
+
104
+ **Complex example — search and enrich:**
105
+ ```
106
+ Find all contacts at Acme Corp and tell me their job titles and when they were created
107
+ → crm_search_contacts({ query: "Acme Corp", limit: 50 })
108
+ → For each contact: crm_get_contact({ id: "..." })
109
+ → LLM synthesizes a table of names, titles, and creation dates
110
+ ```
111
+
112
+ ---
113
+
114
+ #### `crm_get_contact`
115
+
116
+ Get full property details for a contact by HubSpot ID.
117
+
118
+ ```
119
+ Tool: crm_get_contact
120
+ Args:
121
+ id string HubSpot contact ID
122
+ ```
123
+
124
+ **Returns:** `firstname`, `lastname`, `email`, `phone`, `company`, `jobtitle`, `lifecyclestage`, `createdate`, `lastmodifieddate`
125
+
126
+ **Example:**
127
+ ```
128
+ Get the full details for contact 12345
129
+ → crm_get_contact({ id: "12345" })
130
+ → "Contact 12345
131
+ firstname: Alice
132
+ lastname: Johnson
133
+ email: alice@acme.com
134
+ jobtitle: VP Engineering
135
+ lifecyclestage: customer"
136
+ ```
137
+
138
+ ---
139
+
140
+ #### `crm_create_contact`
141
+
142
+ Create a new contact in HubSpot CRM.
143
+
144
+ ```
145
+ Tool: crm_create_contact
146
+ Args:
147
+ email string Email address (required)
148
+ firstname string?
149
+ lastname string?
150
+ phone string?
151
+ company string?
152
+ jobtitle string?
153
+ ```
154
+
155
+ **Example:**
156
+ ```
157
+ Add Bob Smith from Globex as a new CRM contact — bob@globex.com, +1-555-9999, Sr. Engineer
158
+ → crm_create_contact({
159
+ email: "bob@globex.com",
160
+ firstname: "Bob",
161
+ lastname: "Smith",
162
+ company: "Globex",
163
+ phone: "+1-555-9999",
164
+ jobtitle: "Sr. Engineer"
165
+ })
166
+ → "Created contact ID: 67890"
167
+ ```
168
+
169
+ ---
170
+
171
+ #### `crm_search_deals`
172
+
173
+ Search deals by name and/or deal stage.
174
+
175
+ ```
176
+ Tool: crm_search_deals
177
+ Args:
178
+ query string? Deal name search term
179
+ stage string? Deal stage key (e.g. appointmentscheduled, qualifiedtobuy, closedwon, closedlost)
180
+ limit number? Max results (default 10)
181
+ ```
182
+
183
+ **Example — find open deals:**
184
+ ```
185
+ Show me all deals in the "Qualified to Buy" stage worth over $50k
186
+ → crm_search_deals({ stage: "qualifiedtobuy", limit: 50 })
187
+ → LLM filters by amount from results
188
+ → "ID:111 Enterprise License stage:qualifiedtobuy $75,000 close:2026-09-01"
189
+ ```
190
+
191
+ **Example — search by name:**
192
+ ```
193
+ Find the Acme Corp deal
194
+ → crm_search_deals({ query: "Acme Corp" })
195
+ → "ID:222 Acme Corp Enterprise stage:presentationscheduled $120,000 close:2026-07-15"
196
+ ```
197
+
198
+ ---
199
+
200
+ #### `crm_create_note`
201
+
202
+ Log an activity note, optionally linked to a contact and/or deal.
203
+
204
+ ```
205
+ Tool: crm_create_note
206
+ Args:
207
+ body string Note content
208
+ contact_id string? HubSpot contact ID to associate
209
+ deal_id string? HubSpot deal ID to associate
210
+ ```
211
+
212
+ **Example:**
213
+ ```
214
+ Log a call note: "Spoke with Alice, agreed to send proposal by Friday"
215
+ Associate with contact 12345 and deal 222
216
+ → crm_create_note({
217
+ body: "Spoke with Alice, agreed to send proposal by Friday",
218
+ contact_id: "12345",
219
+ deal_id: "222"
220
+ })
221
+ → "Created note ID: 333"
222
+ ```
223
+
224
+ **Complex workflow example:**
225
+
226
+ ```
227
+ After our sales call with Acme Corp today:
228
+ 1. Find the Acme contact and deal
229
+ 2. Log a note about the meeting outcome
230
+ 3. Check if the deal close date needs updating
231
+
232
+ → crm_search_contacts({ query: "Alice Acme Corp" }) → contact 12345
233
+ → crm_search_deals({ query: "Acme Corp" }) → deal 222
234
+ → crm_create_note({ body: "...", contact_id: "12345", deal_id: "222" })
235
+ → LLM: "Note logged. The deal close date is 2026-07-15 — would you like to update it?"
236
+ ```
237
+
238
+ ---
239
+
240
+ ## Jira connector
241
+
242
+ ### Authentication
243
+
244
+ Generate an **API token** at `https://id.atlassian.com/manage-profile/security/api-tokens`.
245
+
246
+ | Environment variable | Required | Description |
247
+ |---------------------|----------|-------------|
248
+ | `JIRA_HOST` | Yes | Your Jira domain (e.g. `mycompany.atlassian.net`) |
249
+ | `JIRA_EMAIL` | Yes | Your Atlassian account email |
250
+ | `JIRA_TOKEN` | Yes | Jira API token (not your password) |
251
+
252
+ ### Tools
253
+
254
+ #### `jira_search`
255
+
256
+ Search Jira issues using JQL (Jira Query Language).
257
+
258
+ ```
259
+ Tool: jira_search
260
+ Args:
261
+ jql string JQL query
262
+ limit number? Max results (default 20)
263
+ ```
264
+
265
+ **JQL examples:**
266
+ ```
267
+ # All open bugs in PROJ, assigned to me
268
+ jql: project = PROJ AND issuetype = Bug AND status != Done AND assignee = currentUser()
269
+
270
+ # Recently updated critical issues
271
+ jql: priority = Critical AND updated >= -7d ORDER BY updated DESC
272
+
273
+ # Issues in the current sprint
274
+ jql: project = PROJ AND sprint in openSprints()
275
+
276
+ # My unresolved tasks due this week
277
+ jql: assignee = currentUser() AND resolution = Unresolved AND due <= endOfWeek()
278
+ ```
279
+
280
+ **Example:**
281
+ ```
282
+ Show me all open bugs in the BACKEND project assigned to Alice
283
+ → jira_search({ jql: 'project = BACKEND AND issuetype = Bug AND status != Done AND assignee = "Alice Johnson"' })
284
+ → "BACK-45 [In Progress] High Auth token expiry bug @Alice Johnson
285
+ BACK-51 [To Do] Medium Rate limit not enforced @Alice Johnson"
286
+ ```
287
+
288
+ ---
289
+
290
+ #### `jira_get_issue`
291
+
292
+ Get full details for a Jira issue including description and recent comments.
293
+
294
+ ```
295
+ Tool: jira_get_issue
296
+ Args:
297
+ key string Issue key (e.g. PROJ-123)
298
+ ```
299
+
300
+ **Returns:** summary, status, assignee, description (converted from ADF to plain text), last 3 comments.
301
+
302
+ **Example:**
303
+ ```
304
+ What's the current status and description of BACK-45?
305
+ → jira_get_issue({ key: "BACK-45" })
306
+ → "BACK-45: Auth token expiry bug
307
+ Status: In Progress Assignee: Alice Johnson
308
+
309
+ Description:
310
+ When a token expires during an active session, the server returns 500
311
+ instead of 401. Reproduction steps: ...
312
+
313
+ Comments (last 2):
314
+ [Alice Johnson · 2026-06-10]: Traced to TokenValidator.validate()
315
+ [Bob Smith · 2026-06-11]: PR opened: https://..."
316
+ ```
317
+
318
+ ---
319
+
320
+ #### `jira_create_issue`
321
+
322
+ Create a new Jira issue.
323
+
324
+ ```
325
+ Tool: jira_create_issue
326
+ Args:
327
+ project string Project key (e.g. PROJ)
328
+ summary string Issue title
329
+ description string? Plain text description
330
+ issue_type string? Issue type (default: Task). E.g. Bug, Story, Epic
331
+ priority string? Priority (Highest, High, Medium, Low, Lowest)
332
+ ```
333
+
334
+ **Example:**
335
+ ```
336
+ Create a high-priority bug in BACK: "Login fails for SAML users with special characters in email"
337
+ → jira_create_issue({
338
+ project: "BACK",
339
+ summary: "Login fails for SAML users with special characters in email",
340
+ description: "Users with + in email address cannot log in via SAML SSO. Affects @company.com+alias emails.",
341
+ issue_type: "Bug",
342
+ priority: "High"
343
+ })
344
+ → "Created BACK-52 (ID: 100023)"
345
+ ```
346
+
347
+ ---
348
+
349
+ #### `jira_add_comment`
350
+
351
+ Add a comment to a Jira issue.
352
+
353
+ ```
354
+ Tool: jira_add_comment
355
+ Args:
356
+ key string Issue key
357
+ body string Comment text
358
+ ```
359
+
360
+ **Example:**
361
+ ```
362
+ Add a comment to BACK-45: "Fixed in PR #127, ready for review"
363
+ → jira_add_comment({ key: "BACK-45", body: "Fixed in PR #127, ready for review" })
364
+ → "Comment added to BACK-45."
365
+ ```
366
+
367
+ ---
368
+
369
+ #### `jira_transition`
370
+
371
+ Move an issue to a new status (e.g. move from "To Do" to "In Progress").
372
+
373
+ ```
374
+ Tool: jira_transition
375
+ Args:
376
+ key string Issue key
377
+ status string Target status name (e.g. Done, In Progress, To Do, In Review)
378
+ ```
379
+
380
+ The tool fetches available transitions first and matches by name (case-insensitive). If the target status isn't available, it reports valid options.
381
+
382
+ **Example:**
383
+ ```
384
+ Mark BACK-52 as "In Progress"
385
+ → jira_transition({ key: "BACK-52", status: "In Progress" })
386
+ → "BACK-52 → "In Progress""
387
+
388
+ Mark BACK-45 as done
389
+ → jira_transition({ key: "BACK-45", status: "Done" })
390
+ → "BACK-45 → "Done""
391
+ ```
392
+
393
+ **Complex workflow — sprint management:**
394
+
395
+ ```
396
+ Close out the sprint: mark all Done issues as closed, move remaining In Progress issues to next sprint
397
+
398
+ → jira_search({ jql: "sprint in openSprints() AND status = Done" })
399
+ For each: jira_transition({ key: "...", status: "Closed" })
400
+
401
+ → jira_search({ jql: "sprint in openSprints() AND status = 'In Progress'" })
402
+ LLM: "Found 3 in-progress issues: BACK-48, BACK-50, BACK-53. Move them to next sprint? (I need the sprint ID)"
403
+ ```
404
+
405
+ ---
406
+
407
+ ## Google Calendar connector
408
+
409
+ ### Authentication
410
+
411
+ Two auth modes are supported, checked in order:
412
+
413
+ #### Mode 1: Service Account (recommended for production)
414
+
415
+ Create a service account in Google Cloud Console, download the JSON key, and share the target calendar with the service account email. Supports automatic token refresh — no manual intervention needed.
416
+
417
+ ```bash
418
+ # Paste the full JSON content of the service account key file
419
+ GCAL_SERVICE_ACCOUNT_KEY='{"type":"service_account","client_email":"bk-agent@my-project.iam.gserviceaccount.com","private_key":"-----BEGIN RSA PRIVATE KEY-----\n...","token_uri":"https://oauth2.googleapis.com/token"}'
420
+ ```
421
+
422
+ The private key can also contain literal `\n` (as stored in environment variables) — the server handles unescaping automatically.
423
+
424
+ #### Mode 2: Access Token (quick testing)
425
+
426
+ ```bash
427
+ GCAL_ACCESS_TOKEN=ya29.a0AfH... # Google OAuth2 access token (expires in ~1h)
428
+ ```
429
+
430
+ | Environment variable | Required | Description |
431
+ |---------------------|----------|-------------|
432
+ | `GCAL_SERVICE_ACCOUNT_KEY` | Recommended | Full JSON content of a service account key |
433
+ | `GCAL_ACCESS_TOKEN` | Alternative | Short-lived OAuth2 token |
434
+
435
+ ### Tools
436
+
437
+ #### `calendar_list_calendars`
438
+
439
+ List all Google Calendars accessible to the authenticated account.
440
+
441
+ ```
442
+ Tool: calendar_list_calendars
443
+ Args: (none)
444
+ ```
445
+
446
+ **Example:**
447
+ ```
448
+ What calendars do I have access to?
449
+ → calendar_list_calendars()
450
+ → "primary@company.com "Alice Johnson" (primary) [owner]
451
+ team-eng@group.calendar.google.com "Engineering Team" [writer]
452
+ company-holidays@group.calendar.google.com "Company Holidays" [reader]"
453
+ ```
454
+
455
+ ---
456
+
457
+ #### `calendar_list_events`
458
+
459
+ List upcoming events within a time range.
460
+
461
+ ```
462
+ Tool: calendar_list_events
463
+ Args:
464
+ calendar_id string? Calendar ID (default: "primary")
465
+ time_min string? Start of range, ISO 8601 (default: now)
466
+ time_max string? End of range, ISO 8601 (default: 7 days from now)
467
+ limit number? Max events (default 10)
468
+ ```
469
+
470
+ **Example — this week:**
471
+ ```
472
+ What's on my calendar this week?
473
+ → calendar_list_events({ limit: 20 })
474
+ → "[2026-06-11 09:00 – 09:30] Daily Standup
475
+ [2026-06-11 14:00 – 15:00] Architecture Review (4 attendees)
476
+ [2026-06-12 10:00 – 11:00] 1:1 with Bob
477
+ [2026-06-13 All Day] Company Holiday"
478
+ ```
479
+
480
+ **Example — specific calendar and range:**
481
+ ```
482
+ Show me all Engineering Team events for next month
483
+ → calendar_list_events({
484
+ calendar_id: "team-eng@group.calendar.google.com",
485
+ time_min: "2026-07-01T00:00:00Z",
486
+ time_max: "2026-07-31T23:59:59Z",
487
+ limit: 50
488
+ })
489
+ ```
490
+
491
+ ---
492
+
493
+ #### `calendar_create_event`
494
+
495
+ Create a new event in Google Calendar.
496
+
497
+ ```
498
+ Tool: calendar_create_event
499
+ Args:
500
+ summary string Event title (required)
501
+ start string Start time ISO 8601 (required)
502
+ end string End time ISO 8601 (required)
503
+ description string? Event description
504
+ attendees string[]? List of attendee email addresses
505
+ timezone string? IANA timezone (default: UTC)
506
+ calendar_id string? Calendar ID (default: "primary")
507
+ ```
508
+
509
+ **Example — simple meeting:**
510
+ ```
511
+ Schedule a 1-hour team sync tomorrow at 2pm Pacific
512
+ → calendar_create_event({
513
+ summary: "Team Sync",
514
+ start: "2026-06-12T14:00:00",
515
+ end: "2026-06-12T15:00:00",
516
+ timezone: "America/Los_Angeles"
517
+ })
518
+ → "Event created: abc123def
519
+ https://calendar.google.com/calendar/event?eid=..."
520
+ ```
521
+
522
+ **Example — meeting with attendees:**
523
+ ```
524
+ Book a sprint planning meeting for next Monday 10am-12pm UTC
525
+ Invite: alice@co.com, bob@co.com, carol@co.com
526
+ Add agenda to description
527
+ → calendar_create_event({
528
+ summary: "Sprint Planning — Q3 Sprint 2",
529
+ start: "2026-06-15T10:00:00",
530
+ end: "2026-06-15T12:00:00",
531
+ timezone: "UTC",
532
+ description: "Agenda:\n1. Review previous sprint\n2. Story point estimation\n3. Sprint commitment",
533
+ attendees: ["alice@co.com", "bob@co.com", "carol@co.com"]
534
+ })
535
+ → "Event created: xyz789
536
+ https://calendar.google.com/..."
537
+ ```
538
+
539
+ ---
540
+
541
+ #### `calendar_get_freebusy`
542
+
543
+ Check free/busy availability for one or more users in a time range.
544
+
545
+ ```
546
+ Tool: calendar_get_freebusy
547
+ Args:
548
+ emails string[] List of email addresses to check (required)
549
+ time_min string Start of range, ISO 8601 (required)
550
+ time_max string End of range, ISO 8601 (required)
551
+ ```
552
+
553
+ **Example — find a meeting slot:**
554
+ ```
555
+ Find a 1-hour slot when alice@co.com, bob@co.com, and carol@co.com are all free next Tuesday
556
+ → calendar_get_freebusy({
557
+ emails: ["alice@co.com", "bob@co.com", "carol@co.com"],
558
+ time_min: "2026-06-16T08:00:00Z",
559
+ time_max: "2026-06-16T18:00:00Z"
560
+ })
561
+ → "alice@co.com:
562
+ BUSY 09:00 – 09:30
563
+ BUSY 11:00 – 12:00
564
+ bob@co.com: FREE all day
565
+ carol@co.com:
566
+ BUSY 10:00 – 11:30
567
+ BUSY 14:00 – 15:00"
568
+ → LLM: "Everyone is free from 12:00 to 14:00 and from 15:00 to 18:00. Should I book 12:00–13:00?"
569
+ ```
570
+
571
+ ---
572
+
573
+ ## Using with agent-core MCPClientManager
574
+
575
+ ### Stdio mode (local — recommended for development)
576
+
577
+ ```typescript
578
+ import { MCPClientManager } from '@backendkit-labs/agent-core';
579
+
580
+ const mcp = new MCPClientManager();
581
+
582
+ await mcp.connectStdio({
583
+ name: 'hubspot',
584
+ command: 'bk-mcp-hubspot',
585
+ args: [],
586
+ env: { HUBSPOT_TOKEN: process.env.HUBSPOT_TOKEN! },
587
+ });
588
+
589
+ await mcp.connectStdio({
590
+ name: 'jira',
591
+ command: 'bk-mcp-jira',
592
+ args: [],
593
+ env: {
594
+ JIRA_HOST: process.env.JIRA_HOST!,
595
+ JIRA_EMAIL: process.env.JIRA_EMAIL!,
596
+ JIRA_TOKEN: process.env.JIRA_TOKEN!,
597
+ },
598
+ });
599
+
600
+ await mcp.connectStdio({
601
+ name: 'gcal',
602
+ command: 'bk-mcp-gcal',
603
+ args: [],
604
+ env: { GCAL_SERVICE_ACCOUNT_KEY: process.env.GCAL_SERVICE_ACCOUNT_KEY! },
605
+ });
606
+
607
+ // All tools are now available to the engine
608
+ const tools = mcp.getTools();
609
+ ```
610
+
611
+ ### Via engine options (recommended approach)
612
+
613
+ Pass `mcpServers` to `createBaseEngine` or `createCodingEngine` — the engine manages lifecycle, retries, and disconnect detection:
614
+
615
+ ```typescript
616
+ import { createBaseEngine } from '@backendkit-labs/agent-core';
617
+
618
+ const engine = createBaseEngine({
619
+ ...baseConfig,
620
+ mcpServers: [
621
+ {
622
+ name: 'hubspot',
623
+ command: 'npx',
624
+ args: ['-y', '@backendkit-labs/mcp-connectors/hubspot'],
625
+ env: { HUBSPOT_TOKEN: process.env.HUBSPOT_TOKEN! },
626
+ },
627
+ {
628
+ name: 'jira',
629
+ command: 'npx',
630
+ args: ['-y', '@backendkit-labs/mcp-connectors/jira'],
631
+ env: {
632
+ JIRA_HOST: process.env.JIRA_HOST!,
633
+ JIRA_EMAIL: process.env.JIRA_EMAIL!,
634
+ JIRA_TOKEN: process.env.JIRA_TOKEN!,
635
+ },
636
+ },
637
+ {
638
+ name: 'gcal',
639
+ command: 'npx',
640
+ args: ['-y', '@backendkit-labs/mcp-connectors/gcal'],
641
+ env: { GCAL_SERVICE_ACCOUNT_KEY: process.env.GCAL_SERVICE_ACCOUNT_KEY! },
642
+ },
643
+ ],
644
+ });
645
+ ```
646
+
647
+ ---
648
+
649
+ ## HTTP mode (multi-session)
650
+
651
+ Start the servers in HTTP mode when you want to share one connector instance across multiple agent sessions (e.g. in a containerized deployment):
652
+
653
+ ```bash
654
+ # Start connectors as HTTP servers
655
+ HUBSPOT_TOKEN=xxx bk-mcp-hubspot --http --port 4001
656
+ JIRA_HOST=co.atlassian.net JIRA_EMAIL=me@co.com JIRA_TOKEN=xxx bk-mcp-jira --http --port 4002
657
+ GCAL_SERVICE_ACCOUNT_KEY='...' bk-mcp-gcal --http --port 4003
658
+ ```
659
+
660
+ Connect via `connectStreamableHTTP`:
661
+
662
+ ```typescript
663
+ await mcp.connectStreamableHTTP({ name: 'hubspot', baseUrl: 'http://mcp-hubspot:4001' });
664
+ await mcp.connectStreamableHTTP({ name: 'jira', baseUrl: 'http://mcp-jira:4002' });
665
+ await mcp.connectStreamableHTTP({ name: 'gcal', baseUrl: 'http://mcp-gcal:4003' });
666
+ ```
667
+
668
+ Docker Compose example:
669
+
670
+ ```yaml
671
+ services:
672
+ mcp-hubspot:
673
+ image: node:20
674
+ command: ["npx", "-y", "@backendkit-labs/mcp-connectors", "bk-mcp-hubspot", "--http", "--port", "4001"]
675
+ environment:
676
+ HUBSPOT_TOKEN: ${HUBSPOT_TOKEN}
677
+ ports: ["4001:4001"]
678
+
679
+ mcp-jira:
680
+ image: node:20
681
+ command: ["npx", "-y", "@backendkit-labs/mcp-connectors", "bk-mcp-jira", "--http", "--port", "4002"]
682
+ environment:
683
+ JIRA_HOST: ${JIRA_HOST}
684
+ JIRA_EMAIL: ${JIRA_EMAIL}
685
+ JIRA_TOKEN: ${JIRA_TOKEN}
686
+ ports: ["4002:4002"]
687
+
688
+ mcp-gcal:
689
+ image: node:20
690
+ command: ["npx", "-y", "@backendkit-labs/mcp-connectors", "bk-mcp-gcal", "--http", "--port", "4003"]
691
+ environment:
692
+ GCAL_SERVICE_ACCOUNT_KEY: ${GCAL_SERVICE_ACCOUNT_KEY}
693
+ ports: ["4003:4003"]
694
+ ```
695
+
696
+ ---
697
+
698
+ ## Wiring all three connectors
699
+
700
+ Full example — an agent that can manage CRM contacts, Jira issues, and calendar events together:
701
+
702
+ ```typescript
703
+ import { createCodingEngine } from '@backendkit-labs/agent-coding';
704
+
705
+ const engine = createCodingEngine({
706
+ providers: {
707
+ deepseek: { apiKey: process.env.DEEPSEEK_API_KEY! },
708
+ },
709
+ defaultProvider: 'deepseek',
710
+ mcpServers: [
711
+ {
712
+ name: 'hubspot',
713
+ command: 'bk-mcp-hubspot',
714
+ args: [],
715
+ env: { HUBSPOT_TOKEN: process.env.HUBSPOT_TOKEN! },
716
+ },
717
+ {
718
+ name: 'jira',
719
+ command: 'bk-mcp-jira',
720
+ args: [],
721
+ env: {
722
+ JIRA_HOST: process.env.JIRA_HOST!,
723
+ JIRA_EMAIL: process.env.JIRA_EMAIL!,
724
+ JIRA_TOKEN: process.env.JIRA_TOKEN!,
725
+ },
726
+ },
727
+ {
728
+ name: 'gcal',
729
+ command: 'bk-mcp-gcal',
730
+ args: [],
731
+ env: { GCAL_SERVICE_ACCOUNT_KEY: process.env.GCAL_SERVICE_ACCOUNT_KEY! },
732
+ },
733
+ ],
734
+ });
735
+
736
+ // Multi-tool agent workflow
737
+ await engine.run(`
738
+ I just closed the Acme Corp deal. Please:
739
+ 1. Find the Acme Corp deal in HubSpot and log a note: "Deal closed — contract signed"
740
+ 2. Create a Jira task in the ONBOARD project: "Onboard Acme Corp — enterprise tier"
741
+ 3. Schedule a kickoff meeting for next Wednesday at 2pm ET with alice@acme.com
742
+ `);
743
+ ```
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/odoo/server.ts"],"names":[],"mappings":""}
@@ -0,0 +1,152 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ const mcp_js_1 = require("@modelcontextprotocol/sdk/server/mcp.js");
5
+ const zod_1 = require("zod");
6
+ const server_runner_js_1 = require("../shared/server-runner.js");
7
+ let _session = null;
8
+ function cfg() {
9
+ const url = process.env.ODOO_URL?.replace(/\/$/, '');
10
+ const db = process.env.ODOO_DB;
11
+ const user = process.env.ODOO_USER;
12
+ const pass = process.env.ODOO_PASSWORD;
13
+ if (!url || !db || !user || !pass) {
14
+ throw new Error('Set ODOO_URL, ODOO_DB, ODOO_USER, ODOO_PASSWORD');
15
+ }
16
+ return { url, db, user, pass };
17
+ }
18
+ async function authenticate() {
19
+ if (_session)
20
+ return _session;
21
+ const { url, db, user, pass } = cfg();
22
+ const res = await fetch(`${url}/web/session/authenticate`, {
23
+ method: 'POST',
24
+ headers: { 'Content-Type': 'application/json' },
25
+ body: JSON.stringify({
26
+ jsonrpc: '2.0', method: 'call', id: 1,
27
+ params: { db, login: user, password: pass },
28
+ }),
29
+ });
30
+ if (!res.ok)
31
+ throw new Error(`Odoo auth HTTP ${res.status}`);
32
+ const data = await res.json();
33
+ if (data.error)
34
+ throw new Error(`Odoo auth error: ${data.error.message}`);
35
+ if (!data.result?.uid)
36
+ throw new Error('Odoo auth failed — no uid returned (wrong credentials?)');
37
+ _session = { uid: data.result.uid, sessionId: data.result.session_id };
38
+ return _session;
39
+ }
40
+ async function odooCall(model, method, args, kwargs = {}) {
41
+ const sess = await authenticate();
42
+ const { url } = cfg();
43
+ const res = await fetch(`${url}/web/dataset/call_kw`, {
44
+ method: 'POST',
45
+ headers: {
46
+ 'Content-Type': 'application/json',
47
+ 'Cookie': `session_id=${sess.sessionId}`,
48
+ },
49
+ body: JSON.stringify({
50
+ jsonrpc: '2.0', method: 'call', id: Date.now(),
51
+ params: { model, method, args, kwargs },
52
+ }),
53
+ });
54
+ if (!res.ok)
55
+ throw new Error(`Odoo ${model}.${method} HTTP ${res.status}`);
56
+ const data = await res.json();
57
+ if (data.error) {
58
+ throw new Error(`Odoo ${model}.${method}: ${data.error.data?.message ?? data.error.message}`);
59
+ }
60
+ return data.result;
61
+ }
62
+ // ── Tools ─────────────────────────────────────────────────────────────────────
63
+ function buildServer() {
64
+ const srv = new mcp_js_1.McpServer({ name: 'bk-mcp-odoo', version: '0.1.0' });
65
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
66
+ const t = srv.tool.bind(srv);
67
+ t('odoo_employee_create', 'Create a new employee record in Odoo HR.', {
68
+ name: zod_1.z.string().describe('Full name'),
69
+ work_email: zod_1.z.string().email().optional().describe('Work email address'),
70
+ job_title: zod_1.z.string().optional().describe('Job title'),
71
+ department: zod_1.z.string().optional().describe('Department name (partial match)'),
72
+ }, async ({ name, work_email, job_title, department }) => {
73
+ const vals = { name };
74
+ if (work_email)
75
+ vals.work_email = work_email;
76
+ if (job_title)
77
+ vals.job_title = job_title;
78
+ if (department) {
79
+ const ids = await odooCall('hr.department', 'search', [[['name', 'ilike', department]]]);
80
+ if (ids.length)
81
+ vals.department_id = ids[0];
82
+ }
83
+ const id = await odooCall('hr.employee', 'create', [vals]);
84
+ return { content: [{ type: 'text', text: `Employee created. Odoo ID: ${id}` }] };
85
+ });
86
+ t('odoo_employee_get', 'Retrieve employee records from Odoo by ID or name search.', {
87
+ employee_id: zod_1.z.number().optional().describe('Exact employee ID'),
88
+ name: zod_1.z.string().optional().describe('Name filter (partial match)'),
89
+ limit: zod_1.z.number().optional().describe('Maximum records to return (default 5)'),
90
+ }, async ({ employee_id, name, limit }) => {
91
+ let domain;
92
+ if (employee_id !== undefined) {
93
+ domain = [['id', '=', employee_id]];
94
+ }
95
+ else if (name) {
96
+ domain = [['name', 'ilike', name]];
97
+ }
98
+ else {
99
+ domain = [['active', '=', true]];
100
+ }
101
+ const records = await odooCall('hr.employee', 'search_read', [], {
102
+ domain,
103
+ fields: ['id', 'name', 'work_email', 'job_title', 'department_id', 'active'],
104
+ limit: limit ?? 5,
105
+ });
106
+ if (!records.length) {
107
+ return { content: [{ type: 'text', text: 'No employees found.' }] };
108
+ }
109
+ const lines = records.map(r => {
110
+ const dept = r.department_id;
111
+ return [
112
+ `ID: ${r.id}`,
113
+ `Name: ${r.name}`,
114
+ `Title: ${r.job_title ?? '—'}`,
115
+ `Dept: ${dept ? dept[1] : '—'}`,
116
+ `Email: ${r.work_email ?? '—'}`,
117
+ ].join(' | ');
118
+ });
119
+ return { content: [{ type: 'text', text: lines.join('\n') }] };
120
+ });
121
+ t('payroll_prepare', 'Create a draft payroll slip (hr.payslip) for an employee and period.', {
122
+ employee_id: zod_1.z.number().describe('Odoo employee ID'),
123
+ date_from: zod_1.z.string().describe('Period start date (YYYY-MM-DD)'),
124
+ date_to: zod_1.z.string().describe('Period end date (YYYY-MM-DD)'),
125
+ }, async ({ employee_id, date_from, date_to }) => {
126
+ const payslipId = await odooCall('hr.payslip', 'create', [{
127
+ employee_id,
128
+ date_from,
129
+ date_to,
130
+ }]);
131
+ return {
132
+ content: [{ type: 'text', text: `Payslip created. ID: ${payslipId} State: draft` }],
133
+ };
134
+ });
135
+ t('payroll_confirm', 'Compute salary lines and confirm (validate) a payroll slip.', {
136
+ payslip_id: zod_1.z.number().describe('Payslip ID returned by payroll_prepare'),
137
+ }, async ({ payslip_id }) => {
138
+ await odooCall('hr.payslip', 'compute_sheet', [[payslip_id]]);
139
+ await odooCall('hr.payslip', 'action_payslip_done', [[payslip_id]]);
140
+ return {
141
+ content: [{ type: 'text', text: `Payslip ${payslip_id} confirmed. State: done` }],
142
+ };
143
+ });
144
+ return srv;
145
+ }
146
+ // ── Start ─────────────────────────────────────────────────────────────────────
147
+ const { httpMode, port } = (0, server_runner_js_1.parseCliArgs)();
148
+ (0, server_runner_js_1.runMcpServer)(buildServer, { httpMode, port, label: 'bk-mcp-odoo' }).catch(err => {
149
+ process.stderr.write(`[bk-mcp-odoo] Fatal: ${err.message}\n`);
150
+ process.exit(1);
151
+ });
152
+ //# sourceMappingURL=server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../../src/odoo/server.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,13 +1,14 @@
1
1
  {
2
2
  "name": "@backendkit-labs/mcp-connectors",
3
- "version": "0.1.0",
4
- "description": "Enterprise MCP connectors — HubSpot CRM, Jira, Google Calendar",
3
+ "version": "0.1.2",
4
+ "description": "Enterprise MCP connectors — HubSpot CRM, Jira, Google Calendar, Odoo ERP",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
7
7
  "bin": {
8
8
  "bk-mcp-hubspot": "./dist/hubspot/server.js",
9
9
  "bk-mcp-jira": "./dist/jira/server.js",
10
- "bk-mcp-gcal": "./dist/gcal/server.js"
10
+ "bk-mcp-gcal": "./dist/gcal/server.js",
11
+ "bk-mcp-odoo": "./dist/odoo/server.js"
11
12
  },
12
13
  "files": ["dist/", "README.md"],
13
14
  "repository": {