MCP server
Give an agent the whole API in one line.
xdataapi.io serves a hosted MCP server at https://api.xdataapi.io/mcp.
Transport is Streamable HTTP. Nothing to install, nothing to run, and on most clients nothing to paste:
the server implements the MCP authorization flow, so a client signs you in through a browser and gets
its own credential. A key in the header works too, and is what a client with no browser needs.
Every tool call is one REST request under the hood, at the same price, through the same cache and rate limit.
The agent sees credits_charged and balance_remaining in every result.
Connect
Claude Code
claude mcp add --transport http xdataapi https://api.xdataapi.io/mcpClaude.ai and Claude Desktop
Add https://api.xdataapi.io/mcp as a custom connector in Settings. These clients have no field for a
request header, so this is the only route that reaches them.
Cursor, Windsurf, and other clients with an MCP config file
{
"mcpServers": {
"xdataapi": {
"url": "https://api.xdataapi.io/mcp"
}
}
}Clients that speak stdio only
The mcp-remote bridge speaks stdio to the client and Streamable HTTP to us, and signs in on the client's behalf.
{
"mcpServers": {
"xdataapi": {
"command": "npx",
"args": ["-y", "mcp-remote", "https://api.xdataapi.io/mcp"]
}
}
}Connect with a key instead
A client that cannot open a browser — a server, a container, a CI job — sends the key in the same header
the REST API takes, or as Authorization: Bearer xd_live_....
claude mcp add --transport http xdataapi https://api.xdataapi.io/mcp --header "x-api-key: xd_live_..."{
"mcpServers": {
"xdataapi": {
"url": "https://api.xdataapi.io/mcp",
"headers": { "x-api-key": "xd_live_..." }
}
}
}For mcp-remote, pass the key through the environment. The header is written with no space after the colon: mcp-remote splits the argument on the first colon, and a space there arrives as part of the key.
{
"mcpServers": {
"xdataapi": {
"command": "npx",
"args": ["-y", "mcp-remote", "https://api.xdataapi.io/mcp", "--header", "x-api-key:${XDATAAPI_KEY}"],
"env": { "XDATAAPI_KEY": "xd_live_..." }
}
}
}Signing in, for people writing clients
An unauthenticated call to /mcp answers 401 with a pointer to the discovery chain, which is all a
client needs to obtain its own credential:
| Step | Where |
|---|---|
| The challenge | WWW-Authenticate: Bearer resource_metadata="..." on the 401 |
| Protected resource (RFC 9728) | https://api.xdataapi.io/.well-known/oauth-protected-resource |
| Authorization server (RFC 8414) | https://xdataapi.io/.well-known/oauth-authorization-server |
| Register a client (RFC 7591) | https://xdataapi.io/api/oauth/register |
| Approve | https://xdataapi.io/oauth/authorize |
| Exchange the code | https://xdataapi.io/api/oauth/token |
| Disconnect (RFC 7009) | https://xdataapi.io/api/oauth/revoke |
Clients are public and PKCE is required, with S256 only. There are no scopes: every tool is read-only
and there is one level of access, so a scope list would be several words that all mean the same thing.
What the exchange returns is an ordinary API key named after the app. It appears in the dashboard beside keys you made by hand, spends the same balance at the same prices, and stops the moment you revoke it. It does not expire, and there is no refresh token: it is the same class of credential you mint yourself, with the same revocation story.
Tools
| Tool | Arguments | Credits |
|---|---|---|
get_user | handle | 1 |
get_users | handles[], up to 100 | 1 per profile found |
get_user_tweets | user, count, cursor | 1 per tweet |
get_followers | user, count, cursor | 0.1 per profile |
get_follower_ids | user, count, cursor | 0.02 per id |
get_following | user, count, cursor | 0.1 per profile |
search_tweets | q, product, count, cursor | 1 per tweet or profile |
get_tweet | id | 1 |
get_tweets | ids[], up to 100 | 1 per tweet found |
get_thread | id, cursor | 1 per tweet |
get_replies | id, cursor | 1 per reply |
get_quotes | id, count, cursor | 1 per tweet |
get_retweeters | id, count, cursor | 0.5 per profile |
get_balance | free |
Every tool takes fresh: true to bypass the cache at 2x. All tools are read-only and idempotent, and are
annotated as such, so clients that auto-approve read-only tools do not prompt on every call.
Notes
- The server is stateless. Each request carries the key; there is no session to expire.
- A REST error (
not_found,no_credits,rate_limited) comes back as a tool error with the same JSON body, so the agent can read thecodeand act on it. - Results are the same flat
TweetandUserobjects as the REST API, as JSON text. - Keys are per account. Create one key per agent in the dashboard so you can revoke it alone.