chore(deps): update mcp requirement from <2,>=1.0 to >=1.0,<3 - #39
Open
dependabot[bot] wants to merge 194 commits into
Open
dependabot[bot] wants to merge 194 commits into
dependabot[bot] wants to merge 194 commits into
Conversation
29 MCP tools across 3 modules: - Proxmox (18 tools): monitoring, VM/CT lifecycle, storage, network, command execution - SSH (4 tools): direct host access with sync/async exec pattern - iLO (7 tools): HP hardware management via SSH tunnel through pve1 Features: - Modular architecture with conditional tool registration - QEMU Guest Agent + LXC exec auto-detection for in-VM commands - Async command execution with polling for long-running operations - Infrastructure context via YAML resource and MCP prompt - Environment-based config with graceful degradation Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- tests/test_integration.py: ~50 tests across all modules (Proxmox monitoring, system, exec QEMU/LXC, VM lifecycle, SSH, iLO, resources, error handling). Runs against real infrastructure with --section filtering and --test-vmid for destructive VM lifecycle tests. - README.md: full installation guide in French covering server-side setup (API tokens, QEMU Guest Agent, SSH, iLO), .env config, Claude Code integration, tool reference, and troubleshooting. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Adds shields.io badges (Python, MCP, Proxmox, iLO, license, Claude Code), a features highlight section, concrete usage examples showing diagnostic workflow, and a license/footer section. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- __main__.py: dual transport (stdio + --http mode with Streamable HTTP) Bearer token auth via TARKAMCP_AUTH_TOKEN, /health endpoint - deploy/tarkamcp.service: systemd unit for running on Proxmox nodes - deploy/install.sh: one-command install script with auto-generated token - README: connection instructions for Claude (Code/web/mobile), ChatGPT, Gemini (CLI/API), Cloudflare Tunnel setup, new badges - .env.example: added TARKAMCP_AUTH_TOKEN, PORT, HOST vars Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Claude web's MCP connector doesn't support bearer tokens (only OAuth, which is optional). Auth token is now optional: - If TARKAMCP_AUTH_TOKEN is set: bearer token required (ChatGPT, Gemini) - If not set: open access, rely on Cloudflare Zero Trust for security Updated README with accurate Claude web connector instructions matching the actual "Add custom connector" dialog. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Bearer tokens don't work with Claude web (only supports OAuth, which is overkill for personal use). New approach: the secret is embedded in the URL path itself (/s/<secret>/mcp), like webhook URLs. - Any request to a path without the secret gets a 404 - The URL is the credential -- treat it like a password - Works with ALL platforms (Claude, ChatGPT, Gemini) with zero auth config - TARKAMCP_SECRET env var replaces TARKAMCP_AUTH_TOKEN Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Implements a real OAuth 2.1 client_credentials flow: - CLI: `tarkamcp auth create/list/revoke` to manage client credentials - OAuth metadata at /.well-known/oauth-authorization-server - Token endpoint at POST /oauth/token (client_credentials grant) - Bearer token validation on all MCP endpoints - Tokens expire after 24h, clients re-authenticate automatically Works with all platforms: - Claude web/mobile: Client ID + Secret in the OAuth fields - ChatGPT/Gemini: POST /oauth/token to get a bearer token - Claude Code: unchanged (stdio, no auth needed) New files: - src/tarkamcp/auth.py: ClientStore (JSON) + TokenStore (in-memory) - Updated __main__.py with auth subcommands and OAuth endpoints Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
TarkaMCP is now exclusively a remote HTTP server: - `tarkamcp serve` (or bare `tarkamcp`) starts the HTTP server - `tarkamcp auth create/list/revoke` manages OAuth clients - No more --http flag, no more stdio transport - Greatly simplified README: single installation flow, no dual-mode docs - Updated CLAUDE.md to reflect HTTP-only architecture Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- tarkamcp.service: replace non-existent --http flag with serve subcommand
(the CLI only exposes serve/auth; --http caused the service to fail on boot).
- proxmox/system.py: pass guest-agent exec `command` as an array instead of
the invented arg0/arg1 form. The previous encoding caused PVE to drop all
arguments, running only the binary path.
- proxmox/system.py: remove the non-existent /nodes/{node}/lxc/{vmid}/exec
call and return actionable guidance (use ssh_exec_command + pct exec).
Applied to both sync and async paths; the async path no longer leaves LXC
sessions stuck in "running" forever.
- proxmox/system.py: guard proxmox_storage_status against entries missing the
"storage" key.
- proxmox/vms.py: use /status/reboot for both QEMU and LXC (PVE has no
/status/restart for LXC).
- proxmox/vms.py: clone uses `hostname` for LXC and `name` for QEMU, matching
the PVE API schema.
- ssh/client.py: retain a strong reference to the asyncio.create_task used
for background SSH runs so the task can't be garbage-collected mid-run.
- tests/test_integration.py: align the LXC exec test with the new
API-unsupported error contract.
Logic / correctness - proxmox_list_nodes no longer duplicates members in a real cluster. PVE returns the full cluster view from any reachable member, so we stop at the first successful GET /nodes and only fall back to other configured nodes when the first is unreachable. Unreachable nodes are still reported so stale credentials are visible. - Bound _exec_sessions and _ssh_sessions: completed sessions older than 1h are pruned whenever a new one is inserted, avoiding unbounded growth on a long-running server. - Removed dead code ProxmoxClient._resolve_node. Auth / OAuth hardening - ClientStore.verify now uses hmac.compare_digest for constant-time hash comparison. - ClientStore._save writes atomically (tmp + replace) with chmod 0600 so the secret-hash file is never world-readable. - ClientStore._load surfaces a clear error on malformed JSON instead of crashing opaquely inside json.loads. - OAuth metadata no longer advertises response_types_supported: ["token"], which is meaningless for the client_credentials grant. Misc - ilo/client.py: asyncio.get_running_loop() (get_event_loop is deprecated when a loop is already running) + drop pointless f-string. - README: correct the iLO section (the SSH tunnel is always used, not a direct connection) and fix the LXC update example since the API has no LXC exec endpoint -- the AI uses ssh + pct exec.
Install script now provisions python3/python3-venv, creates a dedicated virtualenv at /opt/tarkamcp/.venv, installs a /usr/local/bin/tarkamcp wrapper, and the systemd unit launches the server via `serve` from the venv instead of the system python with the removed `--http` flag. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Always refresh apt and install python3-venv plus the versioned variant (python3.X-venv) required on Debian. Recreate the venv when bin/pip is absent instead of trusting the directory's existence, so a half-created venv from a prior run no longer breaks the install. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Claude Web and other interactive MCP connectors require the authorization code flow with PKCE. Previously only client_credentials was supported, causing /authorize to return 401 and the connector to fail. - Add /oauth/authorize endpoint enforcing PKCE S256 and https redirect_uri (localhost allowed for development). - Extend /oauth/token to support grant_type=authorization_code with one-time code consumption bound to client_id, redirect_uri, and PKCE code_verifier. client_credentials remains for script/server use. - Update OAuth metadata to advertise both grants, code response type, and S256 as the only PKCE method. - Explicit /oauth/register endpoint returns 403 with a clear message: dynamic client registration is disabled, clients must be provisioned via `tarkamcp auth create`. - Filter form values to str to avoid UploadFile leaking into credential checks. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The FastMCP streamable-HTTP app initializes its session manager task group via its own lifespan. When it is Mount()ed under a parent Starlette, only the parent's lifespan runs, so requests to /mcp failed with "Task group is not initialized. Make sure to use run()." Wrap the child lifespan in the parent's and pass it via lifespan=. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Without an explicit TransportSecuritySettings, the MCP SDK defaults to only allowing localhost Host headers, which returned 421 Misdirected Request for any request hitting the public hostname (e.g. mcp.example.com). Expose TARKAMCP_ALLOWED_HOSTS and TARKAMCP_ALLOWED_ORIGINS (comma separated) so operators declare their public host(s). Defaults keep localhost working and preauthorize Claude/ChatGPT/Gemini origins. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The DNS-rebinding allowlist is now a required-in-prod env var: without the public hostname declared, every request returns 421. Call it out in the Cloudflare section, the .env reference block, and the troubleshooting table. Also document the 'unauthorized' error on /authorize when the client ID has not been provisioned. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
python-hpilo hardcodes the literal string "localhost" as a signal to switch to ILO_LOCAL mode — it shells out to /sbin/hponcfg and ignores host, port, login and password. Because we tunnel via SSH and pointed hpilo.Ilo at "localhost", every iLO tool error was "hponcfg not installed" on machines without the HPE utility. Use 127.0.0.1 instead so the RIBCL over HTTPS path is taken through the tunnel. No other change needed. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds a second authentication factor (Google Authenticator / Authy compatible TOTP) on top of client_id/client_secret. The server is publicly exposed via Cloudflare Tunnel and client_secret leaks would let an attacker pilot the Proxmox infra end-to-end; 2FA closes that. Enrollment `tarkamcp auth create` now also generates a base32 TOTP seed and prints the otpauth:// URI plus an ASCII QR code to scan once into the authenticator app. Seed is stored plaintext in clients.json (0600) — TOTP validation requires the original secret, not a hash. Browser flow (Claude Web, ChatGPT MCP) /oauth/authorize is split into GET (renders an HTML form asking for the 6-digit code, with hidden inputs preserving the original OAuth params) and POST (revalidates params, validates TOTP, then issues the authorization code). The connector never sees the popup — it's rendered during the redirect, like a consent screen. Non-interactive flow (client_credentials for ChatGPT curl / Gemini) /oauth/token now requires a `totp` field alongside client_id and client_secret for client_credentials. Missing or invalid TOTP returns invalid_grant. The authorization_code grant does not require TOTP because the code itself proves the user passed 2FA at /authorize. Hardening 5 failed TOTP attempts per client_id trigger a 5-minute lockout (in-memory counter, reset on first success). pyotp's valid_window=1 tolerates ±30 s clock drift. The HTML form uses html.escape on all dynamic fields and keeps PKCE S256 mandatory. Migration clients.json entries without a totp_secret are filtered at load, logged to stderr, and removed from disk. Operators must recreate every existing client after upgrade — there is no backwards-compat path. Docs README updated: new 2FA section in the create step, popup mention for Claude, `totp=` added to every curl/Gemini example (with a warning against embedding the seed in code), new troubleshooting rows for invalid_grant / lockout / silent revocation after upgrade. Deps pyotp and qrcode added to pyproject.toml. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Claude Web probes /.well-known/oauth-protected-resource and /.well-known/oauth-protected-resource/mcp before running the OAuth flow, per MCP 2025-06-18. We did not serve those paths, so they fell through to the authenticated MCP mount and returned 401, which caused Claude to show "unauthorized" even though everything else worked. - Add a /.well-known/oauth-protected-resource handler (plus the resource-scoped /mcp variant) that returns RFC 9728 metadata pointing at ourselves as the authorization server. - Whitelist both paths in auth_middleware. - Emit WWW-Authenticate: Bearer realm=..., resource_metadata=<url> on 401 responses so unauth'd clients can discover the AS per spec. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Lets the model voluntarily shorten the lifetime of its own access token
once a task is finished. Reduces the replay window if a token leaks,
and forces the next session to go through the full OAuth + 2FA flow.
Plumbing:
- ContextVar ``current_bearer_token`` in tarkamcp.auth, set by the HTTP
auth middleware around call_next and cleared in a finally block.
- TokenStore gains a ``revoke(token)`` method (single-use pop from the
in-memory dict).
- The middleware registers its TokenStore via ``register_token_store``
so the tool reaches it without circular imports.
- New module ``tarkamcp.security.tools`` exposes the MCP tool
``security_end_session`` which calls ``revoke_current_token()``
and returns ``{"revoked": bool, ...}``. The docstring tells the
model to call it at the very end of a task, never mid-flow.
- ``server.py`` registers the new tool alongside the Proxmox/SSH/iLO
modules.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Revoking the token synchronously from inside an MCP tool was racing against the streamable-HTTP response: the JSON-RPC result goes back to the client via an SSE stream that re-enters the auth middleware, so by the time the client tried to read the tool's response the bearer was already gone and Claude showed "Authentication required" before the answer surfaced. TokenStore.revoke now pulls the token's expires_at down to now + 30s instead of popping it immediately, giving the in-flight response and any immediate follow-up enough time to complete. The standard validate() path enforces the new deadline, so the second a fresh request arrives past the window it is rejected as before. The security_end_session tool advertises the grace period in its response so the model knows the token isn't dead instantly. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
8s is enough for the streamable-HTTP response plus any immediate SSE follow-up, and it closes the replay window faster after the model calls security_end_session. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Bundle the 512×512 WebP logo in src/tarkamcp/assets/logo.webp and expose it to clients via FastMCP(icons=[...]) as an inline data URL. Data URL avoids having to open a public static route through the Cloudflare tunnel and keeps the icon available whatever reverse-proxy config is used. Also set website_url to the GitHub repo so clients that surface it can link home. The asset is force-included in the wheel so `pip install -e .` and real installs both ship it. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Captures the brainstormed design: OAuth-backed login with 90-day session cookie, Gemini 3 chat with MCP tool integration, SQLite persistence, light/dark responsive UI, and scoped security + testing plan. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Introduces src/tarkamcp/dashboard with SQLite-backed sessions encrypting the client_secret with AES-GCM, double-submit CSRF, and Starlette routes for /app/login, /app/refresh, /app/logout, plus a placeholder /app/chat. The dashboard mounts only when GEMINI_API_KEY is set; install.sh auto-generates TARKAMCP_SESSION_KEY on first run. 36 unit and integration tests cover the session lifecycle and the auth flows. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Wires the full chat experience: - ConversationStore + messages DB, CRUD API under /app/api/conversations, SSE streaming endpoint /app/api/chat/stream. - ChatEngine abstraction with FakeChatEngine for tests and GeminiChatEngine wiring google-genai with TarkaMCP exposed via StreamableHttpTransport + the user's bearer. - Chat shell template (sidebar + messages + composer), mobile drawer layout, light/dark palette via prefers-color-scheme, inline SVG icons (zero Unicode emoji), minimal safe markdown renderer in chat.js. - Auto-title after first turn, tool-call cards with args/result/duration, model and effort selectors persisted per conversation. - README section documenting /app/login and /app/chat, the 24h TOTP refresh flow, and the SQLite schema. Thirty-six additional tests cover the conversation CRUD, SSE events, tool-call persistence, session-expired re-prompt, and turn history. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The real API model names are gemini-3-flash-preview and gemini-3.1-pro-preview. The initial commit used the shorter aliases which return 404 on generateContent. Schema migrates to v2 and rewrites stored model IDs on existing conversations and messages so past chats keep resolving. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Google's remote-MCP-server tool (McpServer) is preview-gated and rejects standard API keys with 403 PERMISSION_DENIED. The google-genai SDK also supports a second mode where we open a local MCP ClientSession from the dashboard process and hand it to the SDK as a tool; Gemini then sees the tools as regular function declarations and the SDK orchestrates call/response, bypassing the preview restriction. - GeminiChatEngine now uses httpx.AsyncClient + streamable_http_client + ClientSession against the dashboard's own 127.0.0.1:<port>/mcp endpoint with the user's bearer, then passes the session to the SDK via tools=[session]. All orchestration is fully async. - Adds gemini-2.5-flash and gemini-2.5-pro to VALID_MODELS for users without preview access; defaults to gemini-2.5-flash. - Thinking config now branches: Gemini 3 uses thinking_level enum (MINIMAL/LOW/MEDIUM/HIGH); Gemini 2.5 uses thinking_budget in tokens mapped from the same four effort names. - Three new unit tests cover the thinking-config branching. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Self-review pass on the previous commit: - SSH_ROOT_PW and VAR_NAME were not needed by the config job. The first is a commented-out line in the template, the second is a word in a prose comment. Verified by dropping each in turn. - The dist/ artifact upload had no consumer; there is no release pipeline. - A comment still described the force-include table this branch removed, and the dependabot comment claimed a security-only filter that open-pull-requests-limit does not implement (5 is also the default).
A full read of src/ (auth, dashboard, proxmox, ssh, bmc, deploy) turned up one hole that mattered and a set of smaller ones. The important one: the chat's human-approval gate covered ssh_run / proxmox_run but not proxmox_write_file, so an instruction injected through a log line, a config file or a web-search result could write ~/.ssh/authorized_keys on a guest with no modal -- exec through the front door was gated, exec through the side door was not. - chat: _NEEDS_CONFIRMATION now covers guest-file writes and the destructive tools (vm_bulk_action, vm_stop/restart/migrate, snapshot rollback/delete, backup_restore, bmc_power_off/reset). New _CONFIRM_WHEN_ARG_PRESENT gates proxmox_vm_config only when `updates` is set; dry_run=True calls and exec_id-only polling still skip the modal. - bmc: BMCDevice.verify_tls was declared but never parsed from the YAML, so `verify_tls: true` was silently ignored and every Redfish call ran with certificate verification off -- while sending the BMC admin password in Basic Auth on each request. Parse it, surface it in validate-config, document it in beaconmcp.yaml.example. - bmc: stop passing the BMC password as `ipmitool -P <pw>`; argv is world-readable through /proc/<pid>/cmdline. Use `-E` + IPMI_PASSWORD. - dashboard: chmod 0600 dashboard.db and its WAL sidecars. clients.json and tokens.db were locked down already, but this file stores mcp_bearer in plaintext and was left at the process umask. - deploy: chmod 600 the .env written by install.sh and by `beaconmcp init`; add UMask=0077 / NoNewPrivileges / PrivateTmp to the systemd unit. - auth: is_trusted_redirect_uri matched loopback callbacks by string prefix, so http://localhost:1@attacker.example/cb passed. Resolve the parsed hostname instead. /oauth/authorize caught this downstream, the DCR register endpoint did not. - proxmox: every endpoint is an f-string with caller-supplied path segments (snapname, storage, archive). Reject anything not shaped like a plain segment before the request leaves the process. - audit: redact session_id, code_verifier, totp_secret, session_key, access_token and private_key. - dashboard: /app/overview and /app/usage passed _render's arguments in the wrong order and 500'd on every request. Tests: 341 passed.
ci: add config/build/docker jobs, fix broken wheel build
Bumps [docker/setup-buildx-action](https://github.com/docker/setup-buildx-action) from 3 to 4. - [Release notes](https://github.com/docker/setup-buildx-action/releases) - [Commits](docker/setup-buildx-action@v3...v4) --- updated-dependencies: - dependency-name: docker/setup-buildx-action dependency-version: '4' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com>
Bumps [actions/setup-python](https://github.com/actions/setup-python) from 5 to 7. - [Release notes](https://github.com/actions/setup-python/releases) - [Commits](actions/setup-python@v5...v7) --- updated-dependencies: - dependency-name: actions/setup-python dependency-version: '7' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com>
Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 7. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](actions/checkout@v4...v7) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: '7' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com>
Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 6 to 7. - [Release notes](https://github.com/docker/build-push-action/releases) - [Commits](docker/build-push-action@v6...v7) --- updated-dependencies: - dependency-name: docker/build-push-action dependency-version: '7' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com>
…cker/build-push-action-7 chore(ci): bump docker/build-push-action from 6 to 7
…tions/checkout-7 chore(ci): bump actions/checkout from 4 to 7
…tions/setup-python-7 chore(ci): bump actions/setup-python from 5 to 7
…cker/setup-buildx-action-4 chore(ci): bump docker/setup-buildx-action from 3 to 4
…gate The new dry_run escape in _tool_call_requires_confirmation was applied to every gated tool, but only the three snapshot tools declare the parameter. FastMCP validates arguments with a plain pydantic model, so extra='ignore' silently drops an undeclared dry_run server-side: ssh_run with command="rm -rf /" and dry_run=True skipped the modal and then ran for real. That is a bypass of the pre-existing ssh_run / proxmox_run gate, and it is reachable through exactly the injected instruction the gate exists to stop. Restrict the escape to _DRY_RUN_AWARE. Two tests pin it: the bypass shape on six tools that lack the parameter, and a check that every allow-listed tool really declares dry_run so the list cannot drift. Also skip the dashboard.db permission test on Windows, where os.chmod only toggles the read-only bit.
The example config gained a third BMC device to document verify_tls, and
the config job enumerates the template's ${VAR} references explicitly, so
validate-config rejected the unset one and the job went red.
* docs: split the README into focused guides The README had grown to 359 lines and covered installation, the full config reference, the tool catalogue and the security checklist all at once. Two of the tool section headers were also wrong (VM lifecycle said 7 for 14 entries, system said 3 for 8) and the overview claimed "30+ tools" where the server registers 44. Keep the README as a landing page: what it is, a Docker quick start, a table of contents, the architecture diagram, and a short warning before pointing a model at real infrastructure. Everything else moves out: - docs/installation.md requirements, Docker, systemd, wizard, proxy, updates - docs/configuration.md the two files, YAML keys grouped by section - docs/tools.md all 44 tools with correct per-module counts - docs/security.md tool-call review, token handling, TOTP hygiene The Assistant connector steps only existed in the README, so they move into docs/clients.md next to the other clients. totp-automation.md linked to the now-gone README#connecting-clients anchor three times; those point at clients.md and security.md instead. * @ docs: address review — security tool is always on, TOTP seed guidance `register_security_tools(mcp)` runs unconditionally in server.py, so the tools.md intro was wrong to imply every module disappears with its capability. Spell out that `security_end_session` is always registered. installation.md also told readers to park the raw TOTP seed in a secrets manager, which security.md and clients.md both forbid — it collapses the second factor. Align it and point at the automation doc instead. @
mcp 2.0.0 shipped and renamed mcp.server.fastmcp to mcp.server.mcpserver, along with FastMCP to MCPServer. Every tool module imports the old path, so pip resolving the open-ended >=1.0 to 2.0 broke collection of four test modules on main and on every open PR. Cap at <2 to get CI reporting again. 1.29.0 is the newest 1.x and still ships mcp.server.fastmcp, so the ceiling costs nothing today. Migrating to the 2.0 API is separate work.
#31 split the README into docs/, which collided with this branch's edit to the confirmation paragraph in the old monolithic file. Take the split README as-is and move the substance to its new home in docs/security.md: the gate now covers the write and destructive tools, not just ssh_run and proxmox_run. Also correct docs/dashboard.md, which still described dry_run as exempting any gated tool. That was the behaviour dd0cfd6 removed, so the sentence now names the three snapshot tools and says why the argument alone is not evidence.
fix(deps): hold mcp below 2.0 until the MCPServer rename is done
fix(security): close findings from a full security audit
) * feat(auth): passkey sign-in and a confirmation step on both login pages Adds WebAuthn as an alternative second factor on /app/login and /oauth/authorize. A passkey replaces the TOTP code, never the client secret: both pages keep the client_id + client_secret step first, so a stolen passkey is useless on its own -- and the dashboard session has to encrypt the secret anyway to re-mint MCP bearers later. Both pages now confirm before moving on, instead of redirecting the instant 2FA clears: * the validate button shimmers while the request is in flight, and Enter submits as soon as the sixth digit lands; * the screen that follows shows when access expires, offers to enrol a passkey on this device, and waits for "Finish signing in". On /oauth/authorize that also fixes a latent papercut: the authorization code is minted at "Finish", not before, so its 60 s OAuth 2.1 lifetime is no longer burned while a human reads the page. The approval is held as a single-use in-memory ticket instead. Both flows stay fully functional with JavaScript off -- the forms POST and the confirmation panel renders server-side. Storage is a new `passkeys` table (schema v5) holding a credential id, a public key and a signature counter; challenges are in-memory and single-use. Credentials are listed and removable from /app/tokens. Dynamically-registered clients delegate to their owner's passkeys, the same way they already delegate TOTP. py_webauthn is imported lazily: without it the pages simply hide every passkey affordance and TOTP remains the only way in. Same when the browser has no secure context (plain-HTTP LAN deployments). Tests drive the real ceremonies against a software ES256 authenticator built in the test module, covering wrong origin, wrong RP ID, replayed challenge, foreign credential, counter regression and the CSRF rotation that signing in performs. * fix(passkeys): say why passkeys are unavailable instead of hiding silently When the second factor cleared, the confirmation screen simply had no "Add a passkey" button and no explanation. Two independent gates can switch the feature off and neither was surfaced anywhere: * server side, the optional `webauthn` package may not be installed (a fresh `git pull` without `pip install -e .` is enough); * browser side, WebAuthn is only exposed on a secure context, so a plain-HTTP origin has no API to call. Now: * the boot banner prints `Passkeys: enabled` or `disabled - <reason>`; * `beaconmcp doctor` gained a Passkeys section naming the fix command; * the pages render a short hint when the *server* offers passkeys but the *browser* refuses them, instead of dropping the button with no trace. Hiding the affordance from an anonymous visitor is still right -- there is nothing they could do about it -- but the operator now has three places to ask the question and get an answer.
…P tools (#37) * feat(updates): tell signed-in operators about updates, and offer to apply them BeaconMCP cuts no releases and ships no PyPI package: the canonical install is a git clone with a venv and a systemd unit. So "is there an update?" means "is this checkout behind the upstream default branch?", and nothing in the server was answering that question. Operators found out by happening to read the repo. Adds three things. **A notice, for signed-in operators only.** A card on any /app/* page when the checkout is behind: how far, the recent commit subjects, a link to the diff, and the commands to update. GET /app/api/update requires a live session and 401s otherwise -- the exact revision a server runs is free reconnaissance for anyone who hasn't authenticated, and the card is only ever rendered to someone signed in. Dismissing it hides that revision until a newer one lands. **Instructions that match the install**, rather than assuming everyone ran deploy/install.sh. A git checkout gets its own root and its real venv pip path, plus a systemctl line only when a unit file actually exists; a container gets docker compose; a pip distribution gets the git+https URL. **Two MCP tools.** beaconmcp_check_update is read-only. beaconmcp_self_update applies: pull --ff-only, reinstall dependencies, validate the config, then restart. It requires confirm=True, refuses a dirty checkout so local edits are never discarded, and refuses a non-git install. The config validation is a hard gate, not a warning, and it is what makes this safe to run unattended: it shells out to `beaconmcp validate-config` so the *new* code parses the operator's *actual* config. If a setting was renamed or a new one is now required, the checkout is reset to where it started, dependencies are restored, and nothing is restarted -- an update that bricks the server is worse than no update. The check also diffs the incoming .env.example / beaconmcp.yaml.example against the operator's real files (not the local examples, and honouring variables already exported), so the notice can say "this update wants a variable you haven't set" *before* it is applied. The dashboard's "Update now" re-prompts for 2FA: pulling code and restarting is the most privileged thing the panel can do, so a session alone is not the right bar -- same gate as minting a token. Both are switchable: features.updates.enabled is the air-gap switch (no egress, no tools, no notice) and allow_self_update keeps the notice while forbidding the apply, for deployments where updates go through a pipeline. Also fixes __version__, which had been pinned at "0.1.0" while pyproject said 2.0.0 -- it now reads package metadata, with the real number as the source-tree fallback. Tests drive git for real against throwaway repositories: a mocked subprocess would only prove the mock agrees with itself. pip and the validation subprocess are the two steps stubbed, so the pull/validate/ roll-back orchestration is exercised without touching the interpreter running the suite. * fix(updates): mention updates on the post-2FA screen, and stop caches pinning old assets Two gaps found by actually looking at the rendered pages. **The "You're signed in" screen said nothing.** The toast fetches its status once at page load, which on /app/login happens before the session exists -- so it 401'd and stayed empty, and signing in never re-checks because it does not reload the page. The one moment the operator is guaranteed to pass through said nothing about a pending update. login.js now re-asks once the session is created and renders a one-line mention above "Finish signing in". Deliberately not the full card: that screen has a single primary action, and on a narrow viewport a bottom-anchored card this tall would sit on top of it. The card now opts out of the auth pages entirely and shows on the landing page instead. **Browsers could keep running the previous release's JavaScript.** Starlette serves static files with ETag/Last-Modified but no Cache-Control, which leaves browsers on heuristic freshness -- a file untouched for weeks is reused for a long time without ever revalidating. That was survivable when upgrading meant running commands by hand; it is not once the server can update itself and the next page load is expected to match the new backend. This was not theoretical: it bit the browser used to verify the change, which kept executing a stale bundle across several restarts. Asset URLs now carry a fingerprint of the bundle, recomputed at start from the newest mtime in the static directory (which a git pull bumps). New bytes mean a new URL, so no cache can serve it from an old entry -- which also lets the files be cached hard instead of revalidated: ?v= present -> public, max-age=31536000, immutable ?v= absent -> no-cache (a legacy or hand-typed URL can't pin old code) /app/* pages -> no-store (per-session, and they carry the fingerprint) * fix(updates): serialize update work, and keep the restart off the shell Self-review findings on the update flow. **Two updates could run at once.** The dashboard button and the MCP tool reach `apply_update` independently, so nothing stopped a second one starting mid-pull: two `git pull` / `pip install -e .` runs in one checkout fight over index.lock and can leave a half-applied tree, and one caller's rollback could discard the other's successful update. A second caller is now told an update is already running rather than queued behind a pip that may take minutes -- it never touches git. **Cold-cache checks stampeded.** Every dashboard tab opening at once fired its own `git fetch`, piling up 60 s subprocesses for one answer. The uncached path is now single-flighted; waiters get the result the winner cached. **The deferred restart built a shell string.** `service` is the literal "beaconmcp" today, so this was not exploitable, but interpolating it into `sh -c` means a future change that made the unit name configurable would silently become a shell injection. Values now go through argv. All three are covered by tests, and both locks were mutation-checked: removing either makes its test fail (4 concurrent checks instead of 1; "release unlocked lock" when the second updater proceeds).
* feat(proxmox): interactive VM panel via the MCP Apps extension proxmox_vm_panel carries _meta.ui.resourceUri pointing at a ui:// resource served as text/html;profile=mcp-app, which an Apps-capable client renders in a sandboxed iframe: live CPU/RAM/disk, start/stop/restart, and fields for core count and memory. Runs on mcp 1.x. The Apps class that wraps this lives in 2.0 and needs MCPServer, but the two knobs it sets -- meta= on the tool, mime_type= on the resource -- are already on FastMCP, so the panel does not wait on that migration. The panel holds no cluster access of its own. Its buttons issue ordinary tools/call requests for proxmox_vm_start / _stop / _restart / _config, so the client's approval prompt still stands in front of every action. Clients that skipped the extension ignore _meta.ui and get the same snapshot as data, which is why the tool returns the full state rather than a placeholder. Verified against a harness that speaks the host side of the protocol: handshake, initial render, power actions with refresh, config apply sending only changed keys, tool errors surfacing without wedging the controls, and the theme switch. * fix(panel): send ui/initialize params flat, as the host expects The handshake nested appCapabilities under a `capabilities` key and sent `appInfo` as `clientInfo`. The real shape, per the ext-apps App.connect() implementation, is flat: appInfo / appCapabilities / protocolVersion. A rejected handshake is silent -- the host simply does not reply. So the promise never settled, `ui/notifications/initialized` never went out, the host never delivered `ui/notifications/tool-result`, and the panel sat on "Loading..." with an empty frame and nothing in the console. That is what showed up in Claude: the host reported the widget as rendered while the iframe stayed blank. Also surface the failure instead of hanging on it. A handshake that goes unanswered for 5s now replaces the spinner with the reason, so the next protocol mismatch is one glance rather than an afternoon. The browser harness this was first tested against replied to any ui/initialize it received, which is why the bad shape passed. It now validates the params like a host does, and the new test pins the flat shape against the shipped HTML -- it fails on the old file. * feat(panels): log viewer, cluster dashboard, and model-context sync Three additions on top of the VM panel. proxmox_logs_panel renders a node's syslog or task history as a scrollable list with level and substring filters, error and warning lines coloured, and a fullscreen request. Logs are the worst thing to read through a chat transcript: the model summarises them and the lines you wanted are gone. cluster_overview_interactive is cluster_overview as a browsable panel -- node cards with CPU and memory pressure, a searchable guest table with inline start/stop, storage pools with usage bars. It reuses the aggregators' collection helpers rather than re-querying Proxmox its own way. Both panels, and now the VM panel, push state back with ui/update-model-context after an action. Without it the model keeps whatever the tool returned when the panel opened, so stopping a VM from the panel left the next turn believing it was still running. Where the host advertises ui/message, the VM panel offers an "ask about this guest" button and the dashboard an "open" button per row; both are hidden when it does not. Three panels meant three copies of the JSON-RPC bridge, so it moves to apps/bridge.js with the shared look in apps/panel.css, spliced in at the <!--mcp-runtime--> marker when the resource is read. Each panel still ships as one self-contained document. Verified in a browser against a harness that validates the handshake the way a host does: rendering, filters, source switching, inline power actions with reload, context updates and messages arriving with the right shapes, and the graceful path when the host advertises neither capability. * feat(auth): passkey sign-in + confirmation step on both login pages (#36) * feat(auth): passkey sign-in and a confirmation step on both login pages Adds WebAuthn as an alternative second factor on /app/login and /oauth/authorize. A passkey replaces the TOTP code, never the client secret: both pages keep the client_id + client_secret step first, so a stolen passkey is useless on its own -- and the dashboard session has to encrypt the secret anyway to re-mint MCP bearers later. Both pages now confirm before moving on, instead of redirecting the instant 2FA clears: * the validate button shimmers while the request is in flight, and Enter submits as soon as the sixth digit lands; * the screen that follows shows when access expires, offers to enrol a passkey on this device, and waits for "Finish signing in". On /oauth/authorize that also fixes a latent papercut: the authorization code is minted at "Finish", not before, so its 60 s OAuth 2.1 lifetime is no longer burned while a human reads the page. The approval is held as a single-use in-memory ticket instead. Both flows stay fully functional with JavaScript off -- the forms POST and the confirmation panel renders server-side. Storage is a new `passkeys` table (schema v5) holding a credential id, a public key and a signature counter; challenges are in-memory and single-use. Credentials are listed and removable from /app/tokens. Dynamically-registered clients delegate to their owner's passkeys, the same way they already delegate TOTP. py_webauthn is imported lazily: without it the pages simply hide every passkey affordance and TOTP remains the only way in. Same when the browser has no secure context (plain-HTTP LAN deployments). Tests drive the real ceremonies against a software ES256 authenticator built in the test module, covering wrong origin, wrong RP ID, replayed challenge, foreign credential, counter regression and the CSRF rotation that signing in performs. * fix(passkeys): say why passkeys are unavailable instead of hiding silently When the second factor cleared, the confirmation screen simply had no "Add a passkey" button and no explanation. Two independent gates can switch the feature off and neither was surfaced anywhere: * server side, the optional `webauthn` package may not be installed (a fresh `git pull` without `pip install -e .` is enough); * browser side, WebAuthn is only exposed on a secure context, so a plain-HTTP origin has no API to call. Now: * the boot banner prints `Passkeys: enabled` or `disabled - <reason>`; * `beaconmcp doctor` gained a Passkeys section naming the fix command; * the pages render a short hint when the *server* offers passkeys but the *browser* refuses them, instead of dropping the button with no trace. Hiding the affordance from an anonymous visitor is still right -- there is nothing they could do about it -- but the operator now has three places to ask the question and get an answer. * feat(updates): update notice for signed-in operators + self-update MCP tools (#37) * feat(updates): tell signed-in operators about updates, and offer to apply them BeaconMCP cuts no releases and ships no PyPI package: the canonical install is a git clone with a venv and a systemd unit. So "is there an update?" means "is this checkout behind the upstream default branch?", and nothing in the server was answering that question. Operators found out by happening to read the repo. Adds three things. **A notice, for signed-in operators only.** A card on any /app/* page when the checkout is behind: how far, the recent commit subjects, a link to the diff, and the commands to update. GET /app/api/update requires a live session and 401s otherwise -- the exact revision a server runs is free reconnaissance for anyone who hasn't authenticated, and the card is only ever rendered to someone signed in. Dismissing it hides that revision until a newer one lands. **Instructions that match the install**, rather than assuming everyone ran deploy/install.sh. A git checkout gets its own root and its real venv pip path, plus a systemctl line only when a unit file actually exists; a container gets docker compose; a pip distribution gets the git+https URL. **Two MCP tools.** beaconmcp_check_update is read-only. beaconmcp_self_update applies: pull --ff-only, reinstall dependencies, validate the config, then restart. It requires confirm=True, refuses a dirty checkout so local edits are never discarded, and refuses a non-git install. The config validation is a hard gate, not a warning, and it is what makes this safe to run unattended: it shells out to `beaconmcp validate-config` so the *new* code parses the operator's *actual* config. If a setting was renamed or a new one is now required, the checkout is reset to where it started, dependencies are restored, and nothing is restarted -- an update that bricks the server is worse than no update. The check also diffs the incoming .env.example / beaconmcp.yaml.example against the operator's real files (not the local examples, and honouring variables already exported), so the notice can say "this update wants a variable you haven't set" *before* it is applied. The dashboard's "Update now" re-prompts for 2FA: pulling code and restarting is the most privileged thing the panel can do, so a session alone is not the right bar -- same gate as minting a token. Both are switchable: features.updates.enabled is the air-gap switch (no egress, no tools, no notice) and allow_self_update keeps the notice while forbidding the apply, for deployments where updates go through a pipeline. Also fixes __version__, which had been pinned at "0.1.0" while pyproject said 2.0.0 -- it now reads package metadata, with the real number as the source-tree fallback. Tests drive git for real against throwaway repositories: a mocked subprocess would only prove the mock agrees with itself. pip and the validation subprocess are the two steps stubbed, so the pull/validate/ roll-back orchestration is exercised without touching the interpreter running the suite. * fix(updates): mention updates on the post-2FA screen, and stop caches pinning old assets Two gaps found by actually looking at the rendered pages. **The "You're signed in" screen said nothing.** The toast fetches its status once at page load, which on /app/login happens before the session exists -- so it 401'd and stayed empty, and signing in never re-checks because it does not reload the page. The one moment the operator is guaranteed to pass through said nothing about a pending update. login.js now re-asks once the session is created and renders a one-line mention above "Finish signing in". Deliberately not the full card: that screen has a single primary action, and on a narrow viewport a bottom-anchored card this tall would sit on top of it. The card now opts out of the auth pages entirely and shows on the landing page instead. **Browsers could keep running the previous release's JavaScript.** Starlette serves static files with ETag/Last-Modified but no Cache-Control, which leaves browsers on heuristic freshness -- a file untouched for weeks is reused for a long time without ever revalidating. That was survivable when upgrading meant running commands by hand; it is not once the server can update itself and the next page load is expected to match the new backend. This was not theoretical: it bit the browser used to verify the change, which kept executing a stale bundle across several restarts. Asset URLs now carry a fingerprint of the bundle, recomputed at start from the newest mtime in the static directory (which a git pull bumps). New bytes mean a new URL, so no cache can serve it from an old entry -- which also lets the files be cached hard instead of revalidated: ?v= present -> public, max-age=31536000, immutable ?v= absent -> no-cache (a legacy or hand-typed URL can't pin old code) /app/* pages -> no-store (per-session, and they carry the fingerprint) * fix(updates): serialize update work, and keep the restart off the shell Self-review findings on the update flow. **Two updates could run at once.** The dashboard button and the MCP tool reach `apply_update` independently, so nothing stopped a second one starting mid-pull: two `git pull` / `pip install -e .` runs in one checkout fight over index.lock and can leave a half-applied tree, and one caller's rollback could discard the other's successful update. A second caller is now told an update is already running rather than queued behind a pip that may take minutes -- it never touches git. **Cold-cache checks stampeded.** Every dashboard tab opening at once fired its own `git fetch`, piling up 60 s subprocesses for one answer. The uncached path is now single-flighted; waiters get the result the winner cached. **The deferred restart built a shell string.** `service` is the literal "beaconmcp" today, so this was not exploitable, but interpolating it into `sh -c` means a future change that made the unit name configurable would silently become a shell injection. Values now go through argv. All three are covered by tests, and both locks were mutation-checked: removing either makes its test fail (4 concurrent checks instead of 1; "release unlocked lock" when the second updater proceeds). * feat(dashboard): render MCP Apps panels in the integrated chat Closes #35. The ui:// panels from #34 only rendered in external hosts; /app/chat showed the tool's JSON. The dashboard is both the MCP client and the host, so both halves were missing. Not blocked by mcp 2.0 after all. A client declares Apps support through ClientCapabilities.extensions, which 1.x has no attribute for -- but the model is declared extra="allow", so the field serialises under the name the spec gives it and the server reads the same JSON either way. The pin costs the typed attribute, not the capability. Client half (dashboard/mcp_bridge.py, chat.py): an AppsClientSession that tags the outgoing InitializeRequest rather than reimplementing initialize(), the tool -> ui:// map read off each tool's _meta, and the full CallToolResult carried on ToolCallEnd -- the panel needs the whole payload, not the 500-char preview the tool card shows. Host half (chat.js, two routes): the document is served by /app/api/mcp/panel under its own CSP and framed with sandbox="allow- scripts" and no allow-same-origin. Verified in a browser: the frame gets a SecurityError on document.cookie and on window.parent, and CSP blocks fetch. Its only way out is postMessage, which is what makes the parent page the place where policy is decided. chat.js answers ui/initialize, pushes tool-input/tool-result, relays tools/call through /app/api/mcp/call, routes ui/message into a real turn and ui/update-model-context into the next one, and honours size-changed and request-display-mode. The confirmation question #35 raised, decided: a panel button is a human click on a labelled control, so it is not gated -- but "calls from an iframe skip the gate" is not the rule. A ui:// document is HTML the server wrote and this dashboard is a general MCP host, so the exemption is a closed list enforced in panel_call_allowed(): start/stop/restart on one guest, and proxmox_vm_config only for sizing keys (exempting the tool would exempt hookscript, raw QEMU args and device passthrough with it). Everything else is refused rather than prompted, because there is no turn in flight to hang a modal on -- and a panel that needs more sends ui/message, which puts the request back under the modal. Only the ui:// URI is persisted with a tool call, never the snapshot: a panel reopened from history refetches rather than showing week-old figures in a live-looking frame. Also fixes the panels' theming against a real host: panel.css now reads the spec's standardized variable names with its own values as fallbacks, so hostContext.styles.variables actually lands. 505 passed (+44), ruff clean. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * feat(dashboard): move the model picker to Gemini 3.6 Flash / 3.5 Flash-Lite / 3.1 Pro Gemini 3.6 Flash went GA on 2026-07-21 and supersedes gemini-3-flash-preview; 3.5 Flash-Lite is the Flash-Lite that shipped alongside it, and lands at the price 2.5 Flash used to hold. Gemini 2.5 Flash / Pro and gemini-3-flash-preview leave the picker; 3.1 Pro stays as the preview option. There is no gemini-3.6-flash-lite -- the Lite in that launch is 3.5. Rates (AI Studio, 2026-07-29): 3.6 Flash $1.50/$0.15/$7.50, 3.5 Flash-Lite $0.30/$0.03/$2.50. 3.1 Pro is unchanged. The retired models keep their entries in _PRICING: cost_usd re-prices stored turns, so dropping a rate would silently re-bill that history at the fallback model's price. Schema 6 moves conversations off the retired ids -- conversations.model is what the *next* turn runs on, and a retired id there would fail VALID_MODELS and silently fall back, reading as the picker forgetting the operator's choice. messages.model is deliberately left alone: it records which model actually wrote a reply, which is history rather than configuration. That is the difference from migration 2, which renamed the same model. Also fixes an unrelated fragility in test_fingerprint_changes_when_an_asset_changes: it bumped app.css past its own mtime, but the fingerprint is the directory maximum, so the assertion failed whenever another static file happened to be newer. 510 passed, ruff clean. Picker verified in the browser: chip reads "3.6 Flash", groups Flash / Pro, 3.1 Pro carries the Preview badge. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(dashboard): gate beaconmcp_self_update behind the confirmation modal Found reviewing this branch before merge. Enumerating the 49 registered tools against _NEEDS_CONFIRMATION turned up beaconmcp_self_update sitting outside it: with confirm=True it runs git pull, reinstalls dependencies and restarts the service, so one injected instruction in a log line could replace the process that enforces the gate. It landed ungated with the self-update tools in #37; the panel relay added here would have inherited the hole. _CONFIRM_WHEN_ARG_PRESENT rather than _NEEDS_CONFIRMATION: confirm=False only previews. Reading the argument is sound here because `confirm` is a parameter the tool declares -- the trap the dry_run note describes is an argument the tool does *not* declare, which pydantic drops during validation. Adds a test that walks every @mcp.tool in src/ and fails on any name that is neither gated nor on an explicit reviewed-as-safe list, so the next tool cannot land outside the gate unnoticed. Also restores the composer text when submit() fails before the message is rendered -- moving the clear ahead of sendUserText() for ui/message made a failed conversation-create eat what the operator typed. 513 passed, ruff clean. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Updates the requirements on [mcp](https://github.com/modelcontextprotocol/python-sdk) to permit the latest version. - [Release notes](https://github.com/modelcontextprotocol/python-sdk/releases) - [Changelog](https://github.com/modelcontextprotocol/python-sdk/blob/main/RELEASE.md) - [Commits](modelcontextprotocol/python-sdk@v1.0.0...v2.0.0) --- updated-dependencies: - dependency-name: mcp dependency-version: 2.0.0 dependency-type: direct:production ... Signed-off-by: dependabot[bot] <support@github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Updates the requirements on mcp to permit the latest version.
Release notes
Sourced from mcp's releases.
... (truncated)
Commits
6f69a37Present v2 as the stable release across the README, docs, and policies (#3178)78e6fbbServe v2 docs at the site root, with permanent per-major paths (#3176)af06330Remove unused StreamableHTTPTransport.get_session_id() (#3205)68ca87eDocument the two-line release process for stable v2 (#3179)c9c431bExpose the middleware chain on MCPServer and stop sending unrequested change ...528e366Fail fast on server-to-client requests in JSON-response mode instead of hangi...27f5cc7Remove unused mcpserver.exceptions.ValidationError (#3199)89c5e70Gate log notifications on the per-request log-level opt-in at 2026-07-28 (#3198)b61ce38docs: fix off-by-one hl_lines in apps.md (#3196)b7c9a91Add mcp.types as a permanent alias for mcp_types (#3190)Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting
@dependabot rebase.Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR:
@dependabot rebasewill rebase this PR@dependabot recreatewill recreate this PR, overwriting any edits that have been made to it@dependabot show <dependency name> ignore conditionswill show all of the ignore conditions of the specified dependency@dependabot ignore this major versionwill close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)@dependabot ignore this minor versionwill close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)@dependabot ignore this dependencywill close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)