How to Build a LinkedIn MCP Server (2026 Reference Implementation)
A working reference for building a LinkedIn MCP server in 2026: tools/list, JSON-RPC transport, OAuth, rate limits, and the 19-tool surface in production.
TL;DR
Building a LinkedIn MCP server in 2026 means standing up an HTTP endpoint that speaks JSON-RPC 2.0, exposing tools/list, tools/call, and resources/list, then backing it with a LinkedIn data layer and shaping high-level tools that compose multiple low-level calls into single agent-callable actions. The protocol is defined at modelcontextprotocol.io. Fintalio’s production server exposes 19 tools at https://fintalio.com/mcp, authenticated via Sanctum bearer tokens, rate-limited to 120 requests per minute per token.
This article is the reference. Every layer is shown in code: server bootstrap, transport, tools/list payload, OAuth alternatives, rate limiting, idempotency, audit logging, and the Claude Desktop config that drops it all into a working agent. The working production example throughout is Fintalio’s own FintalioServer, version 0.0.1, exactly as it ships, no invented tools.
If you want the shortest path from this article to a running LinkedIn agent, plug Fintalio into Claude Desktop and skip the build.
What is an MCP server, and why does LinkedIn need one?
The Model Context Protocol (MCP) is an open spec Anthropic released in November 2024 to let LLM applications connect to external tools through a single, consistent wire format (Anthropic MCP announcement, 2024). An MCP server publishes a list of tools, resources, and prompts over JSON-RPC. Any MCP-compatible host, Claude Desktop, Cursor, Windsurf, Claude Code, OpenAI’s Agents SDK, can discover and invoke them without bespoke glue.
LinkedIn is the integration agents ask for most and the one nobody can ship cleanly. The official LinkedIn API covers posting, comments, ads, and Marketing Developer Platform endpoints. It does not cover search, connection requests, direct messages, or InMail at scale. That is the entire write surface an outreach or sourcing agent actually needs. The gap is why every LinkedIn-for-agents product in 2026 wraps a third-party LinkedIn data layer rather than the official API alone.
MCP earns its keep here because it normalizes the messy parts (session auth, retries, rate limits, idempotency) into agent-shaped tools. The LLM sees list_contacts, not GET /api/v1/users?session_id=…&filter[is_connected]=…. The host handles transport. You write the server once and it works in every host that speaks the protocol. For the category overview, see the LinkedIn MCP pillar guide.
What are the four moving parts of any MCP server?
Every working MCP server has four layers: a transport, a wire format, a tool surface, and an authentication scheme. Get those four right and the rest is plumbing. The official MCP spec defines all four, but the spec is intentionally generic. The hard part of a LinkedIn MCP is the choices you make inside each layer, because LinkedIn is not a generic data source.
Transport layer: HTTP + SSE or stdio
Modern MCP servers ship as HTTP endpoints. The legacy stdio transport, where the host launches the server as a subprocess and talks over stdin/stdout, still works for local-only servers (Filesystem MCP, for example). For anything hosted, anything that needs to scale, anything that needs auth tied to a user account, HTTP is the right choice. Fintalio mounts at /mcp over HTTPS with Server-Sent Events for streaming responses.
The Laravel mount is one line. The framework’s laravel/mcp package handles transport negotiation, JSON-RPC framing, and SSE upgrades automatically:
<?php
// routes/ai.php
use App\Mcp\Servers\FintalioServer;
use Laravel\Mcp\Facades\Mcp;
Mcp::web('/mcp', FintalioServer::class)
->middleware(['auth:sanctum', 'throttle:120,1']);
Two middlewares. auth:sanctum resolves the bearer token to a User model. throttle:120,1 caps the endpoint at 120 requests per minute per token. The rest of the server, transport selection, request parsing, response framing, lives inside the package. You write tools, not transports.
JSON-RPC 2.0 wire format
MCP rides on JSON-RPC 2.0 (JSON-RPC 2.0 specification). Four methods matter for a tools-only server: initialize, tools/list, tools/call, and resources/list (plus resources/read if you ship resources). Each request is a JSON object with jsonrpc: "2.0", an id, a method, and optional params. Each response echoes the id and returns either result or error.
A client’s first request after connection is always initialize. The server responds with its capabilities and protocol version. The client then calls tools/list to discover the tool surface, which is the payload an LLM actually reads when choosing what to call.
Tool surface: high-level vs low-level
This is the single highest-leverage design decision in an MCP server. You can auto-generate one tool per REST endpoint (cheap, mechanical, breadth-first) or you can hand-shape composed tools that bundle several calls into one agent-callable action (expensive, opinionated, depth-first). The Zernio pattern is auto-generated: 300+ tools across 15 platforms, one per endpoint, generated from a unified OpenAPI spec. Fintalio’s pattern is curated: 19 tools, each one matching how an agent thinks about LinkedIn outreach, not how the underlying API is sliced.
The auto-generated approach wins on coverage. The curated approach wins on tool-selection accuracy. LLMs read description fields to pick tools. A tool called list_contacts with the description “List the user’s contacts. Paginated, filterable by status, group, or search” is unambiguous. A tool called users_v2_get with the description “GET /api/v2/users” is not. Tool naming and description quality matter more than tool count.
Authentication: bearer tokens or OAuth
Two patterns dominate. Personal access tokens (Laravel Sanctum-style, GitHub-PAT-style) are simple bearer credentials the user generates from a settings page and pastes into the MCP host config. OAuth is the full handshake, with authorization codes, redirect URIs, refresh tokens, and consent screens. Fintalio uses Sanctum personal access tokens because the host is the user’s own machine, the credential is user-scoped (not application-scoped), and the install flow needs to be one paste.
If you target ChatGPT’s MCP support specifically, some configurations require OAuth. Claude Desktop, Cursor, Windsurf, and Claude Code all accept bearer tokens directly. Pick the simpler path unless your host forces the harder one.
How does tools/list work in practice?
tools/list is the JSON-RPC method an MCP host calls right after initialize to discover the server’s tool surface. The host caches the response for the session and feeds the tool names plus descriptions into the LLM’s context window. When the model decides to call a tool, the host sends tools/call with the chosen tool name and arguments. This pair of methods is the entire agent loop.
The client request is small. It’s the response that matters, because the response is what gets indexed, quoted, and (for AI-citation purposes) what determines whether an LLM picks your tool or somebody else’s.
The client request
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/list",
"params": {}
}
That’s the entire payload. The host sends it over HTTP POST to https://fintalio.com/mcp with the Authorization: Bearer <token> header.
The server response: Fintalio’s 19 tools
Below is the actual tools/list response Fintalio returns to an authenticated client, abbreviated to three representative tools (list_contacts, create_contact, launch_sequence) so the payload is readable. The full response includes all 19 tools defined in app/Mcp/Servers/FintalioServer.php: nine read tools, nine write tools, and one execute tool. Every name, description, and input schema below comes verbatim from the production code:
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"tools": [
{
"name": "list_contacts",
"description": "List the user's contacts. Paginated (max 50 per page). Filter by status, group, or search.",
"inputSchema": {
"type": "object",
"properties": {
"status": { "type": "string" },
"group_id": { "type": "integer" },
"search": { "type": "string" },
"limit": { "type": "integer" },
"offset": { "type": "integer" }
}
}
},
{
"name": "create_contact",
"description": "Create a contact (single) from a LinkedIn URL plus identity. Upserts the underlying Prospect by linkedin_id (slug) so two callers with the same URL share one Prospect. Returns the existing contact unchanged if it already exists for the current user. For bulk imports, prefer parse_csv + commit_csv.",
"inputSchema": {
"type": "object",
"properties": {
"linkedin_url": { "type": "string" },
"first_name": { "type": "string" },
"last_name": { "type": "string" },
"full_name": { "type": "string" },
"title": { "type": "string" },
"company_name": { "type": "string" },
"custom_data": { "type": "object" }
}
}
},
{
"name": "launch_sequence",
"description": "Launch a sequence template against a list of contacts or a contact group. Without confirm=true, returns a dry-run preview (target count, first 5 names, template name). With confirm=true, requires the sequences:execute scope and triggers real LinkedIn sends. Each existing active sequence for the same (user, contact, template) is skipped.",
"inputSchema": {
"type": "object",
"properties": {
"template_id": { "type": "integer" },
"contact_ids": { "type": "array" },
"contact_group_id": { "type": "integer" },
"confirm": { "type": "boolean" }
}
}
}
]
}
}
Three things are worth noticing in that payload. First, every tool name is snake_case and reads like a verb plus object: an LLM trained on millions of API surfaces parses these without ambiguity. Second, every description includes both the happy path and the constraints. create_contact explains the upsert semantics in the description because the LLM uses that text to decide whether to call create_contact or parse_csv. Third, launch_sequence exposes its dry-run flag (confirm: false) so the agent can preview a send before triggering real LinkedIn writes. That preview-then-confirm pattern is the single most important design choice for any tool that touches a user’s LinkedIn account.
The full Fintalio surface
For completeness, the 19 tools are:
- Read (9):
list_contacts,get_contact,list_contact_groups,list_sequences,get_sequence,list_sequence_templates,get_sequence_template,list_variables,get_account_status. - Write (9):
create_contact_group,update_contact,pause_sequence,resume_sequence,stop_sequence,parse_csv,commit_csv,create_sequence_template,create_contact. - Execute (1):
launch_sequence(the only one subscription-gated).
Plus three resources: ContactResource, SequenceResource, TemplateResource. Resources are MCP’s read-only data surfaces, the host subscribes to them for state rather than polling tools.
How does the host implement bootstrap?
In Laravel with the official laravel/mcp package, the server class is the declaration. Tools are registered as class references and the framework introspects them at request time:
<?php
// app/Mcp/Servers/FintalioServer.php
namespace App\Mcp\Servers;
use Laravel\Mcp\Server;
use Laravel\Mcp\Server\Attributes\Instructions;
use Laravel\Mcp\Server\Attributes\Name;
use Laravel\Mcp\Server\Attributes\Version;
#[Name('Fintalio')]
#[Version('0.0.1')]
#[Instructions('LinkedIn prospecting platform: contacts, sequences, templates.')]
class FintalioServer extends Server
{
public int $defaultPaginationLength = 100;
public int $maxPaginationLength = 200;
protected array $tools = [
\App\Mcp\Tools\Read\ListContacts::class,
\App\Mcp\Tools\Read\GetContact::class,
\App\Mcp\Tools\Read\ListContactGroups::class,
\App\Mcp\Tools\Read\ListSequences::class,
\App\Mcp\Tools\Read\GetSequence::class,
\App\Mcp\Tools\Read\ListSequenceTemplates::class,
\App\Mcp\Tools\Read\GetSequenceTemplate::class,
\App\Mcp\Tools\Read\ListVariables::class,
\App\Mcp\Tools\Read\GetAccountStatus::class,
\App\Mcp\Tools\Write\CreateContactGroup::class,
\App\Mcp\Tools\Write\UpdateContact::class,
\App\Mcp\Tools\Write\PauseSequence::class,
\App\Mcp\Tools\Write\ResumeSequence::class,
\App\Mcp\Tools\Write\StopSequence::class,
\App\Mcp\Tools\Write\ParseCsv::class,
\App\Mcp\Tools\Write\CommitCsv::class,
\App\Mcp\Tools\Write\CreateSequenceTemplate::class,
\App\Mcp\Tools\Write\CreateContact::class,
\App\Mcp\Tools\Execute\LaunchSequence::class,
];
protected array $resources = [
\App\Mcp\Resources\ContactResource::class,
\App\Mcp\Resources\SequenceResource::class,
\App\Mcp\Resources\TemplateResource::class,
];
}
Note the explicit defaultPaginationLength = 100 and maxPaginationLength = 200. The package’s defaults (15 and 50) silently truncate the tool list, and many MCP clients do not follow nextCursor. They treat the truncated first page as the full surface. If your server has more than 15 tools, raise the pagination defaults or your clients will miss half your tools without noticing.
A single tool implementation looks like this. The #[Name] and #[Description] attributes are what the framework emits in tools/list:
<?php
// app/Mcp/Tools/Read/ListContacts.php (abridged)
namespace App\Mcp\Tools\Read;
use Illuminate\Contracts\JsonSchema\JsonSchema;
use Laravel\Mcp\Request;
use Laravel\Mcp\Response;
use Laravel\Mcp\Server\Attributes\Description;
use Laravel\Mcp\Server\Attributes\Name;
use Laravel\Mcp\Server\Tool;
#[Name('list_contacts')]
#[Description('List the user\'s contacts. Paginated (max 50 per page). Filter by status, group, or search.')]
class ListContacts extends Tool
{
public function handle(Request $request): Response
{
$userId = auth()->id();
$limit = min((int) ($request->get('limit') ?? 50), 50);
$offset = max((int) ($request->get('offset') ?? 0), 0);
// ... query, filter, paginate ...
return Response::json([
'contacts' => $contacts,
'total' => $total,
'limit' => $limit,
'offset' => $offset,
]);
}
public function schema(JsonSchema $schema): array
{
return [
'status' => $schema->string(),
'group_id' => $schema->integer(),
'search' => $schema->string(),
'limit' => $schema->integer(),
'offset' => $schema->integer(),
];
}
}
The Node/TypeScript and Python SDKs from the official modelcontextprotocol/servers repo follow the same shape: declare a name, a description, an input schema, and a handler. The framework wires the rest.
How do you back an MCP server with LinkedIn data?
This is the question nobody answers honestly in the marketing materials. There are two paths and the engineering cost between them differs by an order of magnitude. Pick wrong and you’ll burn a quarter on infrastructure that should not be on your roadmap.
Path 1: build your own LinkedIn data layer
This means proxy farms, residential IP rotation, mobile session emulation, anti-detection headers, OAuth-on-behalf flows, cookie management across user sessions, login-challenge handling, two-factor handling, account-restriction recovery, and roughly two to four years of cat-and-mouse with LinkedIn’s anti-automation team. The companies that own this layer (Apify, the major LinkedIn-data vendors, the larger scraping platforms) spent six to ten years building it and continue spending most of their engineering budget keeping it working. If your team’s core competency is not LinkedIn session handling, this is the wrong path.
Path 2: wrap a hosted LinkedIn data layer
This means picking a third-party LinkedIn data provider, integrating against their API, and shaping MCP tools on top of their endpoints. The provider handles sessions, proxies, anti-detection, and the LinkedIn surface (search, messaging, connections, InMail, Sales Navigator). You handle the MCP shape, the agent-callable tool design, audit logging, and your own users. The integration is weeks, not years. Fintalio took this path. The hosted LinkedIn session layer underneath is generic, several vendors compete in the category, and the choice of provider is an implementation detail your agent does not need to know.
What about the official LinkedIn API?
The official LinkedIn Developer Platform (developers.linkedin.com) covers posting, comments, ads, Marketing API, and partner-tier features that require Marketing Developer Platform approval. It does not cover search, messaging, connections, or InMail outside extremely narrow Recruiter and Sales Navigator partner integrations. If your agent only needs to publish posts and run ad campaigns, the official API is the cleanest path and Composio already ships an MCP wrapper around it. If your agent needs the write surface that defines a real LinkedIn integration, the official API alone is insufficient.
The decision tree
- Posting + comments + ads only: official LinkedIn API. Use Composio’s MCP wrapper. Shortest path, zero session-handling risk.
- Messaging + connections + search + sequences: hosted LinkedIn data layer plus a custom MCP shape. This is the Fintalio pattern. Weeks of integration work, decades of session-handling outsourced.
- Everything LinkedIn does, low-level, multi-platform: generalist multi-channel API providers expose 500+ endpoints across LinkedIn plus other channels. Pair with an auto-generated MCP if you need breadth over agent ergonomics.
- Read-only profile lookups at scale: Proxycurl and similar profile-data APIs. These are not write-surface providers, do not try to send messages with them.
The right answer depends on which slice of LinkedIn your agent actually needs. Most “I need a LinkedIn MCP” requests collapse to “I need messaging and search,” which is the slice the hosted-data-layer approach was designed for.
Rate limits, audit logs, and the production checklist
Moving from “demo on my laptop” to “production with real users” surfaces a different set of problems. Rate limits cascade through three layers (your MCP, your LinkedIn data provider, LinkedIn itself). Audit logs are not optional when an agent is touching real LinkedIn accounts. Idempotency is the difference between “agent retried after a timeout” and “user got the same message three times.” This section is the checklist.
Per-token rate limiting at the MCP layer
Fintalio caps the MCP endpoint at 120 requests per minute per Sanctum token. That’s the throttle:120,1 middleware in the route definition. The cap is high enough that an interactive agent never hits it (a Claude Desktop conversation makes maybe 5 to 20 calls per minute at peak) and low enough to catch runaway loops:
<?php
// routes/ai.php
Mcp::web('/mcp', FintalioServer::class)
->middleware(['auth:sanctum', 'throttle:120,1']);
Pick the cap based on how many tools your agent typically chains. If a single user prompt fans out to 30 tool calls, 120 per minute is fine. If you expect 300 fan-out, raise it. The throttle exists to bound damage, not to ration normal use.
What the LinkedIn data provider rate-limits
Independent of your MCP throttle, the provider underneath enforces its own limits. Typical patterns: roughly 50 sequenced actions per LinkedIn account per day (connect requests, messages, profile visits combined), with burst limits inside that day budget. These limits exist because LinkedIn itself rate-limits sessions, and exceeding the daily window is how accounts get restricted. Your MCP should surface the remaining-budget number to the agent so the LLM can plan its day. The get_account_status tool in Fintalio exists exactly for this: an agent calls it first, sees how many actions are left, and decides whether to launch a sequence today or tomorrow.
Graceful degradation: 429 + Retry-After
When the underlying LinkedIn data layer returns a 429 or the daily quota is exhausted, your MCP should propagate a 429 with a Retry-After header. The MCP host’s retry logic will back off automatically. Do not retry inside the MCP tool handler. The agent might decide to call a different tool, ask the user, or wait until tomorrow. Hiding the rate limit behind silent in-server retries breaks that decision:
<?php
// Inside a tool handler when the underlying provider rate-limits us
if ($providerResponse->status() === 429) {
return Response::error(
'LinkedIn provider rate limit reached. Retry after 60 seconds.',
['Retry-After' => 60]
);
}
Audit logs: every call, every token, every parameter
Every MCP write call should land in an audit table with at least (token_id, tool_name, params_hash, status, latency_ms, created_at). Two reasons. First, debugging: when an agent does something unexpected, the audit log tells you which token issued the call and exactly what parameters were passed. Second, account safety: if a LinkedIn account gets restricted, the audit log is how you find out which agent action triggered the review. Hash the params, don’t store them raw. PII compliance matters and the hash is enough to correlate calls.
Idempotency for retryable writes
Agents retry. The MCP host retries on transport errors. The user retries when they think a tool call hung. Without idempotency keys on writes, retries duplicate. Fintalio’s Message model has a dedicated idempotency key column (migration add_idempotency_to_messages.php), and any write tool that creates a side effect should accept an idempotency_key parameter, hash it with the user ID, and short-circuit duplicate calls. The pattern is the same one Stripe uses, for the same reason.
Subscription gating on the one tool that costs money
In Fintalio, launch_sequence is the one subscription-gated tool. Read tools and most write tools (creating contacts, drafting templates) work for any authenticated user. Sending real LinkedIn messages costs money on the underlying provider and gets gated behind Stripe:
<?php
// app/Mcp/Tools/Execute/LaunchSequence.php (abridged)
if (! auth()->user()->subscribed('default')) {
return Response::error('Subscription required to launch a sequence');
}
The gate sits inside the tool handler, not the route, because the dry-run mode (confirm: false) is free. Only the real send (confirm: true) requires the subscription. That dry-run preview is one of the most useful design patterns in the Fintalio surface: the agent can show the user exactly what would happen before any money or LinkedIn quota is spent.
How does an MCP host connect to your server?
Once your MCP server is running, the host configuration is one block of JSON. The same config shape works for Claude Desktop, Cursor, Windsurf, and Claude Code. Three hosts, one transport, one auth header. This convergence is why MCP shipped to a real ecosystem so quickly.
The Claude Desktop config
On macOS, the file lives at ~/Library/Application Support/Claude/claude_desktop_config.json. On Windows it’s %APPDATA%\Claude\claude_desktop_config.json. The block looks like this:
{
"mcpServers": {
"fintalio": {
"url": "https://fintalio.com/mcp",
"headers": {
"Authorization": "Bearer YOUR_TOKEN"
}
}
}
}
That’s it. Restart Claude Desktop fully (Cmd+Q on macOS, not just close the window) and the tools appear in the conversation’s tools panel. The first request the host issues is initialize. The second is tools/list. From that point forward, every tool call routes through tools/call with the chosen tool name and arguments.
Cursor and Windsurf
Same JSON block, different config file path. Cursor’s MCP settings UI accepts the same shape pasted directly. Windsurf reads ~/.codeium/windsurf/mcp_config.json with an identical structure. The convergence is deliberate: hosts compete on UX, not on protocol fragments.
How to verify the connection
In Claude Desktop, click the tools icon at the bottom of a new conversation. Your server’s tools should appear, listed by name with their descriptions visible on hover. If the list is empty, the most common failure is a malformed JSON config (missing comma, unquoted key). The second most common is an expired token. The third is a tools/list response that the host’s pagination logic rejects, which is why you set defaultPaginationLength high in the server class.
For a hands-on walkthrough with screenshots and a working prompt, see the Claude Desktop + LinkedIn agent tutorial.
What you should NOT build into your MCP server
This section is the part of the article most LinkedIn-MCP vendors will not write. Some integrations are technically possible and operationally radioactive. Building them into your MCP signals to LinkedIn that your account is automated, accelerates restriction, and ends in a flagged session. The responsible LinkedIn-MCP surface is narrower than the maximum technically achievable one.
Feed scraping at scale
Reading a logged-in user’s home feed programmatically is one of the strongest signals an automated session emits. LinkedIn’s anti-automation systems weight session-pattern anomalies (unusual scroll velocity, off-hours activity, missing organic engagement) heavily when deciding which accounts to flag. If your agent needs feed context, ask the user to paste the relevant post URL into the prompt and use a profile-data API to look it up explicitly. Do not build a read_feed tool.
Profile scraping at scale
Profile lookups against arbitrary public URLs are fine through dedicated read-only profile APIs (the Proxycurl pattern). Doing the same lookups inside the user’s authenticated session as a “profile_scrape” MCP tool inherits all the session-restriction risk and gains nothing. The categories are separate for a reason. Keep them separate in your tool surface.
“Auto-post 100 times per day”
The agents most likely to break a LinkedIn account are the ones that try to industrialize content output. Daily message and connection limits exist because LinkedIn’s growth team set them. Treat the published limits as ceilings, not targets. Fintalio caps each plan at 50 daily messages and 50 daily connections per the underlying provider’s limits (config/plans.php). Design for human-rate behavior, not bot-rate.
Aggressive InMail and connection-request bursts
Connection requests have the lowest tolerance threshold on LinkedIn’s side. A burst of 100 requests in 10 minutes triggers an immediate restriction. Spread requests across the workday, randomize intervals, respect declined connections (do not re-request someone who already declined), and surface remaining budget to the agent via get_account_status so the LLM plans intelligently.
The responsible MCP surface is messaging plus connection requests plus search plus a small write set for contacts and sequences. Anything beyond that is a footgun. Building the maximum technically possible surface is not engineering, it is a way to lose your users’ LinkedIn accounts at scale.
A working production reference: Fintalio’s 19-tool surface
Fintalio runs the patterns described above in production. The server is at https://fintalio.com/mcp. The transport is HTTP plus SSE. The auth is Sanctum bearer tokens. The throttle is 120 requests per minute per token. The tool surface is exactly 19 tools, declared in app/Mcp/Servers/FintalioServer.php, no more, no less.
The composed-tool pattern in action
The clearest example of the curated-vs-auto-generated tradeoff is launch_sequence. The underlying operation requires loading a sequence template, resolving a target group of contacts (either by IDs or by group), checking which contacts already have an active or terminal sequence for this template, presenting a dry-run preview, then on confirmation creating one Sequence record per contact and dispatching a ProcessSequenceStep job per record. In a low-level MCP, that’s six tool calls the LLM has to chain in the right order. In Fintalio’s surface, it’s one tool with a confirm: false dry-run and a confirm: true commit. The agent reasons about the action (“launch this sequence”), not the API (“create N records of type Sequence and dispatch N jobs”).
Three other tools worth noting
parse_csv+commit_csvas a pair. Two tools, intentionally split, so the agent shows the user a preview before committing the import. Same dry-run-then-confirm pattern aslaunch_sequence.get_account_statusas the planner’s friend. Returns daily remaining budget for messages and connections. The agent calls this before planning a multi-step LinkedIn campaign so the LLM can schedule across days.list_variables. Lets the agent see what template variables (first name, company, role) are available before drafting acreate_sequence_templatecall with personalization tokens.
Pricing context
Fintalio ships a single plan at €69 per month (config/plans.php). MCP access is bundled. There is no separate MCP tier, no per-call pricing, no usage-based metering on the published plan. The token throttle (120/min) is the only quota. If you’d rather use a working reference than build your own, register for an account and the token plus one paste into your Claude Desktop config gets you to a running LinkedIn agent in five minutes.
For the cross-server comparison (Fintalio vs the broader MCP ecosystem, vs the multi-platform aggregators), see the 10 MCP servers every AI engineer should know roundup.
FAQ
How long does it take to build a LinkedIn MCP server from scratch?
Four to six weeks if you wrap a hosted LinkedIn data layer and focus on the MCP shape (tool design, auth, audit logs, rate limits). Twelve to eighteen months if you also build the LinkedIn session layer underneath (proxies, anti-detection, OAuth-on-behalf, restriction recovery). The category split is real and the engineering effort differs by an order of magnitude.
What is the difference between an MCP server and a REST API?
A REST API exposes resources over HTTP with verbs (GET, POST, PUT, DELETE). An MCP server exposes agent-callable tools over JSON-RPC 2.0 with two methods (tools/list for discovery, tools/call for invocation). A REST API becomes an MCP server when you wrap it in the JSON-RPC shape, add a tools/list response with named tools, and curate the tool surface to read like agent-shaped actions rather than CRUD endpoints.
Can I use the official LinkedIn API to build a write-surface MCP?
Partially. Posting, comments, advertising, and Marketing API actions are supported through LinkedIn’s official Developer Platform (developers.linkedin.com) with appropriate scopes and Marketing Developer Platform approval. Search, messaging, connection requests, and InMail are not generally available through the official partner API. For those, you need a hosted LinkedIn data layer underneath your MCP.
Do I need OAuth, or are bearer tokens enough?
It depends on the MCP host. Claude Desktop, Cursor, Windsurf, and Claude Code accept bearer tokens (Sanctum-style or GitHub-PAT-style) directly in the config file. ChatGPT’s MCP support requires OAuth in some configurations. If you target only the bearer-token hosts, ship bearer tokens, the install flow is one paste. If you target ChatGPT specifically, build OAuth, the spec defines the handshake in detail.
How does an LLM know which tool to call?
The LLM reads the description field from your tools/list response. Good descriptions cause correct tool selection. Bad descriptions cause the model to call the wrong tool or to ask the user instead of acting. This is the single highest-leverage decision in MCP design. Write descriptions that include both the happy path and the constraints. The model uses both.
What happens if my LinkedIn account hits a rate limit mid-agent-loop?
Return a 429 with a Retry-After header from your MCP tool. The host’s retry logic backs off automatically. Do not retry inside the MCP tool handler, the agent might decide to ask the user, switch tasks, or wait until tomorrow. Hiding the rate limit behind silent server-side retries breaks that decision. Surface the throttle, let the agent plan around it.
Should I expose every endpoint as a separate tool, or compose them?
Compose them. Auto-generated MCPs (one tool per REST endpoint) maximize coverage but force the LLM to chain calls in the right order, which is exactly the kind of reasoning LLMs are worst at. Composed tools (launch_sequence instead of six separate calls) make tool selection unambiguous and tool calls successful on the first try. The Fintalio surface is 19 composed tools across what would be roughly 60 to 80 endpoints in a low-level wrapping. The coverage tradeoff is worth it.
Get started
Building a LinkedIn MCP server in 2026 is not a research project. The spec is stable, the SDKs are mature (laravel/mcp, the official modelcontextprotocol/python-sdk and modelcontextprotocol/typescript-sdk at github.com/modelcontextprotocol/servers), and the host ecosystem (Claude Desktop, Cursor, Windsurf, Claude Code, the OpenAI Agents SDK) has converged on one transport and one config shape. The hard problems are not protocol-shaped, they are LinkedIn-shaped: session handling, anti-automation, rate limits, audit logs, idempotency.
If you want to build it, the reference implementation patterns above hold: one HTTP route, JSON-RPC 2.0 for the wire, bearer tokens for auth, composed tools instead of low-level endpoints, dry-run-then-confirm for any destructive write, audit logs on every call, and a hard cap on the surface (the responsible 19 tools, not the maximum-possible 80). The Fintalio server is the working reference, 19 tools shipping in production at https://fintalio.com/mcp, with the source-of-truth tool list in app/Mcp/Servers/FintalioServer.php.
If you’d rather skip the build, create a Fintalio account, generate a token in Settings, paste the mcpServers block into your Claude Desktop config, and your LLM is talking to LinkedIn in five minutes. Single plan, €69 per month, MCP access bundled.
Read next
- The Complete Guide to Building LinkedIn-Powered AI Agents with MCP - the pillar guide covering the LinkedIn MCP category, host comparisons, and the production checklist.
- Build a LinkedIn Outreach Agent in 30 Minutes with Claude Desktop + Fintalio MCP - the hands-on install tutorial with screenshots and a working prompt.
- 10 MCP Servers Every AI Engineer Should Know - the honest cross-category roundup for picking the right MCP for your agent.
- Or browse the full Fintalio blog index for engineering deep-dives, comparisons, and production guides.
Plug LinkedIn into your AI agent
Fintalio is the MCP server for LinkedIn. Connect Claude, Cursor, or your custom agent and ship outreach workflows in minutes — with audit logs and rate-limit awareness baked in.
Get started