MCP server
Connect an agent to x.md over the Model Context Protocol, Streamable HTTP transport, no key required.
The x.md MCP server
https://x.pcstyle.dev/mcp is a Model Context Protocol server that exposes the same read-only X routes this documentation covers, as tools an agent can call directly.
| Endpoint | https://x.pcstyle.dev/mcp |
| Transport | Streamable HTTP |
| Authentication | None. Authorization: Bearer <api key> is optional and raises your search allowance |
| Protocol revisions | 2026-07-28, 2025-11-25, 2025-06-18, 2025-03-26 |
| Sessions | None. The server is stateless and never issues an Mcp-Session-Id |
| Server name | io.github.pc-style/x-md |
Connect
One URL, no key, no OAuth: point your client at https://x.pcstyle.dev/mcp and the five tools below show up. Pick your client.
claude mcp add --transport http x-md https://x.pcstyle.dev/mcpThen ask Claude Code to read an X post. It picks x_md_get_post on its own.
codex mcp add x-md --url https://x.pcstyle.dev/mcpCodex CLI, the IDE extension, and the ChatGPT desktop app share this configuration. The equivalent entry in ~/.codex/config.toml:
[mcp_servers.x-md]
url = "https://x.pcstyle.dev/mcp"Add to ~/.cursor/mcp.json for every project, or .cursor/mcp.json for one:
{
"mcpServers": {
"x-md": {
"url": "https://x.pcstyle.dev/mcp"
}
}
}Add to opencode.json in the project, or ~/.config/opencode/opencode.json globally:
{
"$schema": "https://opencode.ai/config.json",
"mcp": {
"x-md": {
"type": "remote",
"url": "https://x.pcstyle.dev/mcp"
}
}
}Add to ~/.gemini/config/mcp_config.json, or .agents/mcp_config.json in the workspace. Antigravity uses serverUrl, not url:
{
"mcpServers": {
"x-md": {
"serverUrl": "https://x.pcstyle.dev/mcp"
}
}
}amp mcp add x-md https://x.pcstyle.dev/mcpOr in ~/.config/amp/settings.json:
{
"amp.mcpServers": {
"x-md": {
"url": "https://x.pcstyle.dev/mcp"
}
}
}Most clients accept the common mcpServers shape with a Streamable HTTP url:
{
"mcpServers": {
"x-md": {
"type": "http",
"url": "https://x.pcstyle.dev/mcp"
}
}
}If your client asks for a transport, choose Streamable HTTP. There is no session and no login step.
Try it without a client
A plain tools/list from the command line, to see what a connected agent sees:
curl -sS -X POST https://x.pcstyle.dev/mcp \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}'
A client that sends Accept: text/event-stream gets the reply as a single SSE frame; anything else gets application/json. Both carry the same JSON-RPC envelope.
Tools
Every tool is read-only and annotated readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true. None of them post, reply, like, follow, or read protected accounts, direct messages, or Lists.
| Tool | Use it when | Arguments |
|---|---|---|
x_md_get_post |
You have an x.com or twitter.com status link and need its text, author, media, metrics, quoted post, or replies | url, or handle + id; format, thread, max_posts, context, replies, userinfo, full |
x_md_get_profile |
You need to know who an account is or what it has posted recently | handle; page, limit, cursor, full |
x_md_search_posts |
You need current public discussion on a topic, or to locate an account by name | q; feed, page, limit, cursor, full |
x_md_get_followers |
You need to sample or size an account’s audience | handle; page, limit, cursor, full |
x_md_get_following |
You need to map who an account pays attention to | handle; page, limit, cursor, full |
Each result carries the rendered Markdown as text content plus structuredContent matching the tool’s outputSchema. Paging works exactly as it does over HTTP: limit caps at 20, page caps at 10, and deep walks follow next_cursor.
curl -sS -X POST https://x.pcstyle.dev/mcp \
-H 'Content-Type: application/json' \
-H 'Accept: application/json' \
-d '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"x_md_get_profile","arguments":{"handle":"pcstyle53","limit":3}}}'
Resources
resources/list returns the static agent documents, so a client can read the service contract without an extra HTTP round trip of its own.
| URI | Type |
|---|---|
https://x.pcstyle.dev/llms.txt |
text/plain |
https://x.pcstyle.dev/llms-full.txt |
text/plain |
https://x.pcstyle.dev/openapi.json |
application/json |
https://x.pcstyle.dev/index.md |
text/markdown |
https://x.pcstyle.dev/mcp/server-card |
application/mcp-server-card+json |
Errors
Revision 2026-07-28 made the protocol stateless. There is no initialize handshake: a
client declares its version on every request, in the MCP-Protocol-Version header and in
params._meta under io.modelcontextprotocol/protocolVersion. Two more headers mirror the
body so a gateway can route and filter without parsing JSON-RPC: Mcp-Method on every
request, and Mcp-Name on tools/call and resources/read, carrying params.name or
params.uri. A name that is not plain ASCII travels as =?base64?<utf-8 base64>?= and is
decoded before comparison. Every mirrored header must agree with the body —
a mismatch is 400 with -32020, because a proxy routing on one while the server executes
the other is the split-brain that code exists to stop. An unsupported version is 400 with
-32022 and the list we do speak; an unimplemented method is 404 with -32601, so a
client can tell this endpoint from one that never hosted MCP. server/discover returns the
supported versions, capabilities, and identity in a single call; it exists only in this
revision, just as initialize and ping exist only before it, and asking for either from
the wrong era is -32601.
Results in that revision carry resultType: "complete", the server identity under
io.modelcontextprotocol/serverInfo, and — on server/discover, tools/list,
resources/list, resources/templates/list and resources/read — the ttlMs and
cacheScope freshness hints. The lists are compiled into the deployment and change only on
deploy, so they advertise an hour; a resource body is fetched live and advertises five
minutes. Nothing varies per caller, so every hint is cacheScope: "public".
Handshake-era clients are unaffected. initialize still negotiates 2025-11-25 and earlier
and gets exactly the result shape its own revision defines, with no resultType and no cache
hints. This server has always been session-less, which is the stateless core 2026-07-28
standardised, so both eras run through one dispatch.
Protocol mistakes come back as JSON-RPC errors: -32700 for an unparseable body, -32600 for a message that is not a JSON-RPC 2.0 request, -32601 for an unknown method, -32602 for an unknown tool, an unknown resource URI, or arguments that miss the schema, and -32603 for anything unexpected. An invalid-arguments error carries the tool’s inputSchema in error.data so a client can correct itself in one turn.
Upstream failures are different: a rate limit, a protected account, or a missing post comes back as a successful tools/call result with isError: true and a message naming the HTTP status, the machine code, and the seconds to wait. That keeps a recoverable X-side problem out of the transport layer.
Discovery
| Document | What it is |
|---|---|
/server.json |
MCP registry manifest |
/mcp/server-card |
Server card, served as application/mcp-server-card+json |
/.well-known/mcp/server-card.json |
The same card at the well-known path |
A plain GET https://x.pcstyle.dev/mcp returns that card plus the supported protocol revisions and a worked example, so the endpoint is legible before a client speaks JSON-RPC.
Deviations worth knowing
GET with Accept: text/event-stream answers 405: this server sends no unsolicited messages, so there is no standalone stream to open. The official SDK treats that as expected. DELETE answers 405 for the same reason sessions do not exist. JSON-RPC batching is rejected with -32600, having been removed from the protocol in revision 2025-06-18. Origin headers are not validated, because the endpoint is public, unauthenticated, and read-only, so there is nothing a rebound DNS name could reach.