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