LinkedIn CRM MCP服务器

Kanbox MCP服务器为Claude Code等MCP客户端提供LinkedIn CRM API的读写工具,支持消息收发、联系人管理和潜在客户开发。

  • by flacoste
  • TypeScript
  • 本地部署
  • 更新于
在 GitHub 查看 flacoste/kanbox-mcp

使用说明

kanbox-mcp

MCP server for the Kanbox LinkedIn CRM API. Exposes two dispatcher tools — kanbox_read (4 actions) and kanbox_write (6 actions) — for use with Claude Code and other MCP clients.

Use cases

  • CRM triage — Search unread inbox messages, read conversations, then draft and send replies — all from your editor.
  • Pipeline management — Query contacts by pipeline/step, update labels or move contacts between steps based on conversation context.
  • Outreach automation — Build a lead list, send personalized connection requests with icebreakers, and follow up with messages once connected.
  • Lead enrichment — Add leads by LinkedIn URL for full background enrichment, then poll processing status until complete.
  • Contact lookup — Resolve a LinkedIn profile slug to the internal linkedin_id needed for messaging, without leaving your workflow.
  • Refresh a contact — Re-scrape a single profile on demand and get the fresh, normalized record back.

Design

This server uses a two-tool dispatcher pattern instead of exposing 9 separate MCP tools. Inspired by Jesse Vincent's article on MCP API design, the goals are:

  • Minimal context footprint — LLM clients load every tool definition into their context window. Two tools with action routing costs far less context than nine individual tool definitions, leaving more room for the actual conversation.
  • Self-documenting — Each tool's description includes the full action catalog with parameter names and gotchas. The LLM gets everything it needs from the tool definition itself, without requiring external documentation in the system prompt.
  • Read/write permission split — Two tools instead of one lets MCP clients auto-approve kanbox_read while requiring explicit confirmation for kanbox_write.
  • Flat responses — The Kanbox API nests contact data under lead.* and lnuser.* keys. This server flattens responses so fields like linkedin_id, firstname, email appear at the top level, reducing the tokens the LLM spends parsing nested structures.

Key response fields

search_members returns the richest data — these are your inbox/connection contacts:

id, linkedin_id, linkedin_public_id, firstname, lastname, email, phone, company, job, location, labels, pipeline, step, icebreaker, custom, conversations (with id, last_message, unread_count)

With include_history: true, members also carry years_of_experience, the current-position dates (year_position, month_position, startyear_position, startmonth_position), and past_positions — an array of prior roles, each with title, company, company_linkedin_url, location, description, start_year/start_month, end_year/end_month, and is_current. These are omitted by default to keep responses compact; request them only when reconstructing a contact's job changes.

search_leads returns scraped lead data (no linkedin_id — resolve via search_members):

linkedin_public_id, firstname, lastname, email, phone, company, job, location

list_lists returns list metadata:

name, count, is_processing

Setup

In Kanbox Settings, turn on "Enable API Key" in the "API" section, then set the token in your environment:

export KANBOX_API_TOKEN=your_api_key

Claude Code configuration

Add to ~/.claude/settings.json:

{
  "mcpServers": {
    "kanbox": {
      "command": "npx",
      "args": ["-y", "github:flacoste/kanbox-mcp"],
      "env": {
        "KANBOX_API_TOKEN": "${KANBOX_API_TOKEN}"
      }
    }
  }
}

No local clone required — npx fetches the repo, installs dependencies, and builds automatically.

Local install

If you prefer a local copy:

git clone https://github.com/flacoste/kanbox-mcp.git
cd kanbox-mcp
npm install  # also builds via prepare script

Then point the config at the local build:

{
  "command": "node",
  "args": ["/path/to/kanbox-mcp/dist/index.js"],
  "env": {
    "KANBOX_API_TOKEN": "${KANBOX_API_TOKEN}"
  }
}

Tools

kanbox_read

Read-only queries against Kanbox data.

