fix(mcp): honour per-dep registry URL during install (#1393)#1443
fix(mcp): honour per-dep registry URL during install (#1393)#1443sergio-sisternes-epam wants to merge 1 commit into
Conversation
There was a problem hiding this comment.
Pull request overview
This PR fixes MCP installation so that a dependency’s per-entry registry: URL in apm.yml is actually used during install-time server resolution, instead of being stored but ignored.
Changes:
- Group MCP registry-resolved dependencies by
dep.registryand instantiateMCPServerOperations(registry_url=...)per unique registry URL. - Extract
_install_registry_group()to encapsulate the validate -> fetch -> overlay -> install loop for one registry group. - Remove the “registry not yet applied” overlay warning and update/add unit tests to validate the new routing behavior.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
| tests/unit/integration/test_mcp_per_dep_registry.py | Adds coverage for per-dependency registry grouping and routing behavior (but currently includes non-ASCII characters in docstrings/comments). |
| tests/unit/integration/test_mcp_integrator.py | Updates overlay warning expectations so registry: <url> no longer emits a warning. |
| src/apm_cli/integration/mcp_integrator.py | Removes the warning previously emitted for dep.registry overlay strings. |
| src/apm_cli/integration/mcp_integrator_install.py | Implements per-dependency registry URL routing by grouping deps and running installs per registry group. |
e67246b to
776ddc9
Compare
APM Review Panel:
|
| Persona | B | R | N | Takeaway |
|---|---|---|---|---|
| Python Architect | 0 | 1 | 2 | Clean helper extraction with correct grouping logic; 13-param signature is tolerable at this scope but operations: Any loses type safety unnecessarily. |
| CLI Logging Expert | 0 | 2 | 1 | Heartbeat repeats per-group without naming the registry; users with 2+ registries see duplicated lines with no way to tell them apart. |
| DevX UX Expert | 0 | 1 | 2 | Per-dep registry install promise delivered; error path leaks misleading guidance when URL is invalid; search hint points at wrong registry. |
| Supply Chain Security Expert | 0 | 1 | 1 | Per-dep registry URL passes through existing scheme validation (https-only default); no token leakage. Recommend adding allowed_registries policy gate as hardening follow-up. |
| OSS Growth Hacker | 0 | 2 | 1 | Strong community-contributor signal; per-dep registry routing unlocks a compelling enterprise story angle worth a release beat and a docs example. |
| Auth Expert | 0 | 0 | 1 | Per-dep registry routing is auth-neutral: SimpleRegistryClient uses bare requests.Session with no Authorization headers, so no token leakage on arbitrary URLs. |
| Doc Writer | 0 | 2 | 1 | No stale "ignored" claim in user docs (field was already documented), but CHANGELOG entry and an apm-guide example are missing for a now-honoured field. |
| Test Coverage Expert | 0 | 1 | 2 | Unit regression-trap for per-dep registry routing exists but no integration-with-fixtures test proves the end-to-end install path honours registry: URL. |
B = blocking-severity findings, R = recommended, N = nits.
Counts are signal strength, not gates. The maintainer ships.
Top 6 follow-ups
- [Doc Writer] Add CHANGELOG [Unreleased] Fixed entry framing per-dep registry routing as enterprise capability unlock (closes [BUG] apm install ignores dependencies.mcp[].registry #1393). -- Three panelists converge (devx-ux, oss-growth, doc-writer). Silent release-comms drift otherwise; evaluators scan CHANGELOG for enterprise signals.
- [Doc Writer] Add per-dep
registry: <url>example toapm-guide/dependencies.mdandconsumer/install-mcp-servers.md. -- Feature is invisible without docs example. Two panelists converge (oss-growth, doc-writer). Agent-facing apm-guide is authoritative for LLM consumers. - [DevX UX Expert] Wrap
MCPServerOperations(registry_url=...)intry/except ValueErrorwith actionable error pointing at theapm.ymldep, notMCP_REGISTRY_URLenv var. -- Invalid per-dep URL currently surfaces misleading guidance or raw traceback. One try/except delivers correct error ergonomics. - [CLI Logging Expert] Disambiguate heartbeat and success lines per registry group (verbose-mode registry annotation). -- Users with 2+ registries see duplicated output with no way to tell groups apart. Progressive-disclosure fix: verbose annotation only.
- [Test Coverage Expert] Add integration-tier test proving
apm installroutes deps to per-dep registry URL (mock only HTTP layer). -- Unit regression trap exists but integration tier is missing on a portability-by-manifest surface. Elevates above typical nit per evidence weighting. - [Supply Chain Security Expert] Add optional
allowed_registriespolicy field toMcpPolicyfor enterprise registry lockdown. -- No urgency (apm.yml trust boundary already allowscommand:arbitrary code) but completes the governed-by-policy story for enterprise adopters.
Architecture
classDiagram
direction LR
class MCPIntegrator {
<<BaseIntegrator>>
+install(mcp_deps, ...)
+_apply_overlay(cache, dep)
+_detect_mcp_config_drift(deps, configs)
+_install_for_runtime(rt, deps, ...)
}
class run_mcp_install {
<<ModuleFunction>>
+run_mcp_install(mcp_deps, ...) int
}
class _install_registry_group {
<<ExtractedHelper>>
+_install_registry_group(operations, ...) int
}
class MCPServerOperations {
<<Service>>
+__init__(registry_url)
+validate_servers_exist(names)
+batch_fetch_server_info(servers)
}
class SimpleRegistryClient {
<<Adapter>>
+__init__(registry_url)
}
class MCPDependency {
<<ValueObject>>
+name str
+registry str or None or False
+is_self_defined bool
}
run_mcp_install ..> _install_registry_group : delegates per group
run_mcp_install ..> MCPServerOperations : constructs per registry URL
_install_registry_group ..> MCPServerOperations : calls validate/fetch/install
_install_registry_group ..> MCPIntegrator : calls static helpers
MCPServerOperations *-- SimpleRegistryClient : owns
run_mcp_install ..> MCPDependency : reads registry field
class _install_registry_group:::touched
class run_mcp_install:::touched
classDef touched fill:#fff3b0,stroke:#d47600
flowchart TD
A["apm install"] --> B["MCPIntegrator.install delegates"]
B --> C["run_mcp_install"]
C --> D{"Split: self_defined vs registry deps"}
D -->|self_defined| E["_install_self_defined_servers"]
D -->|registry| F["Group deps by dep.registry URL"]
F --> G{"For each group_registry_url"}
G --> H["MCPServerOperations(registry_url=group_url)"]
H --> I["_install_registry_group helper"]
I --> J["operations.validate_servers_exist"]
J --> K{"invalid?"}
K -->|yes| L["raise RuntimeError"]
K -->|no| M["check + drift detection"]
M --> N["batch_fetch_server_info"]
N --> O["apply_overlay per dep"]
O --> P["install_for_runtime per rt"]
P --> Q["configured_count += 1"]
G -->|next group| H
Recommendation
Merge after adding the CHANGELOG entry (highest-confidence panel consensus, can land in same PR). The docs example and ValueError handling are strong fast-follows worth filing as issues immediately post-merge. The contributor did clean work -- warm merge comment naming the enterprise unlock will reinforce the contributor funnel.
Full per-persona findings
Python Architect
- [recommended]
operations: Anydiscards static type info whenMCPServerOperationsis importable under TYPE_CHECKING atsrc/apm_cli/integration/mcp_integrator_install.py:37
Type hints on all public APIs;MCPServerOperationsis a concrete class. UsingAnyhides the contract from static analysis.
Suggested: Add to TYPE_CHECKING block:from apm_cli.registry.operations import MCPServerOperations; changeAnytoMCPServerOperations. Same forproject_root/console/logger. - [nit] 13-parameter helper could use a grouped context dataclass in a follow-up
Pragmatic follow-up: bundleproject_root/user_scope/verbose/console/logger/target_runtimes/stored_mcp_configsinto a frozenMCPInstallContextdataclass. - [nit]
group_dep_nameslist is redundant withgroup_dep_map.keys()for object deps atsrc/apm_cli/integration/mcp_integrator_install.py:250
Defensible for backward-compat with plain-string deps that lack.name, but worth a one-line comment.
CLI Logging Expert
- [recommended] Heartbeat emitted per-group without registry context -- confusing for multi-registry installs at
src/apm_cli/integration/mcp_integrator_install.py:66
mcp_lookup_heartbeatfires inside_install_registry_group; user with 2 registries sees two identical lines. Newspaper test fails. Verbose-mode annotation 'Looking up 3 MCP servers in https://...' would satisfy progressive disclosure.
Suggested: Passgroup_registry_urlinto helper; when not None, append to heartbeat /verbose_detail. - [recommended] Removed "registry not yet applied" warning replaced with silent acceptance -- missing positive info line for custom registry at
src/apm_cli/integration/mcp_integrator.py
When user explicitly configured a non-default registry URL, should see confirmation it took effect (helps debugging auth/network issues).verbose_detailsatisfies progressive disclosure.
Suggested: Before calling helper:if group_registry_url and verbose: logger.verbose_detail(f'Using custom registry: {group_registry_url}') - [nit] "All registry MCP servers already configured" can repeat per-group without disambiguation at
src/apm_cli/integration/mcp_integrator_install.py:111
Non-rich path emits the success line per group; with two groups up-to-date, user sees same line twice.
DevX UX Expert
- [recommended]
ValueErrorfrom invalid per-dep registry URL surfaces misleading "Check MCP_REGISTRY_URL" guidance or raw traceback atsrc/apm_cli/integration/mcp_integrator_install.py:254
SimpleRegistryClient.__init__raisesValueErrorwith "Check MCP_REGISTRY_URL if set" text, but for a per-depregistry:field the env var is irrelevant.try/exceptonly catchesImportError, soValueErrorpropagates unhandled.
Suggested: WrapMCPServerOperations(registry_url=group_registry_url)intry/except ValueErrorthat emits actionable error pointing atapm.ymldep. - [nit] Recovery hint "apm mcp search " after per-dep registry failure points at default registry, not the failing one at
src/apm_cli/integration/mcp_integrator_install.py:348
Search always hits default; misleading for private-registry users. Out of scope for this bugfix but worth follow-up issue. - [nit] Missing CHANGELOG entry for user-visible behavior change at
CHANGELOG.md
PR removes a user-visible warning and enables a previously-promised feature.
Supply Chain Security Expert
- [recommended] No policy gate restricts which registry URLs MCP deps may target at
src/apm_cli/policy/policy_checks.py
Policy engine checks MCP server names (check 7) but noallowed_registriesequivalent.apm.ymlalready a trust boundary (command:allows arbitrary code), so strictly less dangerous than existing surfaces -- but worth a follow-up enterprise lockdown hook.
Suggested: Add optionalallowed_registries: [...]field toMcpPolicy; when set, reject deps whoseregistry:value does not prefix-match. Unset = backward-compat allow-all. - [nit] Consider logging when a per-dep registry override is active
Diagnostic log line improves auditability in CI.
OSS Growth Hacker
- [recommended] Add a CHANGELOG entry that frames the fix as an enterprise capability unlock, not just a bug fix at
CHANGELOG.md
Enterprise framing converts plumbing-fix into adoption signal for evaluators scanning the changelog.
Suggested: Under Unreleased Fixed: 'MCP per-dep registry routing:registry: https://...on individual MCP deps is now honoured at install time, enabling mixed public+private MCP server topologies. Thanks @sergio-sisternes-epam. (fix(mcp): honour per-dep registry URL during install (#1393) #1443, closes [BUG] apm install ignores dependencies.mcp[].registry #1393)' - [recommended] Consumer docs (
install-mcp-servers.md) only showregistry:false-- add a string-URL example to surface the new capability atdocs/src/content/docs/consumer/install-mcp-servers.md
Enterprise adopters land oninstall-mcp-servers.mdfirst; without an example the feature is invisible.
Suggested: Add a section "Using a private MCP registry" with a 3-lineapm.ymlsnippet showingregistry: 'https://mcp.corp.example.com'alongside a public-registry dep. - [nit] Warm contributor reception in merge comment -- name the enterprise unlock
Improves contributor funnel conversion.
Auth Expert
- [nit] Add explicit
# SECURITY: no auth headers -- registry calls are anonymous by designcomment inSimpleRegistryClientatsrc/apm_cli/registry/client.py:150
Class has no doc/assertion guaranteeing "this session MUST remain unauthenticated". A one-line SECURITY comment would make the invariant grep-discoverable to prevent future contributors from naively addingsession.headers['Authorization'].
Doc Writer
- [recommended] Add an [Unreleased] Fixed entry for per-dep
registry: <url>on MCP deps (closes [BUG] apm install ignores dependencies.mcp[].registry #1393) atCHANGELOG.md
Keep a Changelog + project pattern; PR removes a user-visible warning and makes a documented field functional -- exactly the shape users grep CHANGELOG for. Silent release-comms drift otherwise.
Suggested: Under [Unreleased] Fixed:apm install --mcphonours the per-dependencyregistry: <url>override on MCP deps. (fix(mcp): honour per-dep registry URL during install (#1393) #1443, closes [BUG] apm install ignores dependencies.mcp[].registry #1393) - [recommended]
packages/apm-guide/.apm/skills/apm-usage/dependencies.mdMCP examples omit the per-depregistry: <url>form that this PR makes functional atpackages/apm-guide/.apm/skills/apm-usage/dependencies.md:188
apm-guide is agent-facing source of truth for dependency formats; per repo convention MCP-dep-format changes must update it. Currently strictly behind the manifest-schema doc.
Suggested: Add a 3-line example:registry: https://mcp.internal.example.comon a named MCP dep. - [nit]
docs/src/content/docs/producer/author-primitives/mcp-as-primitive.mdcould mirror the same per-dep URL example for parity atdocs/src/content/docs/producer/author-primitives/mcp-as-primitive.md:68
Field table already documents URL form; examples block omits it.
Test Coverage Expert
- [recommended] No integration-tier test proves
apm installroutes deps to per-dep registry URL end-to-end attests/integration/test_mcp_install_flow.py
New unit tests mockMCPServerOperationsat the class boundary -- they prove grouping logic but not real HTTP routing. Tier floor for install-pipeline is integration-with-fixtures; unit alone does not certify user promise.
Suggested: Add integration test callingrun_mcp_installwithMCPDependency(registry='https://custom.example.com')and assertMCPServerOperationsconstructor received that URL -- mock only HTTP layer beneath.
Proof (missing at):tests/integration/test_mcp_install_flow.py-- proves: When user setsregistry: https://custom.example.comon dep inapm.yml,apm installfetches that dep from the custom registry, not default. [devx, portability-by-manifest] - [nit] Unit regression trap exists for per-dep registry routing (kwarg assertion)
test_single_custom_registry_creates_one_instanceassertsops_cls.call_args == call(registry_url=url)-- valid regression trap at unit tier.
Proof (unknown):tests/unit/integration/test_mcp_per_dep_registry.py::test_single_custom_registry_creates_one_instance-- proves: If someone reverts per-dep registry routing, this test fails. [devx]
assert ops_cls.call_args == call(registry_url=url) - [nit] Self-defined (
registry=False) exclusion test only proves non-pollution, not correct installation
Mocks_check_self_defined_servers_needing_installationto return [] -- self-defined install path is unchanged by this PR and tested elsewhere; noting for completeness.
Proof (unknown):tests/unit/integration/test_mcp_per_dep_registry.py::test_false_registry_deps_are_excluded_from_registry_groups-- proves: Self-defined deps do not pollute registry groups. [devx]
Performance Expert -- inactive
PR touches integration/mcp_integrator_install.py only -- outside the perf-hot-path file set. The per-registry-group serial loop adds O(N) sequential HTTP round-trips where N = distinct registry URLs (typically 1-2); parallelizing groups would shave ~100-300ms per extra registry but is not blocking given common-case N=1.
This panel is advisory. It does not block merge. Re-apply the panel-review label after addressing feedback to re-run.
Folds the high-confidence panel consensus into the PR: - CHANGELOG [Unreleased] Fixed entry framing per-dep registry routing as enterprise capability unlock (oss-growth + doc-writer + devx-ux converged). - Docs example: 'Using a private MCP registry' in consumer/install-mcp-servers.md; parity examples in producer/ author-primitives/mcp-as-primitive.md and packages/apm-guide/.apm/ skills/apm-usage/dependencies.md. - ValueError wrap on per-dep MCPServerOperations construction: surfaces an actionable error pointing at the apm.yml dep instead of misleading MCP_REGISTRY_URL guidance (devx-ux recommendation). - Integration test in tests/integration/test_mcp_install_flow.py proves a per-dep 'registry: <url>' reaches SimpleRegistryClient at the HTTP layer (test-coverage-expert recommendation; mutation-break gate verified by forcing registry_url=None). - Two pre-existing integration tests still asserted the removed 'not yet applied' warning -- updated to match the documented new behaviour (mirrors test_registry_str_overlay_no_longer_emits_warning). Co-authored-by: sergio-sisternes-epam <sergio.sisternes@epam.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Folds the high-confidence panel consensus into the PR: - CHANGELOG [Unreleased] Fixed entry framing per-dep registry routing as enterprise capability unlock (oss-growth + doc-writer + devx-ux converged). - Docs example: 'Using a private MCP registry' in consumer/install-mcp-servers.md; parity examples in producer/ author-primitives/mcp-as-primitive.md and packages/apm-guide/.apm/ skills/apm-usage/dependencies.md. - ValueError wrap on per-dep MCPServerOperations construction: surfaces an actionable error pointing at the apm.yml dep instead of misleading MCP_REGISTRY_URL guidance (devx-ux recommendation). - Integration test in tests/integration/test_mcp_install_flow.py proves a per-dep 'registry: <url>' reaches SimpleRegistryClient at the HTTP layer (test-coverage-expert recommendation; mutation-break gate verified by forcing registry_url=None). - Two pre-existing integration tests still asserted the removed 'not yet applied' warning -- updated to match the documented new behaviour (mirrors test_registry_str_overlay_no_longer_emits_warning). Co-authored-by: sergio-sisternes-epam <sergio.sisternes@epam.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
c673f5b to
a13b026
Compare
Panel follow-ups folded into the PRMaintainer asked for the high-consensus panel recommendations to land in-PR. All green on Folded (this PR)
Deferred to follow-up issues
Validation
Co-authored-by: @sergio-sisternes-epam for the original fix. |
e5d149f to
3e2d4c5
Compare
3e2d4c5 to
c643846
Compare
Description
When an MCP dependency in
apm.ymlspecifies a per-depregistry:URL, APM now uses that URL for server resolution instead of the global default. Previously, the field was stored but ignored with a "not yet applied" warning.Fixes
Fixes #1393
Type of change
Changes
_install_registry_group()helper inmcp_integrator_install.py— encapsulates the validate-fetch-overlay-install loop for a group of registry depsdep.registryURL — deps with per-dep registry URLs get their ownMCPServerOperations(registry_url=url)instance_apply_overlay()test_registry_str_overlay_emits_warningnow verifies no warning is emittedTesting
test_mcp_per_dep_registry.py(grouping, routing, mixed deps)