Authentication
The MCP server is a pure resource server: it validates tokens and never issues them. The MotorAdvisor web app at https://motoradvisor.app is the authorization server. Three credentials arrive as a bearer, over two validation paths:
- API keys (
mtr_live_…/mtr_test_…), created by a developer in the portal. Recognised by prefix and looked up in the developer portal's database — never handed to the JWT verifier. - OAuth 2.1 access tokens, obtained interactively by any MCP client that implements the authorization spec. Verified as an MCP-audience JWT.
- Personal access tokens, minted by an administrator for a shop account's headless clients and shown once at mint time. Also an MCP-audience JWT.
Whichever arrived, what the account may reach — its realm, plan, entitlements and subscription state — resolves identically afterwards.
The OAuth flow, as a client experiences it
- The client calls
POST /mcpwithout a token and receives 401 with aWWW-Authenticateheader carryingresource_metadata. - It fetches
/.well-known/oauth-protected-resource(RFC 9728) and learns the authorization server's URL. - It fetches the authorization server's metadata, registers itself dynamically, and starts an authorization-code flow with PKCE (S256).
- The user signs in at https://motoradvisor.app and consents — with a developer account they created themselves, or a shop account an administrator provisioned with the MCP grant. A client arriving with no session is sent to developer sign-in, which offers signup; shop staff sign in at
/login. - The client receives an access token bound to the MCP audience and retries the request.
No client pre-registration, no shared secrets, no configuration beyond the URL. This flow is what an MCP client's "add server → login screen appears" experience is made of.
Account state is live
Beyond the signature and audience, the server checks the account's live state on each request. Disabling an account or revoking its MCP grant kills existing tokens on the next call — there is no revocation lag hiding behind token expiry. For developer accounts the live check also reads the subscription: a cancelled or unpaid subscription stops calls at the next request with a checkout link, while past_due keeps serving for as long as the card is being retried.
Metering and limits
Two limits, depending on the account.
- Developer accounts are metered against their package, counted durably in the usage ledger. Test includes 1,000 data calls and 50 repair plans a month and stops there — no overage, no surprise bill; exhausting it is an in-band tool refusal (
QuotaExceeded) that names the limit and links to the portal, never an HTTP 429. Build and Scale include more and bill overage rather than stopping. Past 50,000 calls or 3,000 repair plans a month, self-serve ends and someone sizes a licence with you. - Licensed shop accounts keep an administrator-set per-account request ceiling. Past it, calls return 429
rate_limited. In this evaluation deployment that counter lives with the serving instance, so enforcement is approximate.
Only calls the server actually served count. A section skipped for want of a licence is a refusal, not usage.
Entitlements
MOTOR content is licensed in two classes, and every account holds a set of them:
motor_owned— labor times, maintenance schedules, specifications, fluids, diagnostic trouble codes. Included with any paid package.oem_licensed— repair procedures, parts, technical service bulletins, wiring diagrams, component locations, part illustrations. Requires a manufacturer-approved agreement and is dark until MOTOR has one on file for the account. Licensed shop accounts hold both classes.
A content type the account cannot reach refuses the whole call with NotEntitled and a link to the portal. Inside service_advisor_lookup the licensed sections are skipped and the rest answers; the response's per-section status says which, and the tool's advertised description carries a [Partially available] notice for that account. A self-serve developer account holds motor_owned only.
Token hygiene
The inbound bearer token is never forwarded to MOTOR (the spec prohibits token passthrough, and a test asserts the inbound value appears in no outbound URL or header). MOTOR credentials are held server-side and signed into upstream requests by the server itself; clients never see or supply them.
HTTP errors
| Status | error | Meaning |
|---|---|---|
| 401 | invalid_token | Missing, expired, wrong-audience, or revoked token — including a disabled account. A revoked API key is named as revoked, so it is distinguishable from an unrecognised one. The WWW-Authenticate header carries the discovery pointer. |
| 401 | insufficient_scope | The account exists but does not hold the MCP grant. |
| 429 | rate_limited | A licensed shop account's per-account request ceiling is exhausted. Developer packages refuse in-band instead — see Tool-level errors. |
| 503 | server_misconfigured | The deployment is missing its signing secret; fail-closed. |
| 503 | service_unavailable | The account service could not be reached, or this deployment has no self-serve side. The credential was not checked — retry; do not rotate keys. |
POC caveats, stated plainly
This is an evaluation deployment, and a few implementation choices are scoped to that. Integrators should know them; none of them changes the contracts a client builds against.
- The authorization server is a deliberately minimal in-app implementation. It exists to prove the boundary, not to be one. Production swaps in an enterprise identity platform without touching the MCP server — the resource server validates audience-scoped JWTs and does not care who minted them, which is the point of the design.
- Single-use enforcement of authorization codes is per serving instance, as is the legacy request ceiling on licensed shop accounts. Developer package usage is already backed by a shared store and is exact. Production backs the rest the same way.
- Sandbox coverage (model years 2010, 2015, 2016) is a data scope, not an architecture scope. Production serves the full MOTOR vehicle database through the same tools with no change to their schemas or contracts.
What is not a caveat: audience-validated tokens, live revocation, the ambiguity contract, two-stage quoting, band-only valuations, and the read-only DaaS posture. Those hold in production unchanged.
Tool-level errors
Tool failures arrive in-band as MCP tool results with isError: true and a typed body:
{ "error": { "type": "ShopNotReady", "message": "…", "explanation": "…" } }
The type names the refusal precisely — InvalidInput, UnknownTool, NoShop, ShopNotReady, NotReady, PaymentProviderError, and so on — and explanation says what would make the call succeed. Agents should relay the explanation rather than retrying blind.
Two of them a developer account meets first:
NotEntitled— the requested content type needs a licensed agreement this account does not hold. The message links to the portal.QuotaExceeded— the package's monthly allowance is used and the tier has no overage (Test). The message names the limit and links to the portal to upgrade.
And one that is not an error at all: a vehicle outside the sandbox's model years resolves to a MotorNotFoundError whose message lists the covered catalog. Relay it as "not covered" rather than retrying.