Skip to content

Resolve and lock MCP servers from official and private registries #100

Description

@kelos-bot

🤖 Kelos Strategist Agent @gjkim42

Area: Integration Opportunities

Summary

Kanon should let MCP servers be declared by registry identity and exact version, then resolve and lock the chosen remote transport into kanon.lock.

Today every Kanon repository hand-copies a server's URL, transport details, and authentication-header shape into kanon.yaml. That loses the server's upstream identity and version, makes updates manual, and gives teams no standard way to consume an approved private MCP catalog. The official MCP Registry now exposes a versioned read API and a standard subregistry contract, while Kanon already has the cross-agent rendering and lockfile primitives needed to make that metadata useful.

The focused MVP should support exact-version remote HTTP/SSE entries only. This maps cleanly to Codex, Claude, and OpenCode today. Registry-described npm, PyPI, OCI, NuGet, and MCPB packages should follow later because selecting and installing runtimes is a separate policy problem.

Why this is a stronger product direction

This moves Kanon from “copy the same MCP JSON to several agents” toward “manage MCP dependencies once.” It has unusually broad leverage:

  1. Every current adapter benefits. MCP is already rendered for Codex, Claude, and OpenCode; this is not a single-agent integration.
  2. The source becomes explainable. A reviewed config names the publisher-owned server identity and version instead of an opaque URL copied from setup docs.
  3. Teams can use approved catalogs. The official registry defines subregistries with the same API, so a security/platform team can expose a filtered catalog with scan metadata while Kanon remains the local compiler.
  4. Updates become reviewable dependencies. Exact versions and lock diffs create a natural future path for Renovate-style update PRs without making render depend on mutable latest metadata.
  5. It builds on shipped architecture. Issue Add kanon.lock for remote skills and source dependencies #53 resulted in the current kanon.lock workflow for git skills; MCP registry entries extend that proven model instead of introducing a second sync controller or background service.

Evidence from the current codebase

Kanon has the renderer but no dependency identity for MCP:

  • MCPServer stores only the resolved native ingredients—command, args, env, url, headers, timeouts, and tool policy. It has no registry, publisher identity, version, or transport selector (internal/core/types.go).
  • ValidateConfig only requires an enabled MCP server to have url or command; it cannot detect an unknown/deleted registry server, an ambiguous transport, or metadata drift (internal/core/config.go).
  • codexMCPServers, claudeMCPServers, and openCodeMCPServers already normalize the same remote URL/header definition into all three native formats. A registry resolver can therefore produce one existing MCPServer value and reuse the adapters unchanged (internal/core/render.go, internal/core/opencode.go).
  • kanon lock, kanon lock check, and kanon lock update already provide the user workflow needed for dependency resolution (internal/cli/root.go).
  • The lock implementation is currently hard-coded to git skill sources: LockRemoteSkillSources enumerates only skills[].git, and validateSourceLock rejects every source type except git (internal/core/source_lock.go).
  • Render receives SourceLock through TargetOptions, so locked MCP resolution can happen before or during the existing render path without introducing destination state (internal/core/types.go, internal/cli/root.go).

Net: users must vendor registry metadata by hand even though Kanon already owns the normalized MCP model, per-agent compilation, and dependency-lock lifecycle.

External evidence

The integration surface is concrete rather than hypothetical:

  • The official registry exposes unauthenticated versioned endpoints for listing servers, listing versions, and fetching an exact version. It also defines a subregistry as any compatible implementation of the same OpenAPI contract. This is the standard seam Kanon can use for both the public registry and company-approved catalogs: https://modelcontextprotocol.io/registry/registry-aggregators
  • Registry server metadata can describe remote Streamable HTTP/SSE endpoints, URL variables, required headers, and the choice between remote and package-based installation: https://modelcontextprotocol.io/registry/remote-servers
  • The package schema separately supports npm, PyPI, NuGet, OCI, and MCPB coordinates, exact package versions, runtime hints/arguments, and optional artifact hashes. That validates a later package phase, but also shows why package execution should not be smuggled into the remote-only MVP: https://modelcontextprotocol.io/registry/package-types
  • The registry hosts metadata rather than artifacts, and its consumer documentation explicitly says it does not promise uptime or data durability. Kanon should therefore resolve deliberately and render from a committed lock entry—not call the registry on every render or apply.
  • A real exact-version response for io.github.github/github-mcp-server currently offers both an OCI package and a remote Streamable HTTP endpoint, including authentication-header metadata: https://registry.modelcontextprotocol.io/v0.1/servers/io.github.github%2Fgithub-mcp-server/versions/1.5.0

The registry is still documented as preview. That argues for an isolated, opt-in v0.1 client and committed resolved data, not for postponing the integration or baking registry calls into normal rendering.

Proposed schema

Add named registries and make registry: an alternative source for an MCP server. Existing raw url: / command: entries remain valid.

version: 1

mcp:
  registries:
    official:
      url: https://registry.modelcontextprotocol.io
      api: v0.1

    acme-approved:
      url: https://mcp-registry.acme.example
      api: v0.1

  servers:
    github:
      registry:
        source: official
        name: io.github.github/github-mcp-server
        version: 1.5.0
        transport: remote
        index: 0
      env_headers:
        Authorization: GITHUB_MCP_AUTHORIZATION
      enabled_tools:
        - get_file_contents
        - list_pull_requests

    internal-docs:
      registry:
        source: acme-approved
        name: com.acme/developer-docs
        version: 2.3.1
        transport: remote
        index: 0
      targets: [codex, claude, opencode]

