Claude cannot post to LinkedIn or any other network on its own. It has no accounts and no OAuth tokens. What it can do is call a tool, and an MCP server is how you hand it one. This guide builds a small MCP server that lets Claude list your connected accounts and schedule a post, then covers the design rules that matter more for posting than for almost any other tool: a wrong call here is a public post you did not mean to make.
An MCP server is a small program that describes a set of tools — their names, parameters and descriptions — over the Model Context Protocol, so any MCP-capable client can discover and call them. Claude Desktop and Claude Code both speak it. You do not write per-framework wrappers, because the protocol is the wrapper.
#Quick answer
- Can Claude post to social media? Only through a tool. An MCP server gives it one:
list_accounts,schedule_post, and a way to check what is already queued. - What does the server hold? The API key for a posting API. The platform OAuth tokens stay with that API, never with the model.
- Local or remote? stdio for a server on your own machine, streamable HTTP for a hosted one.
- Hardest part? Not the protocol. It is tool descriptions and guard rails, so the model never guesses an account or posts twice.
- Should you build one? Build it when the posting logic is yours. If you only need Claude to post, connect an existing posting MCP server instead.
#What MCP actually is
The Model Context Protocol is an open standard, originally from Anthropic, for connecting AI clients to tools. It defines three things a server can expose:
- Tools — actions the model can call.
schedule_post,list_accounts,upload_media. - Resources — read-only context the model can pull in. A file, a config, a list of accounts.
- Prompts — reusable templates the client can offer the user.
The value is not technical novelty. It is that you describe your tools once and every MCP client can use them. Before MCP you wrote a LangChain tool, then an OpenAI function schema, then something bespoke for the next framework. Now you write one server.
#Two transports, and which to pick
stdio — the client launches your server as a subprocess and talks over standard input/output. Simplest to build, runs on the user's machine, has access to their filesystem. Right for developer tooling, local scripts, anything touching local files.
Streamable HTTP — your server runs somewhere and clients connect over HTTPS with headers for auth. Right for anything multi-user or hosted, because you are not asking every user to run your code locally.
If you are exposing a SaaS API, you want HTTP. If you are exposing the user's own machine, you want stdio.
#Building one
The reference SDKs are @modelcontextprotocol/sdk for TypeScript and mcp for Python. Here is a minimal stdio server in TypeScript with two posting tools. It talks to a placeholder posting API at api.example.com; point it at whichever posting API holds your accounts.
1import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
3import { z } from "zod";
4
5const API = "https://api.example.com";
6const headers = {
7 Authorization: `Bearer ${process.env.API_KEY}`,
8 "Content-Type": "application/json",
9};
10
11const server = new McpServer({ name: "social-posting", version: "1.0.0" });
12
13server.tool(
14 "list_accounts",
15 "List the social accounts this user has connected, with their IDs and platforms. " +
16 "Call this before schedule_post. Never guess an account ID.",
17 {},
18 async () => {
19 const res = await fetch(`${API}/accounts`, { headers });
20 return { content: [{ type: "text", text: JSON.stringify(await res.json()) }] };
21 },
22);
23
24server.tool(
25 "schedule_post",
26 "Schedule a text post to one connected account. " +
27 "Use when the user asks to post or schedule something. " +
28 "Without scheduledAt the post is saved as a draft for the user to review.",
29 {
30 accountId: z.string().describe("An ID returned by list_accounts"),
31 text: z.string().describe("The post text, within the platform's character limit"),
32 scheduledAt: z
33 .string()
34 .optional()
35 .describe("ISO-8601 time with a timezone offset, e.g. 2026-10-21T09:00:00+06:00"),
36 },
37 async ({ accountId, text, scheduledAt }) => {
38 const res = await fetch(`${API}/posts`, {
39 method: "POST",
40 headers,
41 body: JSON.stringify({ accountId, text, scheduledAt, draft: !scheduledAt }),
42 });
43 const post = await res.json();
44 return {
45 content: [{ type: "text", text: JSON.stringify({ id: post.id, status: post.status }) }],
46 };
47 },
48);
49
50await server.connect(new StdioServerTransport());
Register it in Claude Desktop's claude_desktop_config.json:
1{
2 "mcpServers": {
3 "social-posting": {
4 "command": "node",
5 "args": ["/absolute/path/to/server.js"],
6 "env": { "API_KEY": "..." }
7 }
8 }
9}
Restart the client. Ask Claude "schedule a LinkedIn post for tomorrow at 9am about our launch" and it should call list_accounts first, then schedule_post.
For a hosted server the shape is the same but the client config points at a URL:
1{
2 "mcpServers": {
3 "schedulenchill": {
4 "url": "https://schedulenchill.com/mcp",
5 "headers": { "Authorization": "Bearer YOUR_API_KEY" }
6 }
7 }
8}
#The part that actually decides whether it works
The protocol is easy. Tool design is where MCP servers succeed or fail, because the model reads your descriptions and nothing else.
Descriptions are prompts, not documentation. "Creates a post" tells the model almost nothing. "Schedule a text post to one connected account. Use when the user asks to post or schedule something. Without scheduledAt the post is saved as a draft." tells it what the tool does, what happens by default, and when to reach for it. That last clause prevents most wrong-tool calls.
Never let the model invent identifiers. If a tool takes an account ID, ship a second tool that lists valid accounts. Asked to post to "the company LinkedIn" with no list available, a model will confidently make an ID up. Our server has get_accounts for exactly this reason, and the guidance in every integration doc is to call it at the start of a run.
Make destructive actions hard to reach. A delete_everything tool with a friendly description will eventually get called. Prefer reversible operations, and where you cannot, add a confirmation parameter the model must set deliberately.
Return structured, compact data. Everything you return costs context. Dumping a 200-field JSON object when the model needed three fields degrades the whole conversation.
Give it read tools, not just write tools. An agent that can only act is worse than one that can look first. Ours has list_scheduled and get_post alongside schedule_post, so the agent can check what already exists before adding to it — which is what stops duplicate posts.
#What a posting tool needs that most tools do not
A wrong database query returns a bad answer. A wrong post goes out under your name. Five rules follow from that.
Never publish because a parameter was missing. Make "draft" the safe outcome, or make the timing parameter required. Our own schedule_post has no default: the caller must pass draft, now, next_slot or an ISO-8601 time, so a forgotten argument cannot publish anything.
Ask for a time with a timezone. "Tomorrow at 9" means nothing to a server. Ask for ISO-8601 with an offset and say so in the parameter description, or the post goes out at 9am UTC.
Make retries safe. If a request times out after the platform accepted the post, a blind retry posts twice. Give the agent a way to look first (a list_scheduled or get_post tool) so it can check before it tries again.
Upload media as its own step. Have one tool upload a file and return an ID, and let schedule_post take that ID. Ours works this way: upload_media returns a media ID that goes in media_ids.
Let the model see each platform's rules. Character limits and media rules differ by network. Ours exposes them through get_capabilities, so the model can check a draft before it posts. Make removal reversible too: our cancel_post and delete_post soft-delete, and restore_post brings a post back.
#Testing
Use the MCP Inspector (npx @modelcontextprotocol/inspector) to call tools directly without going through a model. It catches schema and transport bugs fast.
Then test with an actual model, because the failure modes are different. The inspector proves the tool works; only a model shows you that your description was ambiguous and it picks the wrong tool half the time.
This guide stays on posting tools. For a general MCP server built from scratch, plus the OAuth, transport and wrong-tool failures that only appear once a real model calls it, see Zakir's write-up on the five things that broke his MCP server in production.
#When not to build one
If all you need is for Claude to post, you do not need to build anything. Connect a posting MCP server that already holds the platform integrations. A community wrapper around someone else's API will drift out of date the moment that API changes, and you will be maintaining it.
Build your own when the posting logic is genuinely yours: an approval step your team needs, your own content rules, or posting from inside your product. That is the case MCP is for.
Our own server is a worked example of the hosted pattern: 22 tools, four resources and two prompts, maintained in the same codebase as the REST API so the two cannot drift apart. It is free, and it posts to LinkedIn, Bluesky, Mastodon, YouTube and TikTok. If you want to see the rules above in a real server rather than a toy one, it is documented in full.
#Frequently Asked Questions
What is an MCP server? A program that exposes tools, resources and prompts to AI clients over the Model Context Protocol, so any MCP-capable client can discover and call them without custom integration code.
Can Claude post to LinkedIn through an MCP server?
Yes. Claude calls a tool such as schedule_post, and the MCP server passes the request to a posting API that holds your LinkedIn connection. Claude never sees your LinkedIn password or token.
How do I add an MCP server to Claude?
Add an entry to your client's MCP config — claude_desktop_config.json for Claude Desktop, .mcp.json for Claude Code — with either a command for a local stdio server or a url plus headers for a remote one. Restart the client to load it.
What language should I write an MCP server in? Whatever your tools already live in. Official SDKs exist for TypeScript and Python; those are the smoothest, but the protocol is transport-agnostic.
Should I use stdio or HTTP transport? stdio for local developer tooling that touches the user's machine. Streamable HTTP for anything hosted or multi-user, since you do not want every user running your process locally.
Does an MCP server work with clients other than Claude?
Yes. MCP is an open standard. Any MCP-capable client can connect, and langchain-mcp-adapters converts MCP tools into LangChain tools.
How do I stop an agent posting the wrong thing? Write descriptions that say when to use the tool, not just what it does. Provide a list tool so the model picks a real account instead of guessing one, make draft the safe default, and keep publish-now and delete behind an explicit parameter.
How do I test an MCP server? Start with the MCP Inspector to verify tools and schemas directly, then test through a real model — ambiguous descriptions only show up when a model is choosing between tools.
#Where to go next
- The Schedule & Chill MCP server — all 22 tools, as a worked example
- What is a unified social media API? — where MCP fits into the bigger picture
- LinkedIn MCP server: the complete guide — a posting-specific worked example
- Claude integration guide — connect in two minutes
- Can AI agents post on social media?
- MCP documentation
