Skip to content

fix(install): adopt matching MCP entries when regenerating the lockfile from scratch - #3091

Open
Sebastian Alpers (salpers) wants to merge 2 commits into
microsoft:mainfrom
salpers:fix/fresh-lock-mcp-adoption
Open

Sebastian Alpers (salpers) wants to merge 2 commits into
microsoft:mainfrom
salpers:fix/fresh-lock-mcp-adoption

Conversation

@salpers

Copy link
Copy Markdown
Contributor

Description

Regenerating apm.lock.yaml from scratch, over native MCP configs that are already committed, loses MCP ownership. The self-defined server is logged as (already configured), no kind: uri / target: mcp deployments are recorded, and the lock gets mcp_target_servers: {}, which blocks adoption on every later run. Full analysis and repro are in #3090.

Three small changes:

  • commands/install.py: default old_mcp_target_servers_present to False when there is no lockfile. Previously it stayed True, so resolve_mcp_target_servers returned early and adoption never ran.
  • install/mcp/integration.py: when the old lock has no mcp_configs, use the current resolved configs as the adoption baseline. Adoption stays exact-match only, so user-authored entries that differ are never claimed.
  • adapters/client/claude.py: render_server_config returns the shape normalised by _normalize_mcp_entry_for_claude_code (type: stdio, no tools/id), which is what the adapter actually writes to .mcp.json. Without this, exact-match adoption can never match a Claude Code entry. That also affects the existing legacy-lockfile adoption path (Adopt legacy lockfiles predating mcp_target_servers on upgrade #2158).

Issue and approved scope

Issue: #3090

Human scope-approval comment: pending. Opened alongside the issue; happy to adjust or hold for scope approval.

Does this PR complete the issue, or what remains? Completes it.

Type of change

  • Bug fix
  • New feature
  • Documentation
  • Maintenance / refactor

Testing

  • Tested locally
  • All existing tests pass
  • Added tests for new functionality (if applicable)

On a real repo (four targets, one self-defined stdio server declared in a local sub-package, all four native configs committed):

before after
rm apm.lock.yaml && apm install --force 4 MCP deployment records deleted, mcp_target_servers: {} lock identical to the committed one apart from generated_at / apm_version
second apm install --force no change (records never return) no change
apm audit --ci — all 10 checks pass

I haven't run the full test suite locally; CI will. If you want the change in, I'll add unit coverage for the no-lockfile adoption path and the Claude render shape.

Spec conformance (OpenAPM v0.1)

  • N/A. This PR does not change OpenAPM-observable behaviour.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot review overview

🟡 Changes recommended

Update-path handling and fallback ownership issues remain, with related test and documentation gaps.

Review effort: Lite
Findings: 1 Medium severity · 2 Low severity

Open (3)
What changed in this PR

Fixes MCP ownership loss when regenerating lockfiles, including Claude Code adoption.

Changes:

  • Enables adoption without a prior lockfile.
  • Uses current MCP configs as the fallback baseline.
  • Normalizes Claude Code entries for exact matching.
  • Documents the fix in the changelog.
File Summary
src/​apm_cli/​install/​mcp/​integration.py Adds fallback MCP adoption handling.
src/​apm_cli/​commands/​install.py Corrects ownership state for missing lockfiles.
src/​apm_cli/​adapters/​client/​claude.py Matches Claude’s on-disk configuration shape.
CHANGELOG.md Documents the MCP ownership fix.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread src/apm_cli/install/mcp/integration.py Outdated
Comment thread src/apm_cli/adapters/client/claude.py
Comment thread src/apm_cli/install/mcp/integration.py
@salpers
Sebastian Alpers (salpers) force-pushed the fix/fresh-lock-mcp-adoption branch 3 times, most recently from 1b80d04 to fbf7476 Compare September 30, 2026 18:56
…le from scratch

Without a lockfile, install treated MCP ownership as already recorded and
skipped adoption, so self-defined servers whose native configs were already
committed were logged as already configured and recorded for no runtime.
The regenerated lock then wrote mcp_target_servers: {}, which blocked
adoption on every later run.

Default ownership to absent when there is no lockfile, fall back to the
current resolved configs as the exact-match adoption baseline, and have the
Claude adapter render its normalised on-disk shape so .mcp.json entries can
be adopted at all.

Refs microsoft#3090
Review follow-ups on the fresh-lockfile adoption path.

A lock that lists mcp_servers but carries no mcp_configs fell back to
the current configs for baselines yet kept only the old names as
candidates, so a current self-defined server present natively was never
adopted. Candidates are now the union of both.

Also cover the two contracts the fix rests on:

* Claude's render_server_config equals the entry configure_mcp_server
  writes to .mcp.json, for stdio and remote shapes, so exact-match
  adoption cannot silently break on an adapter change.
* A lockfile-wipe install (no ownership recorded) restores per-runtime
  mcp_target_servers for an exact native match and leaves a user-edited
  entry unowned.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The apm install --mcp path has the same trap. install/mcp/command.py still sets target_servers_present to True when there is no lockfile and only calls resolve_mcp_target_servers when one exists. On this branch, with acme-local committed in .mcp.json and .github/mcp.json: rm apm.lock.yaml && apm install --mcp acme-local -- node srv.js writes mcp_target_servers: {}, and a following apm install --force keeps it empty. Plain apm install --force after the same wipe restores both owners.

This branch has not been deployed

No deployments
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.

3 participants