The MCP SDK supports URL mode elicitation via session.elicit_url(), but
the mcp-use Context only exposed form mode via ctx.elicit(). This adds a
clean ctx.elicit_url(message, url) wrapper so Python server authors
don't need to drop down to the raw session API.
- Auto-generates elicitation_id if not provided
- Tracks telemetry as "elicit_url"
- Forwards related_request_id from context
- Includes docstring with usage example
- 6 unit tests covering delegation, actions, telemetry, and ID generation
* fix(python): respect configured inspector path
* fix(python): treat inspector path as a base prefix
* fix(python): format inspector fixes for ruff
* fix(ci): run conformance with node 22
* fix(ci): fail conformance runs with empty results
* chore(ci): drop inline conformance result validation
* docs(python): clarify inspector path behavior
* feat(python/server): add mcp_logs_only option to suppress HTTP access logs
When mcp_logs_only=True, all uvicorn access logs are suppressed. MCP
protocol logs (printed directly by MCPLoggingMiddleware) continue to
show. This eliminates the duplicate INFO: lines for MCP requests and
hides non-MCP HTTP traffic (/docs, /inspector, static files).
The implementation is simple: MCPAccessFormatter returns an empty string
when the flag is set, since the middleware already handles MCP logging
via direct stdout prints.
Closes#1162
* fix(python/server): use filter instead of empty string to suppress access logs
Replace the formatter returning "" (which still outputs a blank line)
with a proper MCPLogsOnlyFilter that drops log records entirely.
* docs: update
* docs(python): document mcp_logs_only option in server logging docs
* feat(python/server): auto-retry next available port when default is in use
Add port availability checking before starting uvicorn. When the
requested port is occupied, the server automatically tries the next
port (up to 10 retries) instead of crashing.
Refs #1161
* test(python/server): add unit tests for port retry helpers
Tests for _is_port_available and _find_available_port covering:
- Port available/unavailable detection
- Skipping to next port when current is in use
- RuntimeError when all ports exhausted
* docs(python): document automatic port retry in server running docs
* fix(python/server): simplify tool JSON Schema for Optional parameters
Pydantic generates {"anyOf": [{"type": "T"}, {"type": "null"}]} for
Optional[T] tool parameters. While valid JSON Schema 2020-12, this
pattern breaks description rendering in the MCP Inspector UI and fails
Gemini API validation (requires top-level type field). MCP signals
optionality via the required array, making the anyOf/null wrapper
redundant.
- Add simplify_optional_schema() utility that collapses nullable anyOf
into flat {"type": "T"} while preserving description, default, title
- Override MCPServer.add_tool() to apply simplification after Pydantic
generates the schema
- Add 12 unit tests for the schema simplifier (test_json_schema.py)
- Add 23 unit tests for tool schema output (test_list_tool_format.py)
- Update server_example.py with representative parameter patterns
including Literal enum types
* docs(python/server): add parameter types and JSON Schema section to tools docs
Document the Python-to-JSON-Schema type mapping, required/optional/nullable
parameter patterns, and Literal enum usage. Includes a note explaining the
anyOf/null simplification that mcp-use applies automatically.
* feat(python/server): add icons parameter to MCPServer constructor
Pass through the icons parameter to FastMCP so server-level icons can
be set directly in the MCPServer constructor.
* docs(python/server): document icons parameter in server overview
* docs(python): update server example with icons and Literal enum params
* style: format server_example.py
* test(python): add unit tests for icons constructor parameter
* docs: regenerate API reference for icons param
* fix(python/server): simplify tool JSON Schema for Optional parameters
Pydantic generates {"anyOf": [{"type": "T"}, {"type": "null"}]} for
Optional[T] tool parameters. While valid JSON Schema 2020-12, this
pattern breaks description rendering in the MCP Inspector UI and fails
Gemini API validation (requires top-level type field). MCP signals
optionality via the required array, making the anyOf/null wrapper
redundant.
- Add simplify_optional_schema() utility that collapses nullable anyOf
into flat {"type": "T"} while preserving description, default, title
- Override MCPServer.add_tool() to apply simplification after Pydantic
generates the schema
- Add 12 unit tests for the schema simplifier (test_json_schema.py)
- Add 23 unit tests for tool schema output (test_list_tool_format.py)
- Update server_example.py with representative parameter patterns
including Literal enum types
* docs(python/server): add parameter types and JSON Schema section to tools docs
Document the Python-to-JSON-Schema type mapping, required/optional/nullable
parameter patterns, and Literal enum usage. Includes a note explaining the
anyOf/null simplification that mcp-use applies automatically.
* fix(python/server): address PR review comments on json_schema.py
- Fix _simplify_property docstring: says "in place" but returns new dict
- Fix simplify_optional_schema docstring: document fast-path return when
no properties exist (returns original, not a copy)
- Wrap dict comprehension for line length
* style: format server_example.py
* docs: regenerate API reference for json_schema module
* fix: use WeakSet for resource subscriptions to prevent memory leak on client disconnect
Fixes#1092
Previously, resource subscriptions tracked sessions by storing id(session)
integers in plain sets. When clients disconnected without explicitly
unsubscribing, these integer IDs accumulated indefinitely since there was
no cleanup mechanism for dropped connections.
This change replaces the dict[str, set[int]] with dict[str, WeakSet],
storing weak references to session objects directly. When a session is
garbage-collected (after disconnect), its entry is automatically removed
from all subscription sets.
This also simplifies notify_resource_updated() — instead of iterating all
active sessions and checking membership by id, we now iterate the WeakSet
subscribers directly, which is both correct and more efficient.
* style: use idiomatic emptiness check for WeakSet
Replace `if not len(subscribers)` with `if not subscribers` for consistency
with the existing check in notify_resource_updated().
* fix: clean up empty subscription entries + add unit tests
- Clean up empty dict entries in notify_resource_updated() after all
sessions for a URI are GC'd, preventing stale keys from accumulating
- Add 4 unit tests for GC-driven subscription cleanup:
- test_subscription_cleanup_after_session_gc
- test_notify_skips_gc_collected_sessions
- test_notify_does_not_crash_when_all_sessions_gone
- test_multiple_uris_independent_cleanup
---------
Co-authored-by: User <user@example.com>
Co-authored-by: pietrozullo <pietro.zullo@gmail.com>
* fix(python): register debug routes when debug=True passed to run()
When MCPServer was created with debug=False (default) and run(debug=True)
was called, debug routes (/docs, /inspector, /openmcp.json) were never
registered because _add_dev_routes() only ran during __init__.
Now run(debug=True) calls _add_dev_routes() and rebuilds the Starlette
app so routes are available. Also removes dead code in ServerRunner that
could never execute due to the same ordering issue.
Closes#1099
* docs: regenerate API reference after removing debug from ServerRunner.run()
The debug parameter was removed from ServerRunner.run() since debug mode
is now fully handled in MCPServer.run() before the runner is invoked.
* Fix DNS rebinding protection for cloud/proxy deployments
- Add host/port parameters to MCPServer.__init__() and pass to FastMCP
- Default host to "0.0.0.0" (cloud-friendly, disables DNS protection)
- "127.0.0.1" auto-enables DNS protection (handled by FastMCP)
- run() can override host/port, falls back to __init__ values
- Add comprehensive docstring to __init__() explaining parameters
- Update documentation
This fixes 421 Misdirected Request errors when running behind reverse
proxies (Cloudflare, nginx, etc.) where the Host header doesn't match
the auto-configured allowed_hosts for localhost.
* Fix DNS rebinding protection for cloud/proxy deployments
- Add host/port parameters to MCPServer.__init__() and pass to FastMCP
- Default host to "0.0.0.0" (cloud-friendly, disables DNS protection)
- "127.0.0.1" auto-enables DNS protection (handled by FastMCP)
- run() can override host/port and reconfigures DNS protection accordingly
- Rebuild app when host changes in run() to apply new security settings
- Add comprehensive docstring to __init__() explaining parameters
- Add unit tests for host/port configuration and run() overrides
- Update documentation
This fixes 421 Misdirected Request errors when running behind reverse
proxies (Cloudflare, nginx, etc.) where the Host header doesn't match
the auto-configured allowed_hosts for localhost.
* ruff lint
* Bump mcp minimum version to 1.23.0
Required for TransportSecuritySettings and DNS rebinding protection
auto-configuration feature that was added in v1.23.0.
* feat(server): add bearer token authentication for Python MCP server
Add HTTP-layer bearer token authentication that returns proper 401 responses.
- BearerAuthProvider: abstract base class with verify_token() method
- AccessToken: model with token, claims, and scopes fields
- AuthMiddleware: Starlette middleware returning HTTP 401 for invalid tokens
- get_access_token() / require_auth(): context helpers for tools
Usage:
```python
class MyAuth(BearerAuthProvider):
async def verify_token(self, token: str) -> AccessToken | None:
if token != "secret":
return None
return AccessToken(token=token, claims={"user": "alice"})
server = MCPServer(name="my-server", auth=MyAuth())
```
Closes#840
* autogenerate docs
* test(server): add unit and integration tests for bearer token auth
Unit tests:
- AccessToken model creation and serialization
- BearerAuthProvider abstract class and subclassing
- get_access_token/require_auth context helpers
- AuthMiddleware 401 responses, path exclusion, CORS
Integration tests:
- MCPClient connecting to bearer auth server with valid token
- Connection failure with invalid/missing token
- Tool access to user claims and scopes
* fix(server): address code review feedback for bearer auth
- Fix CORS security: allow_credentials=False with allow_origins=["*"]
- Remove dead try/except import for auth module
- Remove PII (email) from debug logs
- Add comment explaining broad exception handling in verify_token
- Add /openmcp.json to docs excluded paths list
- Remove set_access_token from public API (internal only)
- Create AuthenticationError instead of using ValueError
- Skip middleware tests on older httpx versions (TestClient compat)
* docs: update API documentation for auth module
* fix(server): address Copilot review feedback
- Fix bearer.mdx: require_auth raises AuthenticationError, not ValueError
- Fix API docs: claims and scopes are optional with defaults, not required
- Remove deprecated SSE transport from bearer_auth_server.py test server
* autogenerate docs
* fix(server): address renvins review feedback
- Improve bearer.mdx claims documentation with explanation and examples
- Remove unnecessary cast() for AuthMiddleware in server.py
- Refactor auth tests to use pytest fixtures for cleaner setup/teardown
* docs(auth): clarify that all MCP endpoints are automatically protected
- Add "What Gets Protected" section explaining automatic protection
- Clarify that get_access_token/require_auth are for accessing token data, not protection
- Update examples to show proper usage pattern
- Improve Note at bottom to be clearer
* fix(auth): prevent path prefix bypass in auth middleware
Security fix: Use exact path matching or segment-based prefix matching
instead of simple startswith() to prevent auth bypass attacks.
Before: /docs-secret would bypass auth because it starts with /docs
After: Only /docs and /docs/* bypass auth, /docs-secret requires auth
Added tests for path matching security edge cases.
* refactor(auth): update import paths for authentication components
- Changed import statements in multiple documentation files and source files to reflect the new structure, moving authentication-related imports to the `auth` submodule.
- Updated examples to ensure clarity and consistency in usage patterns for `BearerAuthProvider`, `AccessToken`, `get_access_token`, and `require_auth`.
* Fix DNS rebinding protection for cloud/proxy deployments
- Add host/port parameters to MCPServer.__init__() and pass to FastMCP
- Default host to "0.0.0.0" (cloud-friendly, disables DNS protection)
- "127.0.0.1" auto-enables DNS protection (handled by FastMCP)
- run() can override host/port, falls back to __init__ values
- Add comprehensive docstring to __init__() explaining parameters
- Update documentation
This fixes 421 Misdirected Request errors when running behind reverse
proxies (Cloudflare, nginx, etc.) where the Host header doesn't match
the auto-configured allowed_hosts for localhost.
* Fix DNS rebinding protection for cloud/proxy deployments
- Add host/port parameters to MCPServer.__init__() and pass to FastMCP
- Default host to "0.0.0.0" (cloud-friendly, disables DNS protection)
- "127.0.0.1" auto-enables DNS protection (handled by FastMCP)
- run() can override host/port and reconfigures DNS protection accordingly
- Rebuild app when host changes in run() to apply new security settings
- Add comprehensive docstring to __init__() explaining parameters
- Add unit tests for host/port configuration and run() overrides
- Update documentation
This fixes 421 Misdirected Request errors when running behind reverse
proxies (Cloudflare, nginx, etc.) where the Host header doesn't match
the auto-configured allowed_hosts for localhost.
* ruff lint
* Bump mcp minimum version to 1.23.0
Required for TransportSecuritySettings and DNS rebinding protection
auto-configuration feature that was added in v1.23.0.