Docs
Connect an MCP client
SambungAPI serves the tools of one project to an MCP client, such as Claude Code or Cursor, at one URL. This page says what that URL takes, which tools the client sees, how an agent connects an application user's account, and what a call costs.
On this page
The endpoint
The MCP endpoint is https://sabi.metatech.id/mcp. It speaks the Streamable HTTP transport: each POST carries one JSON-RPC message and gets one application/json answer. GET and DELETE answer 405, because there is no event stream and no session.
| Protocol revision | How the endpoint serves it |
|---|---|
2026-07-28 | With no session: the client calls server/discover, and every request carries its own protocol version. |
2025-11-25, 2025-06-18, 2025-03-26, 2024-11-05 | With initialize, but with no session: the endpoint never sends Mcp-Session-Id, and a request after initialize needs none. |
server/discover and initialize advertise one capability, tools, and the server name sambungapi. They also return instructions: a short text for the model about what the tools do, whose account a call uses, and what to do when an account is missing.
The credential
A project key is the credential your software uses to run tools for one project. It is the same ak_ key that runs POST /v1/tools/{slug}/execute, and you mint it in the console at /app/projects/{project_id}/keys. An agent with no key asks a person for one with a key request: see the key request in the API guide. An agent with no project key asks a person for one with a Key request, or uses machine sign-in to get a Control-plane token and mint a project key: see Get a project key. A Control-plane token alone cannot call /mcp. The API guide says who can mint one.
Send the project key as Authorization: Bearer ak_…, or in the x-api-key header. The endpoint never reads a credential from the URL, so never put the key there: a URL is kept in logs and in client settings that other people read.
The endpoint accepts a project key and nothing else. A console session cookie or a personal control-plane token (cpt_) answers 401 Auth_MissingProjectKey. A request from a web page whose Origin the deployment does not allow answers 403 Mcp_OriginForbidden; an MCP client sends no Origin.
The application user
Each tool call acts for one application user: the end user of your software whose provider account the tool uses. Name the application user in the URL, as the user_id query parameter, or in the x-user-id header:
https://sabi.metatech.id/mcp?user_id=user-123
The same value goes in the user_id of a connect link and of the execute body. A user_id in the query and an x-user-id header with a different value answer 400 BadRequest, and so does a value that starts with org: and is not org:<organization_id> of the organization that owns the project of the key. A tool call of a toolkit whose connection scope is user, with no application user in the URL, is a tool error that names user_id, and nothing runs.
To act for the accounts that the project shares, name org:<organization_id> as the user id in the URL. Every toolkit connects one account for each application user by default. GET /v1/tools/toolkits gives each toolkit's connection_scope. A tool of a toolkit with connection_scope: organization, which only the deployment's operator can set, runs on the project's shared account under org:<organization_id>, whatever application user the URL names. A shared account serves only the project that connected it.
Which tools the list holds
tools/list holds the tools of the toolkits that this deployment offers, and the tools of the toolkits that the organization of the key has an auth config for. A toolkit is offered when GET /v1/tools/toolkits gives it available: true. The list holds no deprecated tool and no tool of a channel, such as Slack. Each tool has its slug as name, its name as title, its description, and its input schema as inputSchema. The list is in catalog order, and a client can keep it for five minutes (ttlMs: 300000). The tool catalog at /v1/specs/catalog, which needs no credential, holds more: it has no organization, so it lists the tools of every toolkit that is not a channel, also when the deployment does not offer the toolkit and your organization has no auth config for it. The page The tool catalog shows that catalog to a person: the arguments of each tool of an offered toolkit, and only the slugs of the tools of a toolkit that the deployment does not offer.
Two query parameters narrow the list. A narrowed tool is also one that a call cannot reach.
| Parameter | Value | What it does |
|---|---|---|
toolkits | Toolkit slugs, comma-separated, for example gmail,googlecalendar | Lists only the tools of these toolkits. |
tools | Tool slugs, comma-separated, for example GMAIL_SEND_EMAIL | Lists only these tools. |
A value that names no toolkit or tool of the catalog answers 400 BadRequest. GET /v1/tools/toolkits and GET /v1/tools list the catalog.
The arguments of a call are checked against the tool's input schema before anything runs. Arguments that do not match are a tool error, BadRequest with the message "Error in arguments.
Connect an application user's account
A tool runs on the application user's ACTIVE connected account for its toolkit. When the application user has none, the call is a tool error, ActionExecute_ConnectedAccountNotFound, whose suggested_fix says what to do next. An agent cannot open the console, so the endpoint gives it two ways to send the application user to a connect link.
The tool create_connect_link. The list holds it after the catalog tools, when it holds a tool of an offered toolkit that can get a connect link for the user id of the URL: a toolkit whose connection scope is user, or, when the URL names org:<organization_id>, also a toolkit whose connection scope is organization. Its toolkit values also hold googlesuper when the list holds a tool of a Google Workspace service: one consent connects them all. Its arguments are toolkit, one of the toolkits its input schema names, and an optional auth_config_id. It answers redirect_url, expires_at and connected_account_id. Give redirect_url to the application user: the link expires in 10 minutes. After the application user authorizes the toolkit, call the tool again.
- With no
auth_config_id, the link uses the toolkit's only custom auth config, else its oldest managed one. With none, it creates a managed one when the deployment holds an OAuth application for the toolkit, and answersToolkit_NotConnectablewhen it does not. A toolkit with several custom auth configs answersAuthConfig_Ambiguous, whose message lists their ids; call again with one of them. - When the application user already has an account of that toolkit that waits for authorization, the link is a new one for that account.
- A toolkit that the deployment's operator made shared in each project answers
ScopePolicy_OrgShared, unless the URL namesorg:<organization_id>. A toolkit this deployment cannot connect answersToolkit_NotConnectable.
The browser prompt. A client that speaks revision 2026-07-28 and declares URL-mode elicitation does not get the tool error. The call asks the client to open the connect link instead, and Claude Code asks whether to open it in your browser. After the application user authorizes the toolkit, the client sends the call again, and the tool runs on the new account. When the consent is not finished, or the user declined to open the link, the answer is ActionExecute_ConsentPending; finish the consent and call again. The endpoint never waits for a consent. A client of an earlier revision, and a client that does not declare URL mode, gets the tool error and uses create_connect_link.
The tools list_connected_accounts and delete_connected_account. The list holds both after create_connect_link, when it holds any tool. Each acts only for the application user that the URL names. list_connected_accounts takes no arguments and answers items: the id, toolkit_slug, status, auth_config_id and created_at of each connected account of that user, with every status. delete_connected_account takes connected_account_id, deletes that account, and answers {"id": "<id>", "deleted": true}. It always asks the provider to revoke the access of the account, as DELETE /v1/tools/connected_accounts/{id} does with revoke_on_delete=true, and it cannot be undone. An id of another application user, of another project or of no account answers ActionExecute_ConnectedAccountNotFound.
Repeated calls
A client can lose the answer to a call, for example when the connection breaks, and send the call again. SambungAPI does not run a Tool that changes data twice for the same call. The same call is the same Tool, with the same arguments, from the same project key, for the same application user. If it arrives while the first call still runs, SambungAPI waits up to 20 seconds for the first call to end; if the first call still runs after that, the answer is an error that says so. If it arrives within 60 seconds after the first call ended, SambungAPI answers with the result of the first call, marked replayed: true, and does not run the Tool again. A repeated call costs no usage unit. A Tool that only reads data always runs.
What the MCP endpoint does not do
Some parts of the data plane have no MCP tool. An agent that can send HTTP requests uses the REST route with the same project key. A host that runs tool calls only, such as the Anthropic Messages API, cannot use them.
| What you want to do | REST route |
|---|---|
| List the files of the project | GET /v1/tools/files/list |
| Get a new connect link for an account that is not waiting | POST /v1/tools/connected_accounts/{id}/refresh |
| Create an auth config with your own OAuth application | POST /v1/tools/auth_configs |
| List or read the auth configs of the organization | GET /v1/tools/auth_configs and GET /v1/tools/auth_configs/{id} |
| Delete an auth config of the organization | DELETE /v1/tools/auth_configs/{id} |
Upload a file
A tool that takes a file, such as GOOGLEDRIVE_UPLOAD_FILE, reads the bytes from a file that you upload first. The list holds the tool request_file_upload when it holds a tool that takes a file.
- Call
request_file_uploadwithfilename,mimetypeandmd5, the hex MD5 checksum of the bytes. Its optionaltoolnames the tool that takes the file; without it, the file is forGOOGLEDRIVE_UPLOAD_FILE. It answerss3key,upload_urlandupload_url_expires_at. PUTthe bytes of the file toupload_urlwith an HTTP client, beforeupload_url_expires_at.- Call
GOOGLEDRIVE_UPLOAD_FILEwithfile_to_upload{name, mimetype, s3key}.
A host that runs tool calls only cannot PUT the bytes. It can create a text file with GOOGLEDRIVE_CREATE_FILE_FROM_TEXT.
What a call costs
Each accepted tool call costs one Usage unit, as the execute call does, and writes one row to the project's Execution log, with the path /v1/tools/{slug}/execute#mcp. A row that was written before the execute route moved keeps the path of the old route, with #mcp at its end, and the console still reads it.
tools/list,server/discoverandinitializecost nothing.create_connect_linkcosts nothing and writes no Execution log row.- list_connected_accounts, delete_connected_account and request_file_upload cost nothing and write no Execution log row.
- A call that is refused before it runs costs nothing: arguments that do not match the input schema, no connected account, or a project past its daily quota (
Quota_Exceeded). - A repeated call that gets the first call's result costs nothing.
A provider answer is the tool result: structuredContent holds the same {data, error, successful} object as the data of the execute call, and isError is true when successful is false. A refusal is a tool error whose structuredContent is the error object of the API, with its slug and suggested_fix.
Examples
Each example below connects one client to the endpoint, for the application user user-123. Replace user-123 with the user_id of your application user, and ak_xxxxxxxxxxxxxxxx with your project key. The shapes come from the server code and from each client's documentation. They were not run against sabi.metatech.id, so each one says "shape verified; not executed".
Claude Code
The project key page in the console shows this command with a new key filled in, under the first-call curl.
# shape verified; not executed
claude mcp add --transport http sambungapi \
"https://sabi.metatech.id/mcp?user_id=user-123" \
--header "Authorization: Bearer ak_xxxxxxxxxxxxxxxx"
.mcp.json
A .mcp.json file at the root of a repository shares the server with everyone who works there. Keep the key out of the file: Claude Code reads ${SAMBUNGAPI_PROJECT_KEY} from the environment. Shape verified; not executed.
{
"mcpServers": {
"sambungapi": {
"type": "http",
"url": "https://sabi.metatech.id/mcp?user_id=user-123",
"headers": {
"Authorization": "Bearer ${SAMBUNGAPI_PROJECT_KEY}"
}
}
}
}
Cursor
Cursor reads .cursor/mcp.json in a project, or ~/.cursor/mcp.json for every project, and reads ${env:SAMBUNGAPI_PROJECT_KEY} from the environment. Shape verified; not executed.
{
"mcpServers": {
"sambungapi": {
"url": "https://sabi.metatech.id/mcp?user_id=user-123",
"headers": {
"Authorization": "Bearer ${env:SAMBUNGAPI_PROJECT_KEY}"
}
}
}
}
The Anthropic Messages API
The MCP connector sends authorization_token as Authorization: Bearer, which the endpoint accepts. The connector supports tool calls only, so the agent connects an account with create_connect_link.
# shape verified; not executed
curl -sS https://api.anthropic.com/v1/messages \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "anthropic-beta: mcp-client-2025-11-20" \
-H "content-type: application/json" \
-d '{
"model": "claude-opus-5-5",
"max_tokens": 4096,
"mcp_servers": [{
"type": "url",
"url": "https://sabi.metatech.id/mcp?user_id=user-123",
"name": "sambungapi",
"authorization_token": "ak_xxxxxxxxxxxxxxxx"
}],
"tools": [{"type": "mcp_toolset", "mcp_server_name": "sambungapi"}],
"messages": [{"role": "user", "content": "List my next three calendar events."}]
}'
The OpenAI Responses API
The mcp tool sends authorization as Authorization: Bearer, which the endpoint accepts.
# shape verified; not executed
curl -sS https://api.openai.com/v1/responses \
-H "Authorization: Bearer $OPENAI_API_KEY" \
-H "content-type: application/json" \
-d '{
"model": "gpt-5",
"tools": [{
"type": "mcp",
"server_label": "sambungapi",
"server_url": "https://sabi.metatech.id/mcp?user_id=user-123",
"authorization": "ak_xxxxxxxxxxxxxxxx",
"require_approval": "never"
}],
"input": "List my next three calendar events."
}'
Where the contract is
GET /v1/specs/tools carries an mcp object: the endpoint, the transport, the protocol revisions, the credential, the application user and this page. Where this page and the specs disagree, the specs are right.