fix(deps): drop fastmcp for the official mcp SDK; pin urllib3 for CVE fixes#324
Merged
Conversation
…44432 `pip-audit` against the installed dep graph reports two open urllib3 CVEs in 2.6.3: - CVE-2026-44431 (GHSA-qccp-gfcp-xxvc): when following cross-origin redirects via urllib3's high-level APIs, the redirect handling could leak credentials or headers across origins. - CVE-2026-44432 (GHSA-mf9v-mfxr-j63j): the streaming API mishandles some I/O states. Both are fixed in urllib3 2.7.0. urllib3 reaches NBI transitively through `requests` (tiktoken's dep), and neither `requests` nor tiktoken caps the urllib3 upper bound, so a >=2.7.0 lower bound here is enough to pull the fix on fresh installs. Documents the remaining open litellm CVE (CVE-2026-40217, fix in 1.83.10) inline next to the existing pin. 1.83.10 has not yet been released to PyPI; the vulnerable endpoint is in the litellm proxy server which NBI does not run, so the gap is latent-not-exposed. Re-bump the lower bound when 1.83.10 ships. JS-side `yarn npm audit` reports 21 advisories, all transitive through `@jupyterlab/*`, `webpack`, `eslint`, and `jest`. None are NBI's direct deps; pinning them via yarn `resolutions` would risk breaking JupyterLab compatibility. Tracked as a follow-up for the maintainer to decide on a per-advisory basis after the upstream JupyterLab packages release fixed versions. Verified: `pip-audit` after `pip install 'urllib3>=2.7.0' --upgrade` shows the two CVEs cleared. Full test suite green (1007 passed).
NBI's pyproject was un-installable on a fresh Python 3.14 because `litellm>=1.83.7` (the floor that picks up CVE-2026-42203 / CVE-2026-42208 / CVE-2026-42271 fixes) hard-pins `python-dotenv==1.0.1`, and every `fastmcp>=2.11.2` release requires `python-dotenv>=1.1.0`. Mutually unsatisfiable; pip reports `ResolutionImpossible`. Upstream litellm has closed a CVE-fix bump (BerriAI/litellm#26435) and stalled on a pin-relaxation PR (BerriAI/litellm#25231) for ~7 weeks with no signal of movement. This commit drops the `fastmcp` dep entirely and routes `mcp_manager.py` through a thin local shim (`notebook_intelligence/mcp_client.py`) implemented against the official `mcp` Python SDK. The SDK lists `python-dotenv` only as an optional `cli` extra, so it can coexist with litellm's hard `==1.0.1` and the resolver succeeds. `mcp` was already in NBI's dep tree (transitively via `claude-agent-sdk`); promoted to a direct `>=1.27.0` pin so the version is anchored. The shim exposes the small fastmcp surface NBI used (`Client`, `StdioTransport`, `StreamableHttpTransport`) with constructor signatures and method shapes identical to fastmcp's. Return values are the underlying mcp Pydantic models — the same shapes fastmcp returned, since fastmcp itself is a thin layer over the SDK. mcp_manager.py's two imports change; nothing else in the consumer moves. Tests (`tests/test_mcp_client_shim.py`, 5 cases): - Constructor field-name parity for both transports. - Methods-before-context-entry surface as RuntimeError. - Unsupported transport types surface as TypeError. - End-to-end against a real stdio MCP server: ping, list_tools, call_tool, list_prompts, get_prompt. Asserts the exact attribute paths mcp_manager.py reads (tool.name / tool.inputSchema / prompt.arguments[i].required / get_prompt_result.messages[i]. content.text). A future SDK refactor that returns an envelope where a list was expected (or vice versa) flunks the test rather than silently breaking the consumer. - Context cleanup on initialize failure: the stdio subprocess unwinds cleanly when the server exits before the handshake. Verification: fresh `python -m venv && pip install -e .` on Python 3.14 succeeds (previously failed with ResolutionImpossible). Full pytest suite: 1012 passed. TS gates clean. The shim's end-to-end test runs the real stdio handshake in <1s. Temporary in spirit: once BerriAI/litellm#25231 lands and the fastmcp clients NBI used become installable alongside it again, this shim can revert to a direct `fastmcp` import and the local file can be deleted. Both the new module's docstring and the pyproject comment point at that upstream PR.
Three fix-before-merge items from the parallel review pass.
1. Test-arch flagged that no async test in the repo has a timeout.
``mcp_client.py``'s stdio-subprocess tests rely on the underlying
``mcp`` SDK's pipes; if a pipe ever deadlocks, the test would
hang forever and CI would kill the whole job rather than
surfacing the specific test. Added ``pytest-timeout`` to the test
extras and a 30s default via ``[tool.pytest.ini_options].timeout``
so any hang surfaces as a per-test failure with a stack trace.
2. Test-arch and async-resource both flagged that
``test_context_cleanup_on_initialize_failure`` only asserted
``pytest.raises(Exception)``, which would still pass if the shim
silently leaked the stdio subprocess. The whole point of that
test is the cleanup path. Now uses ``psutil`` to enumerate the
pytest process's children, filters to those running the broken
server script, and asserts the list is empty before and after the
failing connect. A 2-second poll budget covers OS-level SIGCHLD
lag; in practice the assertion resolves under 50ms locally. If
the shim regresses to leaking subprocesses, the test now fails
loudly with the leaked PIDs.
3. Code-reuse and code-quality both flagged the ``_current_exc()``
helper as one-line indirection over ``sys.exc_info()`` with one
caller. Inlined and lifted the ``sys`` import to module top.
Moved the explanation (why we forward exc_info to the exit-stack
cleanup) to the comment at the call site.
Deferred follow-up items the reviewers flagged, none security- or
correctness-relevant:
- Drop ``StdioTransport`` dataclass and let ``Client`` accept
``StdioServerParameters`` directly. Code-reuse win at the eventual
revert, not worth churn while the shim is temporary.
- Move ``_SERVER_SCRIPT`` to ``tests/fixtures/mcp_echo_server.py``
for editor-side syntax highlighting and import-time validation.
- Parametrize ``_require_session`` coverage across all five methods
(currently only ``list_tools`` is tested for the not-connected
guard).
- HTTP transport (``streamablehttp_client``) is exercised by the
constructor test but not end-to-end. Would need a mock ASGI
server to add cleanly; NBI's own usage is stdio-dominant.
- Document the env=={} -> None collapse at mcp_client.py for
callers wanting clean-env semantics.
- Reference counting on the ``Client`` if reentrant context use
ever becomes a live code path (today it's dead in mcp_manager).
mbektas
approved these changes
May 19, 2026
pjdoland
added a commit
to pjdoland/notebook-intelligence
that referenced
this pull request
May 22, 2026
Promotes the [Unreleased] CHANGELOG snapshot to [5.0.0] - 2026-05-22 and expands it to cover everything merged into upstream/main after PR plmbr#287's docs refresh. Bumps package.json to 5.0.0. CHANGELOG additions cover the post-plmbr#287 surface: - Settings tabs: plugin marketplace picker (plmbr#284), plugin marketplace details + Update button (plmbr#303), per-workspace MCP disable (plmbr#286), JSON-paste path in Add MCP server (plmbr#285). - Launchers: hide-with-policy (plmbr#288), brand icons for Codex / opencode (plmbr#325, plmbr#333), per-launch directory picker (plmbr#332). - Chat sidebar and agentic UX: workspace @-mention in Claude mode (plmbr#327), reload-open-files-on-disk (plmbr#330), steered system prompt away from over-eager notebook creation (plmbr#336). - Skills: multi-manifest support (plmbr#321), tracks-upstream for user- imported skills (plmbr#322), HTTP kill switch for the reconciler (plmbr#291). - Accessibility: full sub-section covering plmbr#305-plmbr#320. - Security: shell-tool sandbox (plmbr#290), Claude UI-bridge sandbox (plmbr#323), 0o600 on encrypted token (plmbr#293), env-secret scrubbing (plmbr#295), MCP config shape validation (plmbr#299), XSS allowlist (plmbr#296), Copilot WS auth + origin (plmbr#301), GHE host detection (plmbr#292), fastmcp -> mcp SDK swap (plmbr#324). - Fixed: session listing unification (plmbr#310), session preview unwrap (plmbr#331), down-area runtime throw (plmbr#330 follow-up), WS message-handler leak (plmbr#294). - Removed: fastmcp dependency, history.jsonl session gate. Adds a Migration note covering the five behavior changes operators should review before upgrading from 4.x: fastmcp swap, path sandboxes, history.jsonl gate removal, workspace @-mention pointer shape, and the Copilot WebSocket auth/origin tightening. Two reviewer rounds (six personas each) applied: - Round 1 caught security overclaims (plmbr#293, plmbr#299, plmbr#323), the plmbr#284/plmbr#303 mis-attribution, missing migration note, 3 em dashes, and the stale `fastmcp==2.x.*` recommendation in the admin guide. - Round 2 caught the missing plmbr#301 migration bullet, missing version- matrix 5.0.x row, missing README TOC entry, and a couple of style nits (sub-heading overpromise, orphan bullet). Skipped (deferred to future PRs): - README first-run tour mention. - Admin guide HTTP kill-switch row in Failure-modes table. - Terminal drag-drop trust-model precision update after plmbr#327. - Cipher description nit in plmbr#293 (Fernet AES-128-CBC+HMAC, not AES-GCM).
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.
Summary
Two related fixes:
NBI's
pyproject.tomlonmainis currently un-installable on a fresh Python 3.14 environment.litellm>=1.83.7(the floor that picks up CVE-2026-42203 / CVE-2026-42208 / CVE-2026-42271 fixes) hard-pinspython-dotenv==1.0.1, and everyfastmcp>=2.11.2release requirespython-dotenv>=1.1.0. The constraints are mutually unsatisfiable;pip install -e .reportsResolutionImpossible. Verified in a clean venv.This PR drops the
fastmcpdep entirely and routesmcp_manager.pythrough a thin local shim implemented against the officialmcpPython SDK, which listspython-dotenvonly as an optionalcliextra and is already in NBI's dep tree viaclaude-agent-sdk. Fresh installs succeed.urllib32.6.3 has two open CVEs. Pinnedurllib3>=2.7.0to pick up CVE-2026-44431 (cross-origin redirect handling) and CVE-2026-44432 (streaming API). Reaches NBI viarequests, which doesn't cap the upper bound, so a single lower bound in NBI's pyproject is enough.Solution
fastmcp → mcp swap (
notebook_intelligence/mcp_client.py, new file):Client,StdioTransport,StreamableHttpTransport) implemented on top of the officialmcpSDK. Constructor signatures and method shapes match fastmcp's so the call sites inmcp_manager.pychange in two lines (the two imports).mcp.pyproject.toml:fastmcp>=2.11.2→mcp>=1.27.0. The python-dotenv resolution now lands on 1.0.1 (forced by litellm; CVE-2026-28684 affects onlyset_key/unset_keywrite paths which neither NBI nor any other dep exercises).urllib3 pin (
pyproject.toml):"urllib3>=2.7.0". Comment explains the transitive path throughrequestsand which CVEs the bump addresses.Testing
tests/test_mcp_client_shim.py(5 new cases): constructor parity, programmer-error guards (methods-before-context, unsupported transport), and an end-to-end smoke test against a real stdio MCP server (spawns a subprocess, drivesping/list_tools/call_tool/list_prompts/get_prompt, asserts the exact attribute pathsmcp_manager.pyreads off the responses). Plus a cleanup-on-failure case that pins the stdio subprocess unwinds cleanly when initialization fails.python -m venv /tmp/fresh && /tmp/fresh/bin/pip install -e .on Python 3.14 now succeeds. Previously:ResolutionImpossible.Why this is the right temporary fix
The principled fix lives upstream in BerriAI/litellm#25231 (relax the
python-dotenvpin from==1.0.1to>=1.0.1). That PR has been open since 2026-04-06 with no maintainer engagement, and a parallel CVE-fix bump (#26435) was closed without merge. NBI can't ship a working install while waiting on upstream.The shim is small (~120 lines), tested end-to-end, and the docstring + pyproject comment both point at the upstream PR to revert to once it lands. The official
mcpSDK is a stable Anthropic-maintained library, not a vendored fork — there's no maintenance burden beyond keeping the shim's method signatures aligned withmcp's public API.Tracked upstream:
Risks
mcp_manager.pycalls. JupyterLab's MCP integration paths haven't been touched.mcp_manager.pychange shape; the consumer behavior is byte-for-byte identical.