MCP and OAuth
How an agent reaches a network. For the steps in a given tool, see connect an agent; this page is for people who build agents.
The endpoint
Section titled “The endpoint”POST https://app.inseam.io/mcpMCP over Streamable HTTP, stateless, JSON-RPC 2.0, one message or a batch per
request. The bearer token is an agent’s token from the OAuth flow below, or an API token.
Without a valid token the answer is 401 with
WWW-Authenticate: Bearer resource_metadata="https://app.inseam.io/.well-known/oauth-protected-resource", which
is how an MCP host discovers that it must sign in. GET /mcp and DELETE /mcp are 405: there is no session to
open or close.
The server speaks protocol version 2026-07-28 and still answers the 2025-11-25 initialize. initialize
returns short instructions: what inseam is, that every result carries an address, that read gets the text
behind a hit, and where the agent’s own folder is.
Getting a token
Section titled “Getting a token”With a browser: authorization code
Section titled “With a browser: authorization code”The usual flow for MCP hosts. Discovery is standard:
| document | |
|---|---|
/.well-known/oauth-protected-resource |
RFC 9728: the resource, its authorization server, the scopes |
/.well-known/oauth-authorization-server |
RFC 8414: the endpoints, code_challenge_methods_supported: ["S256"], token_endpoint_auth_methods_supported: ["none"] |
- Register, or bring a client id.
POST /oauth/register(RFC 7591) with{"client_name": "…", "redirect_uris": […], "token_endpoint_auth_method": "none"}returns{"client_id": "…"}. Or use anhttpsURL as the client id that serves a Client ID Metadata Document: JSON withclient_idequal to the URL,client_nameandredirect_uris. Public clients only. - Send the person to
GET /oauth/authorize?response_type=code&client_id=…&redirect_uri=…&code_challenge=…&code_challenge_method=S256&state=…&scope=search%20read. PKCE is required. They sign in, pick a network, the scopes, the hosts and a duration, and approve. - The browser returns to
redirect_uri?code=…&state=…. Exchange it:POST /oauth/tokenwithgrant_type=authorization_code,code,client_id,redirect_uri,code_verifier. - The answer is
{"access_token": "ias_…", "token_type": "Bearer", "expires_in": …, "refresh_token": "rt_…", "scope": "search read"}.
Redirect URIs must match exactly; loopback URIs (http://127.0.0.1:…, http://localhost:…) may change their
port. A refresh token rotates the access token with grant_type=refresh_token and lives 90 days. POST /oauth/revoke with token revokes either kind.
Without a browser: device code
Section titled “Without a browser: device code”For a script on a server or a scheduled job.
POST /oauth/devicewithclient_idandscope. The answer is{"device_code", "user_code", "verification_uri", "verification_uri_complete", "expires_in", "interval"}.- Show the person
user_codeandverification_uri(https://app.inseam.io/activate). They type the code and see the same approval page. - Poll
POST /oauth/tokenwithgrant_type=urn:ietf:params:oauth:grant-type:device_code,device_codeandclient_id, everyintervalseconds. Until approved the answer isauthorization_pending;slow_downmeans wait 5 seconds;access_deniedandexpired_tokenend it. Codes expire after 15 minutes.
Errors
Section titled “Errors”OAuth endpoints answer in RFC 6749 shape: 400 {"error": "invalid_grant", "error_description": "…"}.
Scopes
Section titled “Scopes”| scope | lets the agent | default at approval |
|---|---|---|
search |
search the network, read records, list prefixes, see hosts and status | on |
read |
read the text and the bytes behind an address | on |
write |
write and delete on the hosts the person picked | off |
Whatever the scopes, every agent may read, write and delete in its own folder on the pad:
inseam://{network}/pad/agents/{client id}/. The hosts tool returns it as padFolder.
| tool | input | needs | returns |
|---|---|---|---|
search |
{q, hosts?: [id], topK?: 1-100} |
search |
{hits: [{address, host, score, title, snippet, meta}], hosts: [{id, name, searched, reason?, coverage, items}]} |
get |
{address, follow?: true} |
search |
{record}: the catalog record; a forward carries deleted: true and movedTo |
read |
{address, maxBytes?: ≤ 262144} |
read |
{address, mime, source: "index" | "host", truncated, text}, or {…, base64} for non-text |
list |
{prefix, after?, limit?: ≤ 1000} |
search |
{entries: [{address, id}], more, next} |
hosts |
{} |
{hosts: [{id, name, kind, state, items, coverage, writable, youMayWrite}], padFolder} |
|
write |
{address, mime?, ifMatch?, content?, contentBase64?} |
write |
{entry: {address, content, etag, size, depth, created}}; the object is searchable when the call returns |
delete |
{address} |
write |
{address}; the old address keeps a forward |
read serves text from the index when it has it (source: "index") and from the host otherwise, so a read of an
indexed document works while its device is off. A tool that cannot do what was asked returns isError: true with
the reason (forbidden: not allowed: write …), not a JSON-RPC error. Addresses are full location addresses or
the short form {host}/{path}; see addresses.
Connecting new sources from an agent (connect_host and its companions) is not yet: the tools exist and
answer with the current way to add a host.
Discovery for agents
Section titled “Discovery for agents”| path | |
|---|---|
/llms.txt |
plain-text pointers to the endpoint and these docs |
/.well-known/mcp/server-card.json |
the server card: name, endpoint, authentication |