Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 8 additions & 4 deletions .apm/architecture/owners/install-deployment.json
Original file line number Diff line number Diff line change
Expand Up @@ -68,12 +68,16 @@
"id": "install-invocation-option-defaults",
"decision": "Install invocation option defaults",
"owner": "install/request.py (InstallRequest)",
"selectors": [
"src/apm_cli/install/request.py",
"src/apm_cli/commands/install.py"
],
"selectors": ["src/apm_cli/install/request.py"],
"guards": ["install-deployment-request-defaults"]
},
{
"id": "install-command-scope-selection",
"decision": "Install command scope selection",
"owner": "commands/install.py (install computes scope once)",
"selectors": ["src/apm_cli/commands/install.py"],
"guards": ["install-deployment-install-scope-selection"]
},
{
"id": "file-level-deploy-sync-cleanup",
"decision": "File-level deploy / sync / cleanup",
Expand Down
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
semver tag while continuing unrelated updates; malformed remote tag records
still fail before writes. The contract is recorded in `openapm-v0.1.md`.
(#2667)
- `apm install -g --mcp NAME` now creates or updates the user manifest and
deploys only to global-capable runtimes instead of rejecting `--global`.
Registry identities are validated before user-state writes, fail closed when
any registry is unreachable, and reject ambiguous bare names. Registry URLs
reject embedded credentials, queries, and fragments; saved or
environment-provided HTTP endpoints require explicit opt-in.
`openapm-v0.1.md` now codifies the user-scope target-selection and
pre-mutation contract. (#2734)
- Distributed `apm compile` now reconciles existing managed-section
`AGENTS.md` files without overwriting hand-authored content, generates new
placements safely, and never discovers, writes, or cleans content across
Expand Down
20 changes: 18 additions & 2 deletions CONFORMANCE.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
{
"consumer_user_scope": {
"lockfile_location": "~/.apm/apm.lock.yaml",
"manifest_location": "~/.apm/apm.yml",
"target_capability_declaration": "MCPClientAdapter.supports_user_scope (OpenAPM Target Registry v0.1 implementation profile)"
},
"generator": "gen_statement.py v1",
"requirements": [
{
Expand Down Expand Up @@ -1389,12 +1394,23 @@
"tests/spec_conformance/test_copilot_plugins_reqs.py::test_plugin_name_precedence_and_owner_identity_are_deterministic",
"tests/spec_conformance/test_copilot_plugins_reqs.py::test_removal_is_exact_and_unowned_entries_are_refused"
]
},
{
"conformance_class": "consumer",
"id": "req-tg-014",
"keyword": "MUST",
"section": "8.5.8",
"status": "active",
"test_count": 1,
"tests": [
"tests/spec_conformance/test_manifest_reqs.py::test_user_scoped_mcp_target_selection_ignores_project_signals"
]
}
],
"spec_version": "v0.1.1",
"summary_by_class": {
"consumer": {
"active": 89,
"active": 90,
"skipped": 1,
"unbound": 0,
"xfail": 0
Expand All @@ -1418,5 +1434,5 @@
"xfail": 0
}
},
"total_requirements": 120
"total_requirements": 121
}
9 changes: 8 additions & 1 deletion CONFORMANCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,18 @@ There is NO automated CI detector for spec-vs-behaviour drift beyond the four se

All four conformance classes (Producer, Consumer, Registry, Governance) carry active coverage in this statement. The Registry class is exercised via the trust-anchor invariant test in `tests/spec_conformance/test_registry_reqs.py`, which hashes the committed Registry-archive fixture and asserts equality with the digest the paired lockfile advertises (sec.11.3.3, req-rg-001).

## Consumer user-scope disclosure

- Manifest: `~/.apm/apm.yml`
- Lockfile: `~/.apm/apm.lock.yaml`
- Target capability declaration: `MCPClientAdapter.supports_user_scope (OpenAPM Target Registry v0.1 implementation profile)`

## Coverage summary

| Class | Active | Skipped | Xfail | Unbound |
|-------|-------:|--------:|------:|--------:|
| Producer | 12 | 0 | 0 | 0 |
| Consumer | 89 | 1 | 0 | 0 |
| Consumer | 90 | 1 | 0 | 0 |
| Registry | 1 | 0 | 0 | 0 |
| Governance | 17 | 0 | 0 | 0 |

Expand Down Expand Up @@ -147,6 +153,7 @@ All four conformance classes (Producer, Consumer, Registry, Governance) carry ac
| [req-tg-011](docs/src/content/docs/specs/openapm-v0.1.md#req-tg-011) | MUST | 8.5.5 | consumer | active | 2 | - |
| [req-tg-012](docs/src/content/docs/specs/openapm-v0.1.md#req-tg-012) | MUST | 8.5.6 | consumer | active | 1 | - |
| [req-tg-013](docs/src/content/docs/specs/openapm-v0.1.md#req-tg-013) | MUST | 8.5.7 | consumer | active | 7 | - |
| [req-tg-014](docs/src/content/docs/specs/openapm-v0.1.md#req-tg-014) | MUST | 8.5.8 | consumer | active | 1 | - |

## Waivers

Expand Down
5 changes: 5 additions & 0 deletions docs/public/specs/manifests/openapm-v0.1.requirements.yml
Original file line number Diff line number Diff line change
Expand Up @@ -438,6 +438,11 @@ requirements:
section: "8.5.7"
conformance_class: consumer
notes: "schema, effective-target, integrity, security, and executable admission drives one in-place aggregate registration opaque to legacy projection without lifecycle host-binary probing; only admitted dependencies participate in claimant selection and registration, direct dependencies win plugin-name collisions over transitive dependencies, equal-precedence collisions fail during admission, advisory cleanup omits ambiguous or changed-owner entries, and ledger-primary ownership permits exact-entry recovery of a reserved namespace while rejecting foreign collisions and invalid JSON, preserving unrelated JSON semantics, and rolling back catalog, ledger, and settings together"
- id: req-tg-014
keyword: MUST
section: "8.5.8"
conformance_class: consumer
notes: "user-scoped MCP target selection uses a disclosed versioned capability contract, ignores project-only detection signals, applies first-source precedence without fallback, filters mixed sets, and refuses zero-supported selections before user manifest, lockfile, or target-config mutation"
- id: req-pr-006
keyword: MUST
section: "8.1"
Expand Down
22 changes: 16 additions & 6 deletions docs/src/content/docs/consumer/install-mcp-servers.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,12 @@ apm install --mcp filesystem -- npx -y @modelcontextprotocol/server-filesystem /

# Remote
apm install --mcp linear --transport http --url https://mcp.linear.app/sse

# Global stdio install (writes user APM + runtime config only)
apm install -g --target claude --mcp fetch -- npx -y @modelcontextprotocol/server-fetch

# Global registry install (requires one unique registry match)
apm install -g --target claude --mcp io.github.github/github-mcp-server
```

`apm mcp install NAME ...` is an alias that forwards to the same code
Expand Down Expand Up @@ -220,12 +226,16 @@ non-whitespace absolute path. Unset or blank values use `~/.claude.json`;
relative values are rejected. Codex CLI writes to
`$CODEX_HOME/config.toml` when `CODEX_HOME` is set to a non-whitespace value or `~/.codex/config.toml` otherwise, Gemini CLI to `~/.gemini/settings.json`, Antigravity CLI to `~/.gemini/config/mcp_config.json`, Windsurf to
`~/.codeium/windsurf/mcp_config.json`, Kiro to `~/.kiro/settings/mcp.json`,
and JetBrains Copilot to its OS-specific user config). When the
package declares a `targets:` field (or the CLI passes `--target`),
only the matching runtimes receive the config write. When neither
restricts targets, all detected user-scope-capable runtimes are
configured. Workspace-only runtimes (VS Code, Cursor, OpenCode) are
skipped at user scope.
JetBrains Copilot to its OS-specific user config, and Hermes when enabled).
When the user-scope manifest declares a `targets:` field (or the CLI passes `--target`),
only the matching runtimes receive the config write. When no CLI target,
user-scope manifest target, or saved `apm config target` restricts targets,
all detected user-scope-capable runtimes are configured. Workspace-only
runtimes (VS Code, Cursor, OpenCode) are
skipped with a warning when a mixed target set also contains a global-capable
runtime. If none of the selected targets supports user scope, the command exits
`2` before changing the user manifest, lockfile, or runtime configuration. The direct command creates or updates
`~/.apm/apm.yml`; it does not fall back to the current project's manifest.

## stdio vs HTTP servers

Expand Down
11 changes: 11 additions & 0 deletions docs/src/content/docs/enterprise/security.md
Original file line number Diff line number Diff line change
Expand Up @@ -482,6 +482,17 @@ APM integrates MCP (Model Context Protocol) server configurations from packages.
For Codex remote transport requirements, see
[stdio vs HTTP servers](../../consumer/install-mcp-servers/#stdio-vs-http-servers).

### Direct registry installs at user scope

`apm install -g --mcp NAME` can use an MCP registry endpoint to update
account-wide runtime configuration. APM requires HTTPS for registry URLs from
the environment or saved config unless `MCP_REGISTRY_ALLOW_HTTP=1` is set, and
rejects embedded credentials, query strings, and fragments. When a non-default
registry supplies the entry, APM saves that registry URL for reproducible
replay, but registry metadata is not signed or content-hash verified. Treat a
custom registry as a trusted source with access to every global runtime selected
for the install.

### Direct dependencies

MCP servers declared by your direct dependencies (packages listed in your `apm.yml`) are auto-trusted. You explicitly chose to depend on these packages, so their MCP server declarations are accepted.
Expand Down
4 changes: 2 additions & 2 deletions docs/src/content/docs/reference/cli/config.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ Write `KEY` to `~/.apm/config.json`. Validates the value before writing:
- `self-update.channel` must be `stable` or `prerelease`.
- `self-update.install-dir` is expanded and stored as an absolute path. It becomes the default `APM_INSTALL_DIR` for `apm self-update` when the env var is not set.
- `copilot-cowork-skills-dir` must be absolute after expansion; the directory itself does not need to exist.
- `mcp-registry-url` must be an `http://` or `https://` URL with a valid host. All other schemes are rejected.
- `mcp-registry-url` must be an `http://` or `https://` URL with a valid host. Embedded credentials, invalid ports, query strings, fragments, and other schemes are rejected. Configured `http://` endpoints require `MCP_REGISTRY_ALLOW_HTTP=1` when used.
- Boolean keys reject anything outside the accepted truthy/falsy strings.

### `apm config unset KEY`
Expand All @@ -75,7 +75,7 @@ Remove `KEY` from `~/.apm/config.json`. No-op if the key is not set. Supported u
| `audit-on-install` | enum | `off` | Default content-audit mode for `apm install`: `off` / `warn` / `block`. `warn` records findings in the install summary; `block` halts on critical findings. Overridable per-install with `--audit` / `--no-audit`; an org policy `security.audit.on_install` floor can raise it. Requires the `external-scanners` experimental flag for `set`. |
| `external.<name>.llm` | boolean | unset | Opt a SARIF scanner into LLM-powered analysis (`<name>` validated against supported scanners). SkillSpector default is offline. LLM mode makes outbound API calls and needs `OPENAI_API_KEY` or `NVIDIA_INFERENCE_KEY`. Overridable per-run with `--external-llm` / `--no-external-llm`. Requires the `external-scanners` experimental flag. |
| `external.<name>.args` | string | unset | Extra scanner CLI flags, stored shlex-split as a list (e.g. `"--model gpt-4o"`). Allowlist-validated per adapter at run time. Overridable per-run with `--external-args`. Requires the `external-scanners` experimental flag. |
| `mcp-registry-url` | URL | public registry | Persist a private MCP registry endpoint. Accepts `http://` or `https://` URLs. Sits between `MCP_REGISTRY_URL` env and the built-in default in the resolution chain. Equivalent to exporting `MCP_REGISTRY_URL` permanently. |
| `mcp-registry-url` | URL | public registry | Persist a private MCP registry endpoint. Accepts `http://` or `https://` URLs. Configured HTTP endpoints require `MCP_REGISTRY_ALLOW_HTTP=1` when used; an explicit `--registry http://...` is the per-invocation opt-in. Sits between `MCP_REGISTRY_URL` env and the built-in default in the resolution chain. |
| `registry.<name>.url` | URL | unset | Base URL for registry `<name>`. Requires `registries` experimental flag. |
| `registry.<name>.token` | string | unset | Bearer token for registry `<name>`. Stored in `~/.apm/config.json`; never in repo-tracked files. Requires `registries` experimental flag. |
| `registry.<name>.default` | boolean | `false` | Mark `<name>` as the user-scoped default registry. Only one registry may be default at a time; setting `true` clears any previous default. Requires `registries` experimental flag. |
Expand Down
7 changes: 4 additions & 3 deletions docs/src/content/docs/reference/cli/install.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ With no arguments it installs everything from `apm.yml`. With one or more `PACKA
|---|---|---|
| `--update` | off | Re-resolve dependencies to the latest version or Git ref allowed by `apm.yml` and rewrite `apm.lock.yaml`. Mutable Git refs must resolve against upstream; APM does not fall back to stale refs from the local bare Git cache. Mutually exclusive with `--frozen`. For interactive use with a confirmation prompt, use [`apm update`](../update/) instead. |
| `--frozen` | off | Lockfile-only install: refuse to resolve anything new and fail before any project, config, deployment, or cache write if `apm.lock.yaml` is missing or out of sync with `apm.yml`, including MCP state. Mirrors `npm ci`. Mutually exclusive with `--update`, positional package additions, and `--mcp`. |
| `--dry-run` | off | Print the install plan without deployment writes. Positional packages and ref changes appear in the preview after validation but do not change an existing `apm.yml`; auto-bootstrap still keeps its new manifest and any explicit `--target` selection for the next run. |
| `--dry-run` | off | Print the install plan without deployment writes. Positional packages and ref changes appear in the preview after validation but do not change an existing `apm.yml`; auto-bootstrap still keeps its new manifest and any explicit `--target` selection for the next run. The `-g --mcp` path creates no user manifest, lockfile, or runtime configuration. |
| `--force` | off | Overwrite locally-authored files on collision **and** bypass the security scan's critical-finding block. Does **not** suppress general install errors (any reported error still exits `1`, matching npm / pip / cargo) or select ref freshness. Add `--update` or `--refresh` to resolve mutable refs upstream; [`apm update`](../update/) does so with or without `--force`. Use only after independent verification. |
| `--verbose`, `-v` | off | Show per-file paths and full error context in the diagnostic summary. |
| `--dev` | off | Add new packages to `devDependencies`. Dev deps install locally but are excluded from `apm pack` output. |
Expand All @@ -46,11 +46,11 @@ With no arguments it installs everything from `apm.yml`. With one or more `PACKA

| Flag | Default | Description |
|---|---|---|
| `--target`, `-t VALUE` | auto-detect | Force deployment targets. Comma-separated for multiple (`-t claude,cursor`). Values: `copilot`, `claude`, `grok-build`, `cursor`, `opencode`, `codex`, `gemini`, `antigravity`, `windsurf`, `kiro`, `intellij`, `vscode`, `agent-skills`, `all`; experimental `copilot-cowork`, `copilot-app`, and `grok-cloud` (skills only) are also accepted when enabled. IntelliJ-specific integration is MCP-only and writes JetBrains Copilot's user-scope MCP config; package file primitives use the Copilot profile. `all` excludes `agent-skills`, `antigravity`, `intellij`, and all experimental targets; combine them explicitly to add them, for example `all,intellij`. Explicit MCP target lists are exact: `intellij,claude` writes only those two MCP configs. See the precedence note below. With nothing to detect, install exits `2` with a teaching message. |
| `--target`, `-t VALUE` | auto-detect | Force deployment targets. Comma-separated for multiple (`-t claude,cursor`). Values: `copilot`, `claude`, `grok-build`, `cursor`, `opencode`, `codex`, `gemini`, `antigravity`, `windsurf`, `kiro`, `intellij`, `vscode`, `agent-skills`, `all`; experimental `copilot-cowork`, `copilot-app`, `grok-cloud` (skills only), and `hermes` are also accepted when enabled. IntelliJ-specific integration is MCP-only and writes JetBrains Copilot's user-scope MCP config; package file primitives use the Copilot profile. `all` excludes `agent-skills`, `antigravity`, `intellij`, and all experimental targets; combine them explicitly to add them, for example `all,intellij`. Explicit MCP target lists are exact: `intellij,claude` writes only those two MCP configs. See the precedence note below. With nothing to detect, install exits `2` with a teaching message. |
| `--runtime VALUE` | unset | Legacy alias for `--target` (single value only). Still accepted; prefer `--target`. |
| `--exclude VALUE` | unset | Skip one runtime from the resolved MCP/LSP target set (explicit selection, manifest, saved config, or auto-detection). |
| `--only apm\|mcp` | both | Install only APM packages or only MCP servers. |
| `-g`, `--global` | off | Install to user scope (`~/.apm/`) instead of the current project. MCP servers deploy only to global-capable runtimes, such as Copilot CLI, Claude Code, Codex CLI, Gemini CLI, Antigravity CLI, Kiro, Windsurf, and JetBrains Copilot. |
| `-g`, `--global` | off | Install to user scope (`~/.apm/`) instead of the current project. `apm install -g --mcp NAME` creates or updates `~/.apm/apm.yml`, then deploys only to global-capable runtimes, such as Copilot CLI, Claude Code, Codex CLI, Gemini CLI, Antigravity CLI, Kiro, Windsurf, JetBrains Copilot, and Hermes when enabled. Mixed selections skip workspace-only targets with a warning. A selection with no global-capable target exits `2` before changing the user manifest, lockfile, or runtime configuration. |
| `--legacy-skill-paths` | off | Deploy skills to per-client paths (`.cursor/skills/`, `.github/skills/`, ...) instead of the converged `.agents/skills/`. Env: `APM_LEGACY_SKILL_PATHS=1`. |

File primitives resolve targets in this order: `--target`, manifest
Expand Down Expand Up @@ -120,6 +120,7 @@ in `apm.yml`, then run `apm install` again.
- **Target persistence on bootstrap.** When `--target` maps to recognized manifest targets, those target(s) are persisted to the new manifest's `targets:` field so a later bare `apm update` redeploys to the same targets without re-specifying `--target`.
- **One effective target.** Package primitives, MCP servers, and LSP servers consume one target decision per invocation: `--target` > `apm.yml targets:` > `apm config set target ...` > auto-detect. A saved target therefore applies to `apm install`, `apm install --mcp`, and later `apm update` runs without another flag.
- **Required service writes fail loudly.** If MCP or LSP work is declared but no target can be resolved, install exits non-zero before changing the manifest, package deployment, or native service config. A native MCP/LSP config write failure also exits non-zero with the failed target and a permissions/path next step. A successful direct `--mcp` add never reports `Install interrupted`.
- **Direct registry lookup fails closed.** Registry-form MCP entries (`apm install --mcp NAME` with no `--url` and no post-`--` command) resolve one unique registry identity before writing the manifest or user config. An unreachable registry, missing identity, or ambiguous bare server name exits non-zero without changing state.
- **Diff-aware.** Packages whose ref or version changed in `apm.yml` are re-downloaded automatically. MCP servers with matching config are skipped (`already configured`); changed config is re-applied (`updated`).
- **Transactional replacement.** `--update` and `--refresh` download package
replacements to isolated staging paths and validate them before publication.
Expand Down
Loading