How to do it
- Create an agent key In https://app.socialhandoff.com, open Connections and add an agent for this LangChain app. Grant draft and, if needed, schedule and analytics.
- Install the packages Run pip install langchain langchain-mcp-adapters plus the integration package for your chat model provider.
- Set the key as an environment variable Export SOCIAL_AGENT_KEY in the environment where the agent runs. Keep it out of source control.
- Load the tools Create a MultiServerMCPClient with transport streamable_http, the /mcp URL, and the Authorization header, then await get_tools().
- Run the agent Pass the tools to create_agent with your model and invoke it. Check the drafts in the SocialHandoff inbox.
import asyncio
import os
from langchain.agents import create_agent
from langchain_mcp_adapters.client import MultiServerMCPClient
async def main():
client = MultiServerMCPClient({
"social": {
"transport": "streamable_http",
"url": "https://app.socialhandoff.com/mcp",
"headers": {"Authorization": "Bearer " + os.environ["SOCIAL_AGENT_KEY"]},
}
})
tools = await client.get_tools()
agent = create_agent("openai:gpt-4.1", tools)
result = await agent.ainvoke({
"messages": [{"role": "user", "content": "Read brand memory, then draft a LinkedIn post about our 2.0 release. Draft only."}]
})
print(result["messages"][-1].content)
asyncio.run(main()) What the adapter gives you
MultiServerMCPClient connects to one or more MCP servers and converts each tool into a LangChain tool with its name, description, and input schema. The SocialHandoff server is stateless and answers plain JSON over streamable HTTP, so each tool call is a single request carrying your bearer key. Swap the model string for any chat model LangChain supports; the tools do not care which model calls them.
Pick the tools you expose
Every tool you hand the model is one it might call. For a writing agent, filter the list to what it needs:
get_brand_memoryto load voice rules and banned topics.draft_postto create a pending post.preview_poststo check per-network length and alt text before finishing.list_postsso it does not repeat a post already in the queue.
Leave out schedule_post unless the key has schedule permission and you want the agent picking times. approve_post will return 403 for an agent key anyway.
Patterns that work
- Research then draft: a retrieval step pulls your latest docs or release notes, then the agent drafts per network. The draft stays pending until someone approves it.
- LangGraph interrupt plus inbox: pause the graph after drafting to show a reviewer the preview, then let the SocialHandoff inbox hold the final approval and publishing.
- Batch jobs: loop over a list of topics and call
draft_postfor each. Add anidempotencyKeyif you call the REST API directly for batch work, so reruns do not duplicate drafts.
REST instead of MCP
If you prefer plain HTTP, wrap POST https://app.socialhandoff.com/v1/posts in a LangChain tool yourself. The OpenAPI spec at /openapi.json describes the body. MCP is less code and gets tool descriptions for free.
Troubleshooting
- 401: the header is missing or the environment variable is empty in the process that runs the agent.
- 422 in a tool result: a guardrail rejected the draft. The message says why, and most models will revise and retry.
- Agent loops on draft_post: set a recursion limit, and rely on the per-account daily limit guardrail as a backstop.
- Transport errors: use
streamable_http(with an underscore) as the transport name.
Network examples: LangChain for X, LangChain for LinkedIn, LangChain for Bluesky, LangChain for Mastodon, and LangChain for Threads.