Skip to content

Allow MCP clients to supply their own Blockscout PRO API key#400

Merged
akolotov merged 14 commits into
mainfrom
claude/angry-franklin-4316be
Jun 5, 2026
Merged

Allow MCP clients to supply their own Blockscout PRO API key#400
akolotov merged 14 commits into
mainfrom
claude/angry-franklin-4316be

Conversation

@akolotov
Copy link
Copy Markdown
Collaborator

@akolotov akolotov commented Jun 5, 2026

What

Lets an MCP client supply its own Blockscout PRO API key on a per-request basis (HTTP MCP transport only), so the resolved effective key — client-supplied when present, otherwise the server's configured key — authenticates every PRO API request. Closes #392.

Why

Previously the server could only ever use the single BLOCKSCOUT_PRO_API_KEY configured at deploy time. Multi-tenant / hosted deployments need each client to bring its own key so rate limits and billing attribute to the caller, without the server holding every client's secret.

How it works

  • A new configurable request header (default Blockscout-MCP-Pro-Api-Key) carries the client key on HTTP MCP requests.
  • A request-scoped ContextVar holds the client key's state (absent / valid / malformed) for the duration of a tool invocation; a @pro_api_key_scope decorator on every tool sets and resets it with strict discipline.
  • resolve_pro_api_key() centralizes precedence: a valid client key wins, a malformed one fails fast, otherwise we fall back to the server's configured key.
  • All PRO API request paths (tools/common.py helpers, contract fetch, and the Web3 pool) resolve the effective key at request time and inject Authorization per-request — the pooled Web3 providers no longer store the secret.
  • REST mode ignores client-supplied keys by design.

Phases (one commit each)

  1. Add the pro_api_key_header configuration setting
  2. Create the pro_api_key_context module (state types, ContextVar, resolver, scope decorator)
  3. Apply the pro_api_key_scope decorator to all tools
  4. Resolve the effective key across the PRO API request paths
  5. Inject auth at request time in the Web3 pool
  6. Integration test — a resolved client key against the live PRO API
  7. Documentation and version bump (0.16.0.dev16)

Testing

  • Unit: 698 passed (uv run pytest tests/)
  • Integration (timeout-protected runner): 86 passed, 0 failed, 0 skipped, 0 timed out — including the new live test test_make_blockscout_request_client_key_via_context_var, which ran (not skipped) and authenticated a request to the live gateway using only the client-supplied key
  • ruff check . and ruff format --check . both clean

Reviewer notes

  • Secrets are never logged, never put in the analytics payload, and never embedded in cache keys or exception messages — there are explicit tests for each.
  • A malformed client key fails the request closed (it does not silently fall back to the server key), including on contract-cache hits.
  • Version bumped to 0.16.0.dev16 in pyproject.toml, blockscout_mcp_server/__init__.py, and server.json; mcpb/manifest.json is intentionally unchanged.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • MCP clients may supply a per-request Blockscout PRO API key via a configurable HTTP header; valid client keys take precedence for that request and are applied to JSON-RPC/contract reads.
  • Configuration

    • New env/config option to set the request header name (empty = disable client-supplied keys). REST endpoints continue to use only the server key.
  • Behavior Changes

    • Malformed client keys fail the PRO-authenticated request (no silent fallback).
  • Privacy

    • Client-supplied keys are excluded from analytics and logs.

akolotov and others added 7 commits June 4, 2026 22:41
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…O API

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jun 5, 2026

Too many files changed? Review this PR in Change Stack to see how the pieces fit before you dive in.

Review Change Stack

Warning

Review limit reached

@akolotov, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 20 minutes and 23 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 4e6fd00e-62a9-454d-8de0-611a071aef86

📥 Commits

Reviewing files that changed from the base of the PR and between ba5cdea and 9e5a651.

📒 Files selected for processing (13)
  • AGENTS.md
  • SPEC.md
  • blockscout_mcp_server/__init__.py
  • blockscout_mcp_server/tools/common.py
  • pyproject.toml
  • server.json
  • tests/test_analytics.py
  • tests/test_pro_api_key_context.py
  • tests/tools/contract/test_fetch_and_process_contract.py
  • tests/tools/test_common_blockscout_request_auth.py
  • tests/tools/test_common_metadata.py
  • tests/tools/test_common_post_request.py
  • tests/tools/test_decorators.py

