Vend a token
getAccessToken(). This is the legacy entry point used by the stdio runtime; the HTTP MCP transport calls resolveToken() directly (see below).
| Parameter | In | Required | Description |
|---|---|---|---|
userId | body | Yes | User ID |
provider | body | Yes | OAuth provider name (e.g. github, google) |
| Status | Code | Description |
|---|---|---|
| 400 | — | Missing userId or provider |
| 404 | NOT_LINKED | No connection found for this provider |
| 500 | VEND_FAILED | Token retrieval failed |
resolveToken() — three-source resolution
Inside the HTTP MCP transport (apps/server/src/routes/mcp-remote.ts), every plugin handler’s ctx.auth.getToken("<provider>") runs through resolveToken(userId, provider) which checks three sources in order:
tool_connectionsrow for the user withtool_id = <provider>. If present, decrypt and return the stored secret. This is how dashboard-managed API keys (granola personal/enterprise) and any future per-user tokens work.- Better Auth
getAccessToken({ providerId, userId }). Returns the OAuth access token (auto-refreshing if expired). Covers the social providers (github/google/slack/linear) and Generic OAuth providers (quickbooks/gcp/docusign/carta). process.env["${UPPER(provider).replace(/-/g, "_")}_API_KEY"]. Falls back to a server-wide env var. This is how the built-in tools (attio, browserbase, firecrawl, perplexity) get their keys without per-user storage.
resolveToken() throws No credentials for "<provider>". Connect it in the Toolshed dashboard. — surfaced to the agent verbatim by call_tool.