Developer reference
Technical reference for the connacto MCP server: the endpoint, the OAuth 2.1 flow that guards it, and the tool surface an agent sees.
One stateless MCP endpoint
connacto speaks MCP over Streamable HTTP at a single address:
{origin}/api/mcpThe server runs in stateless mode: every request spins up a fresh server and transport, with no session handshake or SSE stream to keep alive. There is no server-assigned session id, so GET, POST, and DELETE all route to the same handler.
OAuth 2.1 with PKCE
Every call to /api/mcp requires an Authorization: Bearer <token> header. A request without one gets a 401 whose WWW-Authenticate header points at connacto's protected-resource metadata, so a compliant MCP client can discover the rest on its own.
Most clients, including Claude Desktop, Claude Code, and claude.ai connectors, run this whole flow for you the first time you add the endpoint: a browser tab opens for login and consent, then the client stores the token and reconnects on its own from then on. The steps below are what happens under the hood, useful if you are writing a client that does not already speak MCP auth.
1. Discover the endpoints
GET /.well-known/oauth-protected-resource/api/mcp returns the resource identifier and which authorization server backs it. GET /.well-known/oauth-authorization-server returns that server's endpoints (RFC 8414):
{
"issuer": "{origin}",
"authorization_endpoint": "{origin}/oauth/authorize",
"token_endpoint": "{origin}/api/oauth/token",
"registration_endpoint": "{origin}/api/oauth/register",
"response_types_supported": ["code"],
"grant_types_supported": ["authorization_code", "refresh_token"],
"code_challenge_methods_supported": ["S256"],
"token_endpoint_auth_methods_supported": ["none"]
}2. Register a client
POST /api/oauth/register implements RFC 7591 dynamic client registration. connacto only issues public clients, since MCP clients have nowhere safe to keep a secret, so there is no client secret to store, only a client_id.
curl {origin}/api/oauth/register \
-H "Content-Type: application/json" \
-d '{
"redirect_uris": ["https://your-client.example/callback"],
"client_name": "Your Client"
}'3. Send the user to authorize
Generate a PKCE code verifier and its S256 challenge, then send the user's browser to /oauth/authorize with response_type=code, client_id, redirect_uri, code_challenge, code_challenge_method=S256, and a state value. connacto prompts for login (email and password, or Google) if needed, then shows a consent screen naming your client. On approval it redirects to your redirect_uri with a code and your state.
4. Exchange the code for tokens
curl {origin}/api/oauth/token \
-d grant_type=authorization_code \
-d client_id=$CLIENT_ID \
-d code=$CODE \
-d redirect_uri=https://your-client.example/callback \
-d code_verifier=$CODE_VERIFIERThe response is an access_token and refresh_token. Authorization codes are single-use: connacto claims each code atomically, so a replayed or double-submitted code is rejected on the second attempt. When the access token expires, exchange the refresh token the same way with grant_type=refresh_token.
Core tools
These tools are always available, regardless of which modules you have. Use them to discover modules, create databases from them, and toggle or delete database instances. Once a built-in module is active, it also gets its own dedicated tools with full parameter and return docs on its module page.
Tool dispatch at scale
A module's dedicated tools (e.g. diet_tracking_add_entry) are registered directly, by name, as long as you have few enough active modules that the tool list stays manageable for an LLM to pick from. Past that point, connacto stops registering each module's tools individually and switches to these two instead, which dispatch into the exact same per-module handlers by slug and short tool name:
Building a custom connector
If your AI harness already speaks MCP, skip this section. Give it connacto's URL and let it run the OAuth flow above on its own. This is for harnesses that don't, such as a custom agent loop or an internal tool-calling framework, and need to talk to /api/mcp directly once you already hold an access token.
1. List available tools
curl {origin}/api/mcp \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/list"
}'The response's result.tools array gives you each tool's name, description, and JSON Schema input shape. Feed that straight into your harness's tool or function definitions.
2. Call a tool
curl {origin}/api/mcp \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{
"jsonrpc": "2.0",
"id": 2,
"method": "tools/call",
"params": {
"name": "diet_tracking_add_entry",
"arguments": {
"meal": "oatmeal",
"calories": 350
}
}
}'The result comes back as result.content, an array of content blocks. connacto's tools return a single text block containing JSON. Parse it and hand it back to your model as the tool result.
3. Wire it into your agent loop
The general shape, independent of harness:
- On startup, call
tools/listand register each tool with your model using its name, description, and input schema as given. - When the model emits a tool call for one of these tools, forward it as a
tools/callrequest with the same name and arguments. - Take the text out of
result.content[0].textand return it to the model as the tool result. connacto always returns JSON-stringified data, so most harnesses can pass it through unmodified. - Check
result.isError, or the JSON-RPCerrorfield for transport-level failures, and surface it as a failed tool call rather than valid data. - On a
401, refresh your access token with the OAuth token endpoint before retrying.
A minimal fetch-based adapter looks like this.
async function callConnacto(method, params, accessToken) {
const res = await fetch("{origin}/api/mcp", {
method: "POST",
headers: {
"Authorization": `Bearer ${accessToken}`,
"Content-Type": "application/json",
"Accept": "application/json, text/event-stream",
},
body: JSON.stringify({ jsonrpc: "2.0", id: 1, method, params }),
});
const { result, error } = await res.json();
if (error) throw new Error(error.message);
return result;
}
const { tools } = await callConnacto("tools/list", {}, accessToken);
const { content, isError } = await callConnacto(
"tools/call",
{ name: "list_modules", arguments: {} },
accessToken,
);That's the whole surface area. No SDK is required, since connacto speaks plain MCP over HTTP with standard OAuth 2.1. If your harness has an existing MCP client library, most do, such as Python's mcp package or TypeScript's @modelcontextprotocol/sdk, prefer that over hand-rolling the JSON-RPC and OAuth calls above.