search_members

Search inbox, connections, or unread messages.

Param Type Required Description
q string no Fuzzy name search (a single distinctive term works best)
type inbox | unread_inbox | connections no Filter by member type
pipeline_name string no Filter by pipeline name
step_title string no Filter by pipeline step title
linkedin_public_ids string[] no Public profile slugs for exact lookup
updated_since string no ISO 8601 timestamp filter
limit number no Max results (1-100)
offset number no Results to skip
include_history boolean no Include position history (past_positions, years_of_experience, position dates). Default false — off keeps output compact

search_leads

Search scraped leads by list name or query.

Param Type Required Description
name string no List name filter
q string no Search query
limit number no Max results (1-100)
offset number no Results to skip

get_messages

Get conversation messages.

Param Type Required Description
conversation_id number yes Conversation ID from search_members conversations[].id
cursor string no Pagination cursor from previous response

list_lists

List available Kanbox lead lists.

Param Type Required Description
limit number no Max results (1-100)
offset number no Results to skip

kanbox_write

Write operations. Most writes are async (HTTP 202 Accepted); refresh_lead returns synchronously (HTTP 200).

update_member

Update member fields.

Param Type Required Description
id number yes Member ID from search_members
email string no Email address
phone string no Phone number
labels string[] no Full replacement — pass ALL desired labels
pipeline string no Pipeline name
step string no Pipeline step
custom string no Custom note
icebreaker string no Icebreaker note

send_message

Send a LinkedIn message.

Param Type Required Description
recipient_linkedin_id string yes Internal LinkedIn ID (ACoAAA... format)
message string yes Message text (max 8000 chars)

send_connection

Send a connection request.

Param Type Required Description
recipient_linkedin_id string yes Internal LinkedIn ID (ACoAAA... format)
message string no Request message (max 300 chars)

add_lead

Add a structured lead to a list (partial enrichment only).

Param Type Required Description
list string yes Kanbox list name
linkedin_public_id string yes Public profile slug
firstname string yes First name
lastname string yes Last name
email string no Email address
phone string no Phone number
company string no Company name
location string no Location
job string no Job title
icebreaker string no Icebreaker note
labels string[] no Labels
pipeline string no Pipeline name
step string no Pipeline step

add_lead_url

Add a lead by LinkedIn URL for full enrichment (takes minutes).

Param Type Required Description
linkedin_profile_url string yes Full LinkedIn profile URL
list string yes Kanbox list name

refresh_lead

Re-scrape a single contact and return the refreshed, normalized member record. Synchronous (HTTP 200), unlike the async writes above. Accepts a public slug or a full profile URL — not the Kanbox lead_id. The record always includes the full position history (past_positions, years_of_experience, position dates) — unlike search_members, which gates that behind include_history to keep list responses compact; a single refreshed record carries no such cost. This also resolves a scraped lead into a messageable member: refreshing a pure lead creates its member record, returns the internal linkedin_id, and makes it appear in search_members.

Param Type Required Description
identifier string yes LinkedIn public slug (e.g. janedoe) or full profile URL

Key conventions

  • linkedin_id vs public slug — search_members returns a linkedin_id field (internal ID starting with ACoAAA...). Use this ID for send_message and send_connection, not the public profile slug.
  • Labels are full replacement — update_member labels replace the entire label set. Read existing labels first, then pass the complete desired list.
  • Async writes — Most write operations return HTTP 202 and may take a few seconds to appear on subsequent reads. refresh_lead is the exception: it returns HTTP 200 synchronously with the refreshed record.
  • Leads vs members — search_leads does not return linkedin_id, and a pure lead is not visible to search_members. Resolve it with refresh_lead (slug or URL), which creates the member record and returns the internal linkedin_id needed for messaging.

Development

npm run build       # tsc -> dist/
npm run dev         # tsx src/index.ts
npm test            # vitest run
npm run test:watch  # vitest (watch mode)

License

MIT