Skip to content

fix(mcp): report the real import failure, not a missing extra - #36

Merged
CameronBrooks11 merged 2 commits into
mainfrom
fix/mcp-import-failure-cause
Sep 10, 2026
Merged

fix(mcp): report the real import failure, not a missing extra#36
CameronBrooks11 merged 2 commits into
mainfrom
fix/mcp-import-failure-cause

Conversation

@CameronBrooks11

@CameronBrooks11 CameronBrooks11 commented Sep 10, 2026

Copy link
Copy Markdown
Member

netspec-mcp caught ImportError and printed netspec-mcp needs the mcp extra: pip install 'kicad-netspec[mcp]' whatever the cause. ModuleNotFoundError is a subclass of ImportError, so under mcp 2.x the message was wrong in the one way that matters: the extra is installed, and the user was told to install it.

This is piece 1 of the two in #35. Piece 2 — the FastMCPMCPServer migration, and whether the rest of the API netspec uses moved with it — is untouched here, and so is the mcp>=1.27,<2 pin. #33 widens that pin to <3; this change does not depend on that decision and does not pre-empt it. It does change what #33 would look like if merged: instead of an entry point that blames a missing extra, a user on 2.x gets upstream's migration message, so the pin question is visible rather than disguised.

The three cases

Measured in three fresh venvs, kicad-netspec installed from this branch.

mcp 2.1.1 installed. Before:

$ netspec-mcp
netspec-mcp needs the mcp extra: pip install 'kicad-netspec[mcp]'
$ echo $?
4

After:

$ netspec-mcp
netspec-mcp could not import the MCP server API. The mcp package itself is installed, so the extra is not absent; netspec does not know more than that. The import said:

    ModuleNotFoundError: No module named 'mcp.server.fastmcp'. This is mcp 2.x, where FastMCP was renamed to MCPServer (from mcp.server.mcpserver import MCPServer) and other APIs changed; see the migration guide at https://py.sdk.modelcontextprotocol.io/v2/migration/#fastmcp-renamed-to-mcpserver or pin 'mcp<2' to keep running v1 code.
$ echo $?
4

mcp absent — unchanged, before and after:

$ netspec-mcp
netspec-mcp needs the mcp extra: pip install 'kicad-netspec[mcp]'
$ echo $?
4

Incomplete installmcp 1.30.0 with pydantic-settings uninstalled. After:

$ netspec-mcp
netspec-mcp could not import the MCP server API. The mcp package itself is installed, so the extra is not absent; netspec does not know more than that. The import said:

    ModuleNotFoundError: No module named 'pydantic_settings'
$ echo $?
4

The message says only what the lookup established. An earlier revision of this branch said "this is not a missing dependency: the mcp package is installed", which denied a missing dependency in the same breath as printing one — a categorical claim about the whole dependency tree from resolving one name in it. It was also the wrong steer: pip install 'kicad-netspec[mcp]' re-resolves the missing transitive dependency and repairs that install (measured), so the denial argued against the one thing that works.

The change

_diagnose_import_failure(exc, *, mcp_installed) decides between the two messages. main() supplies the observation as importlib.util.find_spec("mcp") is not None.

find_spec is a design preference, not a necessity: the exception's own name attribute would also separate the reachable cases, and both discriminators agree on all three. Measured:

case exc.name find_spec("mcp") is None
mcp 2.1.1 installed 'mcp.server.fastmcp' False
mcp absent 'mcp' True
mcp 1.30.0, pydantic-settings removed 'pydantic_settings' False

find_spec is preferred because "is the package there" is the question being asked, and asking it directly does not depend on an error attribute keeping its shape across upstream versions. It is a better way to learn the same thing, not the only way.

Exit stays 4 in every case. Exit 4 was already the right outcome — netspec could not run, which says nothing about a design (D10). The reason was the fabricated part.

Tests

tests/test_mcp_import_failure.py, deliberately not gated on the mcp extra — three of its four tests construct their own exceptions and need no mcp, and a module-level gate would take them with it on a contributor machine without the extra:

  • an absent package still says to install the extra
  • a moved symbol does not mention the extra, and carries upstream's text through verbatim
  • an incomplete install is not told that nothing is missing
  • through main(), against the real find_spec: exit 4, upstream's text on stderr, no kicad-netspec[mcp] in it — the one test that reads the environment, and the only one that skips without the extra

tests/test_mcp.py is unchanged; its gate is still right for the MCP surface it covers, and still fails loudly in CI.

No decision in docs/DECISIONS.md moves. D7's pin, D10's exit 4, and D18's tool surface are all as they were.

Closes #35.

`netspec-mcp` caught `ImportError` and printed "needs the mcp extra"
whatever the cause. `ModuleNotFoundError` is a subclass, so under mcp
2.x -- where `mcp/server/fastmcp.py` is a stub that raises, naming the
rename to `MCPServer`, the new import path, the migration guide and the
`mcp<2` escape hatch -- all of that was discarded and the user was told
to install what they already had.

Blame the extra only when the `mcp` package is genuinely absent; when it
is present, pass the import's own words through. Exit stays 4 either
way: netspec could not run, which says nothing about a design (D10). It
was the reason that was fabricated, not the outcome.

Closes #35.
@github-actions

Copy link
Copy Markdown

Connectivity unchanged

tests/fixtures/good_ldo.kicad_sch

design sha256:36f119b0091afd2449fcd8cc92856510f5eda0d95156ad096fc1272a39cff90e

Connectivity unchanged.

tests/fixtures/polcap_rail_correct.kicad_sch

design sha256:60a7ddcc8a14c5b7c1269e499974c219ce6e3e3b53f22c734099aeb749c4b4b1

Connectivity unchanged.

Reported by netspec — KiCad's own netlist, not a guess.

The message added for #35 said "this is not a missing dependency: the
mcp package is installed". Only the second half was established. On an
incomplete install -- mcp present, a transitive dependency of it gone --
the message denied a missing dependency and then printed one:

    ...this is not a missing dependency: the mcp package is installed.
    The import said:

        ModuleNotFoundError: No module named 'pydantic_settings'

That is the substitution the change exists to remove, and it also steers
the user away from the fix, since reinstalling the extra re-resolves the
missing dependency and repairs the install.

Narrow the sentence to the lookup that was actually done. Pin the case
in a test, and move the helper's tests to a module that is not gated on
the extra, so the three that need no `mcp` run without it.
@CameronBrooks11
CameronBrooks11 merged commit e041686 into main Sep 10, 2026
9 checks passed
@CameronBrooks11
CameronBrooks11 deleted the fix/mcp-import-failure-cause branch September 10, 2026 02:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Under mcp 2.x, netspec discards the migration message and blames a missing extra

1 participant