Suggested rules:

  • registry is mutually exclusive with raw url, command, args, and type fields that define a transport. Existing policy fields such as targets, enabled_tools, timeouts, and approvals remain local overlays.
  • version is required and exact in the MVP. Do not accept latest or ranges in kanon.yaml; intentional upgrades should create reviewable source and lock diffs.
  • transport: remote is required initially. index disambiguates when the registry version advertises multiple remote transports; omit it only when there is exactly one compatible remote.
  • Non-secret URL template variables may be supplied in a variables: map. Reject secret URL variables in v1 so credentials are not embedded in destination URLs or logs.
  • Required secret headers bind through the existing env_headers / bearer_token_env_var fields. Missing required bindings fail fast and name the MCP server.
  • Unknown registry extension metadata is preserved in the lock hash but ignored by render. Kanon should not interpret a private registry's security rating unless a later explicit policy feature defines that contract.

Lockfile design

Extend kanon.lock to support a typed MCP registry entry while continuing to read existing version-1 git-only lockfiles. The lock must contain enough resolved, non-secret transport data for offline render.

Illustrative shape:

version: 2
sources:
  - owner: mcp.registry.github
    type: mcp-registry
    url: https://registry.modelcontextprotocol.io
    ref: io.github.github/github-mcp-server@1.5.0
    resolved_ref: io.github.github/github-mcp-server@1.5.0#remote:0
    content_sha256: sha256:4ab8...
    mcp:
      status: active
      transport: streamable-http
      url: https://api.githubcopilot.com/mcp/
      required_headers:
        - Authorization

Important properties:

  • Never write header values, tokens, expanded environment variables, or credentials into kanon.lock.
  • Canonicalize and hash the exact registry response fields that affect resolution. Store the selected resolved transport so normal render is deterministic and network-free.
  • kanon lock resolves missing entries without advancing matching existing entries, consistent with git skills.
  • kanon lock update github deliberately re-resolves that MCP entry after the version in kanon.yaml changes.
  • kanon lock check fetches the exact version, verifies the hash and selected transport, and checks mutable registry status. A deleted server is an error; deprecated should be at least a warning that names the server.
  • If the registry is unavailable, render, diff, and apply continue from a valid lock. lock check fails because it was explicitly asked to verify upstream state.

CLI workflow

No new top-level command is required for the MVP:

# Resolve git skills and registry-backed MCP servers.
kanon lock

# CI: verify coordinates, metadata hash, selected transport, and status.
kanon lock check

# Deliberately refresh one named dependency after changing its exact version.
kanon lock update github

# Render from committed source + lock without registry access.
kanon diff
kanon apply

Update command output from “git skill provider(s)” to dependency-oriented wording, but preserve the current commands and hidden source compatibility aliases.

A later kanon mcp search <query> --registry acme-approved command could improve discovery, but it is not needed to prove the dependency model and should not be bundled into the MVP.

Example team workflow

A platform team runs a standard-compatible subregistry that mirrors only approved servers and adds its own scanning metadata. Application teams reference exact entries from it:

mcp:
  registries:
    approved:
      url: https://mcp-registry.platform.example
      api: v0.1
  servers:
    issue-tracker:
      registry:
        source: approved
        name: com.example/issue-tracker
        version: 4.8.2
        transport: remote

A pull request changing 4.8.2 to 4.9.0 also updates kanon.lock. Reviewers see the upstream identity, selected endpoint, and metadata hash change. Developers still receive native Codex, Claude, and OpenCode configuration through the normal Kanon flow.

Existing issue overlap

I searched open and closed issues for registry, catalog, marketplace, MCP Registry, and private-registry proposals. I found no issue for consuming registry identities as MCP dependencies.

This is intentionally distinct from:

It also avoids the excluded agent lanes: this is not prompt self-development, documentation/CLI polish, or PR-review-derived configuration.

Suggested MVP

  1. Add MCPRegistryConfig and MCPRegistryRef types plus validation for named sources, exact versions, mutually exclusive raw/registry transports, and unambiguous remote selection.
  2. Add a small v0.1 registry client for the exact-version endpoint only. Bound response sizes, require HTTPS by default, use timeouts, and reject redirects to untrusted hosts unless explicitly allowed.
  3. Add backward-compatible lockfile v2 parsing and typed entries for git skills and MCP registry transports.
  4. Resolve registry MCP entries during kanon lock; require a matching lock before render. Convert the locked remote into the existing neutral MCPServer fields, then reuse all three adapters.
  5. Extend lock check and named lock update across both dependency types.
  6. Add tests for exact resolution, multiple-remote ambiguity, missing headers, inactive status, response/hash drift, offline render, credential redaction, old lockfiles, and unchanged raw MCP configs.
  7. Run make verify.

Backward compatibility

  • Existing kanon.yaml MCP entries using url or command are unchanged.
  • mcp.registries and servers.<name>.registry are optional.
  • Existing version-1 kanon.lock files remain readable; write version 2 only when needed for the new typed entry.
  • Imports continue producing raw MCP definitions. Do not guess a registry identity from a matching URL or command.
  • No destination file format or merge behavior changes; registry entries resolve into the neutral fields adapters already render.

Non-goals

  • Do not install or execute npm/PyPI/OCI/NuGet/MCPB packages in the MVP.
  • Do not build or host a Kanon registry service.
  • Do not treat the public registry as a security approval by itself.
  • Do not fetch registry metadata during ordinary render/apply when a valid lock exists.
  • Do not store or resolve secret values in the lockfile.
  • Do not add fuzzy search, ratings UI, or interactive marketplace browsing to the first implementation.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions