Skip to content

Delegation tokens

IdentityHTTPMCP_auth.tokenPOST /api/delegation/validateGET /api/delegation/oauth/:provider/tokenlt_svc_

A tool call carries a five-minute, scope-limited token for the person behind the run, which an external server trades for that person's access.

The situation

An external MCP server, running in its own container, needs to read a person’s Google calendar on behalf of a workflow. It must not hold that person’s tokens, and it must not be able to act for anyone else.

The recipe

When a workflow activity calls an MCP tool, the framework mints a delegation token and passes it as _auth: { userId, token } in the tool’s arguments. The external server holds a long-lived service token (lt_svc_...) and uses it to check the delegation:

validate the delegation
curl -X POST https://lt.acme.dev/api/delegation/validate \
-H "Authorization: Bearer $LT_SERVICE_TOKEN" -H 'Content-Type: application/json' \
-d "{ \"token\": \"$DELEGATION\" }"
# { "valid": true, "userId": "user-123", "scopes": ["oauth:google:read"], ... }

Then trades it for the person’s fresh provider token:

fetch the person's access token
curl https://lt.acme.dev/api/delegation/oauth/google/token \
-H "Authorization: Bearer $DELEGATION"
# { "access_token": "...", "expires_at": "...", "provider": "google" }

The token endpoint requires the scope oauth:<provider>:read. Tools that need no auth ignore _auth.

The guarantee

A delegation token is a JWT signed by Long Tail with a 5-minute default lifetime (1 hour maximum). It cannot be refreshed or extended, cannot authenticate as a different user, cannot reach resources outside its scopes, and cannot reach the admin API. The external server can only access what the token names, for the person it names. See OAuth and delegation.