Programmatic Gateway Access
The Willow gateway speaks the Model Context Protocol (MCP) over Streamable HTTP. Any client that can send an HTTP request with a bearer token can complete the handshake, list tools, and call them. This page is the reference for doing that from a script.
For a guided walkthrough, start with Make Your First Gateway Call with a Machine User.
Endpoint
POST https://{your-org}.mcp-s.com/mcp
Your gateway URL is the Your Basecamp link in the top bar of the admin app, with /mcp appended.
Send these headers on every request:
Authorization: Bearer <ACCESS_KEY>:<SECRET>
Content-Type: application/json
Accept: application/json, text/event-stream
Authentication
A machine user authenticates with its Access Key and Secret, joined with a colon and sent as a bearer token:
Authorization: Bearer <ACCESS_KEY>:<SECRET>
Both halves are required. The access key on its own, the secret on its own, or a missing header all return 401 Unauthorized:
{ "error": "invalid_token", "error_description": "Invalid token" }
Reveal the access key and secret from Manage > Machine Users (the eye icons on the row). Rotate the secret from the row menu; the access key stays the same.
The Slug shown on the row is not interchangeable with the access key. They share a default value, but only the access key authenticates.
The MCP handshake
Open a session with initialize:
{
"jsonrpc": "2.0",
"id": 1,
"method": "initialize",
"params": {
"protocolVersion": "2024-11-05",
"capabilities": {},
"clientInfo": { "name": "my-script", "version": "0.1" }
}
}
The gateway responds with its capabilities and identity:
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"protocolVersion": "2024-11-05",
"capabilities": { "tools": {}, "prompts": {}, "resources": {} },
"serverInfo": { "name": "mcp-s", "version": "1.0.0" }
}
}
Listing and calling tools
Request the tool list:
{ "jsonrpc": "2.0", "id": 2, "method": "tools/list", "params": {} }
Tools are namespaced by their MCP server as {server-slug}__{tool}. A search-messages tool on the slack server is slack__search-messages. A machine user only sees tools from the MCP servers assigned to the groups it belongs to, so an empty list usually means the machine user is in no group with an MCP server.
Call a tool with tools/call:
{
"jsonrpc": "2.0",
"id": 3,
"method": "tools/call",
"params": { "name": "slack__search-messages", "arguments": { "query": "release" } }
}
The result is under result.content.
The gateway is an OAuth protected resource
The /mcp endpoint advertises OAuth resource metadata at:
https://{your-org}.mcp-s.com/.well-known/oauth-protected-resource/mcp/
The authorization server for that resource is the gateway itself, which exposes its own token endpoint at https://{your-org}.mcp-s.com/token. Any SSO provider you configure (Okta, Keycloak) sits upstream of the gateway; the gateway mints and validates its own tokens. Presenting an upstream provider's token directly to /mcp does not authenticate. For the full resource metadata and token endpoint, see OAuth Authorization Server.
Do not confuse this with an internal MCP server's own OAuth. Here the gateway is the authorization server for callers. When you build an OAuth-protected MCP server, your server is the authorization server that Willow, acting as the client, authorizes against.
Error reference
| Status | Body | Cause |
|---|---|---|
401 | {"error":"invalid_token","error_description":"Missing Authorization header"} | No Authorization header |
401 | {"error":"invalid_token","error_description":"Invalid token"} | Malformed credential, or only one of access key / secret |
200 with isError | Tool Error in result | The call reached an MCP server that rejected it (for example an unconfigured passthrough server) |