You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
docs: clarify PRO API key authorizes upstream requests, not MCP access
Document that the PRO API key exists to authorize the server's upstream
requests, not to gate MCP functionality, so serving cached contract data
to an unvalidated client key is deliberate. Also align "Effect of a
missing key" to the resolved-effective-key model.
@coderabbitai ignore
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Copy file name to clipboardExpand all lines: SPEC.md
+4-1Lines changed: 4 additions & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -193,6 +193,8 @@ This architecture provides the flexibility of a multi-protocol server without th
193
193
194
194
All Blockscout data flows through the authenticated Blockscout PRO API gateway. Authentication and the request identity (`User-Agent`) are centralized here rather than scattered across individual tools.
195
195
196
+
The key's purpose is to ensure every request the server makes to the Blockscout API is authorized — not to act as an access-control gate on MCP functionality itself. An MCP response is therefore gated only insofar as it requires a fresh, authorized upstream request: when no such request is made (for example, a cache hit), there is nothing to authorize. This principle explains several behaviors documented below, including what does not require the key and why cached data may be served without validating a client-supplied key upstream.
197
+
196
198
**Credential**
197
199
198
200
- The single credential is the `BLOCKSCOUT_PRO_API_KEY` environment variable (`config.pro_api_key`, empty by default). When set, it is sent as an `Authorization: Bearer <key>` header on every PRO API request.
@@ -203,6 +205,7 @@ All Blockscout data flows through the authenticated Blockscout PRO API gateway.
203
205
- In addition to the server-side key, an MCP client may supply its own PRO API key in a dedicated request header whose name is configured by `BLOCKSCOUT_PRO_API_KEY_HEADER` (`config.pro_api_key_header`, default `Blockscout-MCP-Pro-Api-Key`). Setting this config to an empty string disables the feature.
204
206
- Resolution is pure precedence with **no fallback on a bad client key**: a valid client-supplied key is used for that request; if the client supplies no key, the server-side key is used; if neither exists, the request fails with the not-configured error. A client key that is present but malformed (control characters, or over the length bound) is a terminal error for any PRO-authenticated request that would consume it — the server never silently falls back to its own key for a malformed client key. Tools that never call the PRO API (for example `get_chains_list` or ENS lookups) are unaffected: the malformed state is recorded for the invocation but only the PRO API request helpers consult it.
205
207
- The credential is resolved per request via a request-scoped `ContextVar` in `blockscout_mcp_server/pro_api_key_context.py`. The `@pro_api_key_scope` decorator (applied alongside `@log_tool_invocation` on every tool, but kept independent of it) records the client-key state for the duration of a single tool invocation, and the PRO API request helpers call `resolve_pro_api_key()` at the moment they build the `Authorization` header. The header is read only for genuine MCP calls (skipped when `ctx.call_source == "rest"`) and the client key is never written to logs, analytics, or cache keys.
208
+
- Because the key authorizes upstream requests rather than gating MCP functionality, a response served entirely from cache (e.g. contract metadata/source) requires only that some effective key be present, not that the client-supplied key was validated upstream. A well-formed but invalid, expired, or out-of-credit client key may therefore receive cached PRO-gated data — no protected upstream request is made on its behalf. This is a deliberate consequence of the principle above, not a validation gap.
206
209
207
210
**Two transports, one scheme**
208
211
@@ -218,7 +221,7 @@ The server reaches the PRO API over two transports, each with its own header bui
218
221
219
222
**Effect of a missing key**
220
223
221
-
The key requirement is enforced as a single chokepoint: each PRO API entry point checks `config.pro_api_key` first and raises a `ValueError`*before any network call*, so the server never issues a request the gateway is guaranteed to reject. The chain-support validation runs only after this check, keeping the key as the first gate.
224
+
The key requirement is enforced as a single chokepoint: each PRO API entry point resolves the effective key via `require_pro_api_key()` (which calls `resolve_pro_api_key()`) and raises a `ValueError`*before any network call*, so the server never issues a request the gateway is guaranteed to reject. The effective key is the client-supplied key when present and valid, otherwise the server-side `config.pro_api_key`; resolution runs before chain-support validation, keeping the key as the first gate.
222
225
223
226
-**Primary data requests** (`make_blockscout_request` / `make_blockscout_post_request`) and **contract reads** (`Web3Pool.get`) fail fast — the tool returns a clear error and makes no network call.
224
227
-**Secondary metadata requests** (`make_metadata_request`, used by `get_address_info`) also fail fast, but callers treat this like any other metadata failure: the `metadata` field is returned `null` with an explanatory note while the primary data is still returned.
0 commit comments