> **Building with AI coding agents?** Install the authstack plugin with one command. This equips your agent with accurate Scalekit implementation patterns.
>
> **Recommended**:
> ```bash
> npx @scalekit-inc/cli setup
> ```
>
> Global:
> ```bash
> npm install -g @scalekit-inc/cli
> scalekit setup
> ```
>
> Supports Claude Code, Cursor, GitHub Copilot, Codex + skills for other Agent Skills-compatible agents.
> Skills: integrate-agentkit, implement-saaskit, add-mcp-oauth, implement-sso, implement-scim.
> [Full setup guide](https://docs.scalekit.com/dev-kit/build-with-ai/)

---

# Connect any MCP client

The HTTP contract of an AgentKit Virtual MCP server for clients that don't use the Scalekit SDK: URL, bearer token, transport, requests, responses, token lifetime and errors.
A Virtual MCP server is a standard MCP endpoint, so any client that speaks streamable HTTP can call it: an agent runtime, a hosted platform, your own HTTP code or `curl`. Your backend needs the Scalekit API only to mint session tokens; the client never needs the SDK. This page is the contract that client works against.

## The contract

| | |
| --- | --- |
| Endpoint | The server's `mcp_server_url`, from [Create a Virtual MCP server](/agentkit/mcp/configure-mcp-server/). Send every request as `POST`. |
| Authentication | `Authorization: Bearer <session token>`. A token is for one user on one server. [Mint session tokens](/agentkit/mcp/session-tokens/) shows how. |
| Request headers | `Content-Type: application/json` and `Accept: application/json, text/event-stream` |
| Transport | MCP streamable HTTP. Each response is a server-sent event stream with one `message` event, whose `data` line is the JSON-RPC response. |
| Session | None to manage. `tools/list` and `tools/call` work without an `initialize` call first, and the server sends no `Mcp-Session-Id`. Clients that do send `initialize` and `notifications/initialized` get the normal responses. |
| Methods | `initialize`, `tools/list` and `tools/call`. `GET` on the URL returns `405`: the server never opens a stream of its own. |
| Tools | The tools you chose for the server. By default, also `connect_account`: when the user still needs to connect an app, it returns an authorization link for that connection. |
| Token lifetime | 1 hour by default, from 60 seconds to 24 hours. A token can't be refreshed: mint a new one. Tokens are independent, so minting one doesn't revoke the others. |

## Call the server

1. ## Mint a session token

   Your backend mints the token for the user, as in [Mint session tokens](/agentkit/mcp/session-tokens/#mint-the-token), and hands the client the server URL and the token. Keep them in `MCP_SERVER_URL` and `SESSION_TOKEN`.

2. ## List the tools

   ```bash
   curl -sS "$MCP_SERVER_URL" \
     -H "Authorization: Bearer $SESSION_TOKEN" \
     -H "Content-Type: application/json" \
     -H "Accept: application/json, text/event-stream" \
     -d '{"jsonrpc": "2.0", "id": 1, "method": "tools/list"}'
   ```

   The response is one event. Its `data` line holds each tool's `name`, `description` and `inputSchema`:

   ```text
   event: message
   data: {"jsonrpc":"2.0","id":1,"result":{"tools":[{"name":"connect_account","description":"Connect (or RE-connect) the user's account…","inputSchema":{…}},{"name":"github_user_repos_list",…}]}}
   ```

3. ## Call a tool

   Send the tool's `name` and `arguments` that match its `inputSchema`:

   ```bash
   curl -sS "$MCP_SERVER_URL" \
     -H "Authorization: Bearer $SESSION_TOKEN" \
     -H "Content-Type: application/json" \
     -H "Accept: application/json, text/event-stream" \
     -d '{
       "jsonrpc": "2.0",
       "id": 2,
       "method": "tools/call",
       "params": {"name": "github_user_repos_list", "arguments": {"per_page": 5}}
     }'
   ```

   The result is MCP text content. Its `text` is a JSON string, and the app's response is under `data`:

   ```text
   event: message
   data: {"jsonrpc":"2.0","id":2,"result":{"content":[{"type":"text","text":"{\n  \"data\": [ … ]\n}"}]}}
   ```

   To read it in a shell, keep the `data:` line and parse the text twice:

   ```bash
   … | sed -n 's/^data: //p' | jq -r '.result.content[0].text' | jq '.data'
   ```

## Connect a user's account from the agent

If a tool call fails because the user hasn't authorized that connection, or tools you expect are missing from `tools/list`, call `connect_account`:

1. Call it with no arguments. The response lists `connections_requiring_authorization` and `connections_already_authorized`, each with a status and a reason. Nothing is created, so this is always safe.
2. Call it again with `connection_name` set to one of the connections that needs authorization. The response has a time-limited `magic_link_url`.
3. Show the link to the user. The agent shouldn't open it itself. Once the user signs in, retry the tool call with the same token.

A connection that uses an organization-wide credential has no user step: the response says `auth_mode` is `ORG_WIDE` and has no link.

## Renew the token

The client can't renew a token. Before it expires, your backend mints a new one and the client switches to it. A host with an expired token fails at its next request, not at startup. [Choose when to mint](/agentkit/mcp/session-tokens/#choose-when-to-mint) covers single runs, chat sessions, scheduled jobs, concurrent runs and long-running hosts.

## Errors

A rejected token gets HTTP `401` with `{"error":"unauthorized","error_description":"invalid or expired bearer token"}`. Errors inside a request that got through come back as JSON-RPC errors in the `message` event.

| What you see | Cause | Fix |
| --- | --- | --- |
| `401`, `invalid or expired bearer token` | No `Authorization` header, an expired token, a token minted for another server, or a token that was copied incompletely | Mint a new token for this server and user. |
| `405` | A `GET` request | Send JSON-RPC requests as `POST`. |
| JSON-RPC error `-32602` with `unknown tool` | The tool isn't on this server, or the name is misspelled | Use a name from `tools/list`. To add a tool, [update the server](/agentkit/mcp/configure-mcp-server/#change-or-remove-a-server). |
| A tool call fails, or a connection's tools are missing | The user hasn't authorized that connection | Call `connect_account`, as above. |

## Next

  - [Mint session tokens](/agentkit/mcp/session-tokens/): Mint the token on your backend, and decide when to mint again.
  - [Choose a framework](/agentkit/examples/): Set up CrewAI, Mastra or Claude Managed Agents against the server.


---

## More Scalekit documentation

| Resource | What it contains | When to use it |
|----------|-----------------|----------------|
| [/llms.txt](/llms.txt) | Structured index with routing hints per product area | Start here — find which documentation set covers your topic before loading full content |
| [/llms-full.txt](/llms-full.txt) | Complete documentation for all Scalekit products in one file | Use when you need exhaustive context across multiple products or when the topic spans several areas |
| [sitemap-0.xml](https://docs.scalekit.com/sitemap-0.xml) | Full URL list of every documentation page | Use to discover specific page URLs you can fetch for targeted, page-level answers |