Walkthrough

Per-request client-supplied Blockscout PRO API key support for MCP tools: adds a configurable header and ServerConfig field, implements ContextVar-backed extraction/normalization/resolution (client-first, no fallback for malformed client keys), provides a require_pro_api_key gate and @pro_api_key_scope decorator, injects Authorization per request, updates Web3Pool to avoid mutating shared providers, and adds comprehensive tests and docs.

Changes

Client-Supplied PRO API Key Support

Layer / File(s) Summary
Configuration and docs
.env.example, Dockerfile, README.md, SPEC.md, AGENTS.md, blockscout_mcp_server/llms.txt, blockscout_mcp_server/__init__.py, pyproject.toml, server.json
Adds BLOCKSCOUT_PRO_API_KEY_HEADER/pro_api_key_header config and documentation; updates SPEC and README to describe MCP-only client header semantics; bumps version to 0.16.0.dev15.
Core types and state
blockscout_mcp_server/pro_api_key_context.py, blockscout_mcp_server/config.py
Defines _MAX_KEY_LENGTH, _Absent/_Valid/_Malformed dataclasses, ClientKeyState alias, and module-level _client_key_state ContextVar; adds ServerConfig.pro_api_key_header with trimming validator.
Normalization, extraction, resolution
blockscout_mcp_server/pro_api_key_context.py
Implements _normalize_key() (strip/length/control-char checks), extract_client_pro_api_key_from_ctx() (reads configured header defensively, MCP-only), and resolve_pro_api_key() which returns client key if valid, raises on malformed client key, else falls back to config.pro_api_key.
Validation gate
blockscout_mcp_server/pro_api_key_context.py
require_pro_api_key(disabled_feature) centralizes fail-fast behavior: calls resolver, raises standardized not-configured error (with hints) when resolved key is empty, propagates malformed-key errors.
@pro_api_key_scope decorator
blockscout_mcp_server/pro_api_key_context.py
Async decorator that locates ctx in tool args, stores extracted client key state into _client_key_state around execution, and reliably resets it in finally; malformed keys deferred to resolver.
Apply decorator to tools
blockscout_mcp_server/tools/*
Imports and applies @pro_api_key_scope to many MCP tool handlers (address, block, chains, contract, direct_api, ens, initialization, search, transaction) so each tool runs with request-scoped key state.
Shared helpers: tools/common
blockscout_mcp_server/tools/common.py
Replaces direct config.pro_api_key checks with require_pro_api_key() in request helpers; _pro_api_headers() uses resolve_pro_api_key() to conditionally set Authorization and omits header when no key; malformed-key errors are propagated early.
Web3Pool: request-time auth
blockscout_mcp_server/web3_pool.py
Injects Authorization per JSON-RPC request inside _make_http_request() using resolve_pro_api_key(), removes provider mutation, ensures cache keys/header material exclude credentials, and uses require_pro_api_key() in Web3Pool.get().
Contract access guard
blockscout_mcp_server/tools/contract/_shared.py
Adds early require_pro_api_key("data access") before contract cache lookup so malformed/absent keys fail closed even on cache hits.
Unit tests for key resolution and decorator
tests/test_pro_api_key_context.py
Comprehensive tests for normalization, extraction, resolve precedence, require guard messages, decorator behavior, redaction, and concurrency isolation.
Transport and integration tests
tests/test_pro_api_key_http_transport.py, tests/integration/test_common_helpers.py, tests/test_analytics.py
Transport tests ensure FastMCP HTTP passes header into MCP ctx; integration test exercises live request using ContextVar client key; analytics test asserts header/value not sent to Mixpanel.
Request/metadata/POST auth tests
tests/tools/test_common_blockscout_request_auth.py, tests/tools/test_common_metadata.py, tests/tools/test_common_post_request.py
Adds/extends tests verifying client-key precedence, serverless mode (server key empty + client key present), fallback to server key when client absent, malformed-key fail-fast before HTTP, upstream 401 propagates without fallback, both-keys-absent error, and ContextVar propagation into spawned tasks.
Web3Pool tests
tests/test_web3_pool.py
Updates tests to assert Authorization is request-injected (not cached), cache keys exclude credentials, changed server keys re-applied on cache hits, caller auth sanitized, and adds serverless/concurrency/upstream-rejection tests.
Contract fetch tests
tests/tools/contract/test_fetch_and_process_contract.py
Adds Phase 4 tests validating cache short-circuit gating with client ContextVar (valid client key allows cache hit without HTTP; malformed/absent keys fail closed) and ensures cache keys remain credential-free.
Decorator interaction and config tests
tests/tools/test_decorators.py, tests/test_config.py
Tests stacking pro_api_key_scope with logging decorator, REST-mode ignores client header, logs do not leak header/name/value; config tests for pro_api_key_header default, env override, trimming, and preserving empty string.

Possibly related issues

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 47.03% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: allowing MCP clients to supply their own Blockscout PRO API key, which is the central feature of this PR.
Linked Issues check ✅ Passed The PR fully implements all coding requirements from issue #392: client-supplied key via configurable HTTP header, per-request precedence resolution, no fallback on rejected client keys, request-scoped isolation, credential exclusion from logs/analytics/caches, and MCP-tools-only scope.
Out of Scope Changes check ✅ Passed All changes are directly aligned with issue #392 requirements: config additions, context/decorator implementation, tool decoration, Web3 pool auth injection, comprehensive tests, and documentation updates. No unrelated modifications detected.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/angry-franklin-4316be

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@akolotov akolotov self-assigned this Jun 5, 2026
- Centralize the not-configured raise in a new `require_pro_api_key()`
  helper so every PRO API entry point (`make_blockscout_request`,
  `make_blockscout_post_request`, `make_metadata_request`,
  `_fetch_and_process_contract`, `Web3Pool.get`) emits the same error
  text and names the configurable client header when enabled.
- Tighten `_MAX_KEY_LENGTH` from 4096 to 256 (~4x current key length
  of 79) so the bound rejects abuse without limiting plausible
  future formats.
- Log unexpected ctx-shape failures in
  `extract_client_pro_api_key_from_ctx` at DEBUG (with `exc_info`) so
  the defensive bare-except no longer hides bugs silently.
- Reword the test-shape comment in `_normalize_key` and document the
  blanket `@pro_api_key_scope` policy plus the required decorator
  stacking order (must sit inside `@log_tool_invocation`).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@akolotov akolotov marked this pull request as ready for review June 5, 2026 17:08
@akolotov
Copy link
Copy Markdown
Collaborator Author

akolotov commented Jun 5, 2026

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jun 5, 2026

✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (3)
tests/tools/contract/test_fetch_and_process_contract.py (1)

262-262: 💤 Low value

Move import to top of file.

This import should be at the top of the module with the other imports from pro_api_key_context. As per coding guidelines, all import statements must be placed at the top of the Python module.

♻️ Suggested fix

At line 8, update the import to include _Malformed:

-from blockscout_mcp_server.pro_api_key_context import _client_key_state, _Valid
+from blockscout_mcp_server.pro_api_key_context import _client_key_state, _Malformed, _Valid

Then remove line 262:

-    from blockscout_mcp_server.pro_api_key_context import _Malformed
-
     cached = CachedContract(metadata={}, source_files={})
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/tools/contract/test_fetch_and_process_contract.py` at line 262, Move
the from blockscout_mcp_server.pro_api_key_context import _Malformed statement
to the top of the module alongside the other imports from pro_api_key_context
(e.g., update the existing import at the top to also import _Malformed) and
delete the duplicate import at line 262 so the module only imports _Malformed
once at the file header.
tests/test_pro_api_key_context.py (1)

219-236: ⚡ Quick win

Exercise malformed-key redaction via the real extraction→scope→resolve path.

These two tests currently inject _Malformed() directly, so they don’t validate the integration path where raw client input is parsed and then rejected. Routing the assertion through pro_api_key_scope with malformed header values will better protect against regressions in the actual request flow.

As per coding guidelines, tests/**/*.py unit tests must provide comprehensive coverage including error handling and edge cases.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/test_pro_api_key_context.py` around lines 219 - 236, The tests should
exercise the real extraction→scope→resolve path instead of directly injecting
_Malformed(): update both tests to send malformed header values through
pro_api_key_scope (use the same raw_value strings) so the flow goes through the
extractor and scope creation before resolve_pro_api_key() is called; replace the
_set_key_state(_Malformed()) injection with creating a pro_api_key_scope from a
request/header containing the malformed value and then call
resolve_pro_api_key() (or the same resolve helper) so the ValueError assertion
confirms the redaction behavior in the actual request path.
tests/tools/test_decorators.py (1)

280-281: ⚡ Quick win

Make the no-leak log assertion case-insensitive.

The current check only blocks one header casing. Normalizing caplog.text to lowercase makes this test catch leaked header names/values regardless of logger casing behavior.

Suggested patch
-    assert client_key not in caplog.text
-    assert "Blockscout-MCP-Pro-Api-Key" not in caplog.text
+    log_text = caplog.text.lower()
+    assert client_key.lower() not in log_text
+    assert "blockscout-mcp-pro-api-key" not in log_text

As per coding guidelines, tests/tools/**/*.py unit tests must provide comprehensive coverage including edge cases.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/tools/test_decorators.py` around lines 280 - 281, The test currently
asserts header leakage using a case-sensitive match; update the two assertions
to perform case-insensitive checks by comparing caplog.text.lower() against
client_key.lower() and against "blockscout-mcp-pro-api-key" (lowercased) so
leaked header names/values are caught regardless of logger casing behavior;
modify the two assertions that reference caplog.text and the literal
"Blockscout-MCP-Pro-Api-Key" accordingly in tests/tools/test_decorators.py.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@SPEC.md`:
- Around line 226-227: Update the contradictory wording so the section
consistently states the auth gate checks the resolved effective key: replace
references that say the gate checks config.pro_api_key first with language that
it checks resolve_pro_api_key() (the resolved effective key), and clarify that a
valid client-supplied key passes, a malformed client key raises a distinct
terminal error (no fallback), and only absence of both client and server keys
raises the not-configured error; ensure both the sentence referencing
config.pro_api_key and the later sentence about guards reference
resolve_pro_api_key() uniformly.

In `@tests/test_analytics.py`:
- Around line 200-203: The header-name leak check is case-sensitive; change the
second assertion to compare lowercased strings so casing won't miss leaks:
compute a lowercased version of all_text (use all_text.lower()) and assert that
"blockscout-mcp-pro-api-key".lower() is not in that lowercased text (leave the
client_key assertion as-is). Update the assertion using the existing all_text
and the header string "Blockscout-MCP-Pro-Api-Key" to perform the
case-insensitive check.

In `@tests/tools/test_common_blockscout_request_auth.py`:
- Around line 241-255: Add an assertion that the AsyncMock progress callback was
actually awaited during the periodic-progress path: after awaiting
make_request_with_periodic_progress and before resetting _client_key_state (or
immediately after result is obtained), assert that mock_ctx.report_progress was
awaited at least once (e.g. call mock_ctx.report_progress.assert_awaited() or
assert mock_ctx.report_progress.await_count > 0). This ensures the test for
make_request_with_periodic_progress / make_blockscout_request verifies progress
beats were emitted.

---

Nitpick comments:
In `@tests/test_pro_api_key_context.py`:
- Around line 219-236: The tests should exercise the real
extraction→scope→resolve path instead of directly injecting _Malformed(): update
both tests to send malformed header values through pro_api_key_scope (use the
same raw_value strings) so the flow goes through the extractor and scope
creation before resolve_pro_api_key() is called; replace the
_set_key_state(_Malformed()) injection with creating a pro_api_key_scope from a
request/header containing the malformed value and then call
resolve_pro_api_key() (or the same resolve helper) so the ValueError assertion
confirms the redaction behavior in the actual request path.

In `@tests/tools/contract/test_fetch_and_process_contract.py`:
- Line 262: Move the from blockscout_mcp_server.pro_api_key_context import
_Malformed statement to the top of the module alongside the other imports from
pro_api_key_context (e.g., update the existing import at the top to also import
_Malformed) and delete the duplicate import at line 262 so the module only
imports _Malformed once at the file header.

In `@tests/tools/test_decorators.py`:
- Around line 280-281: The test currently asserts header leakage using a
case-sensitive match; update the two assertions to perform case-insensitive
checks by comparing caplog.text.lower() against client_key.lower() and against
"blockscout-mcp-pro-api-key" (lowercased) so leaked header names/values are
caught regardless of logger casing behavior; modify the two assertions that
reference caplog.text and the literal "Blockscout-MCP-Pro-Api-Key" accordingly
in tests/tools/test_decorators.py.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 381f728e-6a5b-4fab-9d9d-57c66db6a52c

📥 Commits

Reviewing files that changed from the base of the PR and between d67888a and 31e245e.

📒 Files selected for processing (41)
  • .env.example
  • AGENTS.md
  • Dockerfile
  • README.md
  • SPEC.md
  • blockscout_mcp_server/__init__.py
  • blockscout_mcp_server/config.py
  • blockscout_mcp_server/llms.txt
  • blockscout_mcp_server/pro_api_key_context.py
  • blockscout_mcp_server/tools/address/get_address_info.py
  • blockscout_mcp_server/tools/address/get_tokens_by_address.py
  • blockscout_mcp_server/tools/address/nft_tokens_by_address.py
  • blockscout_mcp_server/tools/block/get_block_info.py
  • blockscout_mcp_server/tools/block/get_block_number.py
  • blockscout_mcp_server/tools/chains/get_chains_list.py
  • blockscout_mcp_server/tools/common.py
  • blockscout_mcp_server/tools/contract/_shared.py
  • blockscout_mcp_server/tools/contract/get_contract_abi.py
  • blockscout_mcp_server/tools/contract/inspect_contract_code.py
  • blockscout_mcp_server/tools/contract/read_contract.py
  • blockscout_mcp_server/tools/direct_api/direct_api_call.py
  • blockscout_mcp_server/tools/ens/get_address_by_ens_name.py
  • blockscout_mcp_server/tools/initialization/unlock_blockchain_analysis.py
  • blockscout_mcp_server/tools/search/lookup_token_by_symbol.py
  • blockscout_mcp_server/tools/transaction/get_token_transfers_by_address.py
  • blockscout_mcp_server/tools/transaction/get_transaction_info.py
  • blockscout_mcp_server/tools/transaction/get_transactions_by_address.py
  • blockscout_mcp_server/web3_pool.py
  • pyproject.toml
  • server.json
  • tests/integration/test_common_helpers.py
  • tests/test_analytics.py
  • tests/test_config.py
  • tests/test_pro_api_key_context.py
  • tests/test_pro_api_key_http_transport.py
  • tests/test_web3_pool.py
  • tests/tools/contract/test_fetch_and_process_contract.py
  • tests/tools/test_common_blockscout_request_auth.py
  • tests/tools/test_common_metadata.py
  • tests/tools/test_common_post_request.py
  • tests/tools/test_decorators.py

Comment thread SPEC.md Outdated
Comment thread tests/test_analytics.py Outdated
Comment thread tests/tools/test_common_blockscout_request_auth.py
akolotov and others added 6 commits June 5, 2026 13:51
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>
Trim implementation mechanics from the "Blockscout PRO API
Authentication" section per the SPEC.md preference for concise,
high-level descriptions: drop private symbol names (decorator/ContextVar
internals, JSON-RPC injection internals) in favor of module pointers,
while preserving the security invariants (per-request scoping, no
log/cache leakage, no cross-contamination) and removing the redundant
resolved-key paragraph.

@coderabbitai ignore

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Route malformed-key redaction tests through the real extraction → scope →
  resolve path instead of injecting _Malformed() directly, so redaction is
  verified in the actual request flow.
- Move the _Malformed import to the module header in the contract test.
- Make header name/value leak checks case-insensitive in the decorator and
  analytics tests.
- Assert the periodic-progress callback was actually awaited.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…oth test sections

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@akolotov akolotov merged commit 9e75aae into main Jun 5, 2026
8 checks passed
@akolotov akolotov deleted the claude/angry-franklin-4316be branch June 5, 2026 21:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Allow MCP Clients to Supply Blockscout PRO API Key

1 participant