Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
8 changes: 8 additions & 0 deletions .github/workflows/crates-publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,16 @@ jobs:
- name: Package and verify CLI crate
working-directory: rust
run: cargo publish --dry-run -p decided
- name: Package and verify MCP crate
working-directory: rust
run: cargo publish --dry-run -p decided-mcp
- name: Publish CLI crate
working-directory: rust
env:
CARGO_REGISTRY_TOKEN: ${{ steps.cargo-auth.outputs.token }}
run: cargo publish -p decided
- name: Publish MCP crate
working-directory: rust
env:
CARGO_REGISTRY_TOKEN: ${{ steps.cargo-auth.outputs.token }}
run: cargo publish -p decided-mcp
59 changes: 59 additions & 0 deletions .github/workflows/mcp-registry-publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
name: Publish to MCP Registry

on:
workflow_dispatch:
inputs:
version:
description: Exact released version to register
required: true
type: string

permissions:
contents: read
id-token: write

jobs:
publish:
runs-on: ubuntu-latest
environment: mcp-registry
steps:
- uses: actions/checkout@v5
- name: Verify requested and packaged versions
env:
REQUESTED_VERSION: ${{ inputs.version }}
run: |
WORKSPACE_VERSION="$(cargo metadata --manifest-path rust/Cargo.toml --no-deps --format-version 1 | jq -r '.packages[] | select(.name == "decided-mcp") | .version')"
SERVER_VERSION="$(jq -r '.version' server.json)"
PACKAGE_VERSION="$(jq -r '.packages[] | select(.identifier == "decided-mcp") | .version' server.json)"
test "$REQUESTED_VERSION" = "$WORKSPACE_VERSION"
test "$REQUESTED_VERSION" = "$SERVER_VERSION"
test "$REQUESTED_VERSION" = "$PACKAGE_VERSION"
- name: Verify MCP crate is available
env:
REQUESTED_VERSION: ${{ inputs.version }}
run: |
for attempt in {1..30}; do
if cargo info "decided-mcp@$REQUESTED_VERSION" --registry crates-io >/dev/null 2>&1; then
exit 0
fi
sleep 10
done
echo "::error::decided-mcp $REQUESTED_VERSION did not reach the crates.io index"
exit 1
- name: Install checksum-pinned MCP publisher
env:
PUBLISHER_VERSION: 1.8.0
PUBLISHER_SHA256: 1370446bbe74d562608e8005a6ccce02d146a661fbd78674e11cc70b9618d6cf
run: |
ARCHIVE="mcp-publisher_linux_amd64.tar.gz"
curl --fail --location --silent --show-error \
--output "$ARCHIVE" \
"https://github.com/modelcontextprotocol/registry/releases/download/v${PUBLISHER_VERSION}/${ARCHIVE}"
echo "${PUBLISHER_SHA256} ${ARCHIVE}" | sha256sum --check
tar -xzf "$ARCHIVE" mcp-publisher
- name: Validate Registry metadata
run: ./mcp-publisher validate
- name: Authenticate with GitHub OIDC
run: ./mcp-publisher login github-oidc
- name: Publish Registry metadata
run: ./mcp-publisher publish
21 changes: 21 additions & 0 deletions .github/workflows/pr-checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -121,3 +121,24 @@ jobs:
exit "$EXIT_CODE"
fi
echo "AsDecided Sentry passed."

mcp-distribution:
name: mcp distribution metadata
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- name: Verify Registry and Cargo metadata agree
run: |
WORKSPACE_VERSION="$(cargo metadata --manifest-path rust/Cargo.toml --no-deps --format-version 1 | jq -r '.packages[] | select(.name == "decided-mcp") | .version')"
test "$(jq -r '.name' server.json)" = "io.github.asdecided/core"
test "$(jq -r '.packages | length' server.json)" = "1"
test "$(jq -r '.packages[0].registryType' server.json)" = "cargo"
test "$(jq -r '.packages[0].registryBaseUrl' server.json)" = "https://crates.io"
test "$(jq -r '.packages[0].identifier' server.json)" = "decided-mcp"
test "$(jq -r '.packages[0].transport.type' server.json)" = "stdio"
test "$(jq -r '.version' server.json)" = "$WORKSPACE_VERSION"
test "$(jq -r '.packages[0].version' server.json)" = "$WORKSPACE_VERSION"
grep -Fx "mcp-name: io.github.asdecided/core" rust/decided-mcp/README.md
- name: Package MCP crate
working-directory: rust
run: cargo publish --dry-run --locked -p decided-mcp
4 changes: 4 additions & 0 deletions decisions/decisions/adr-039-lore-server-identity.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
---
schema_version: 1
id: RAC-KTY0D0DFTCJA
Expand All @@ -5,6 +5,10 @@
---
# ADR-039: Lore Server Identity

> The public Registry identity in this historical decision is superseded by
> ADR-124. Its `lore` handshake identity remains frozen only for the legacy MCP
> compatibility path specified by ADR-121.

## Status

Accepted
Expand Down
137 changes: 137 additions & 0 deletions decisions/decisions/adr-124-public-mcp-distribution.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
---
schema_version: 1
id: RAC-KYVTHFQD44BP
type: decision
---
# ADR-124: Publish the Native MCP Server Through Cargo and the Official Registry

## Context

AsDecided already ships `decided-mcp` in Homebrew and native GitHub Release
archives, but those channels do not make the server discoverable to MCP clients.
The official MCP Registry is a metadata registry: a listing must point at an
independently published package and prove control of both its namespace and that
package. Its live validator supports Cargo packages and verifies ownership by
reading an exact `mcp-name` token from the crate README.

ADR-039 reserved `io.github.tcballard/lore` before the AsDecided rename and
before the Rust cutover. ADR-121 has since made `decided-mcp` the current
protocol identity while retaining `lore` only in frozen legacy responses. A
fresh public distribution identity is therefore required; reviving the retired
name would make discovery disagree with the product, repository, and current
wire protocol.

## Decision

The public MCP Registry identity is `io.github.asdecided/core`.

- The native server is published as the dedicated `decided-mcp` crate on
crates.io. It is not hidden inside the `decided` CLI package and is not
wrapped in Python, npm, or an OCI-only launcher.
- The crate README carries the exact ownership marker
`mcp-name: io.github.asdecided/core`. Registry namespace ownership is proven
independently through GitHub OIDC for the `asdecided` organisation.
- `server.json` identifies the package as Cargo, pins an exact release version,
and declares stdio transport. Its title is AsDecided; its executable and
current MCP handshake identity remain `decided-mcp`.
- Registry publication happens only after the exact `decided-mcp` version is
visible on crates.io. The publisher binary is version- and checksum-pinned,
validates the manifest before publishing, and uses no long-lived Registry
credential.
- The listing describes a local server over a local repository. It does not
imply that AsDecided hosts, uploads, or remotely indexes a user's corpus.
- The first Registry publication must come from a new immutable release commit
and tag. Existing v0.26.0 artifacts are not retroactively rebuilt.

This decision supersedes only ADR-039's public Registry identity. ADR-121's
legacy `lore` bytes remain unchanged.

## Consequences

### Positive

- MCP clients and catalogues can discover the canonical Rust server through the
ecosystem's official metadata surface.
- Cargo is both the installation source and the Registry verification source,
so there is no second runtime or repackaged implementation to drift.
- GitHub OIDC and a checksum-pinned publisher preserve the release chain without
introducing a reusable organisation-wide Registry token.

### Negative

- A release now has another ordered publication step and can be partially
complete if crates.io succeeds but Registry publication fails.
- `server.json`, the workspace, and the package record must carry exactly the
same version at each release.
- The Registry is in preview, so its schema or availability can change.

### Risks

- A Registry retry could accidentally rebuild an already-published crate.
Mitigation: crate publication and Registry publication are separate manually
dispatched workflows; the latter first verifies that the immutable crate is
already available.
- Namespace or package verification could silently weaken. Mitigation: pin the
publisher checksum and test the exact namespace, package, transport, version,
and README token in pull-request CI.

## Status

Accepted

## Category

Architecture

## Alternatives Considered

### Register the GitHub Release archive or existing OCI image

Rejected as the primary Registry package. Cargo is natively supported and maps
directly to the Rust executable, while archive installation is not a Registry
package type and the existing image defaults to the CLI rather than the MCP
server.

### Publish an npm or Python wrapper

Rejected. A wrapper would restore the duplicate-runtime maintenance burden the
Rust cutover removed and would add no product capability.

### Keep `io.github.tcballard/lore`

Rejected. It represents a retired owner and product name and is retained only
where ADR-121 requires historical wire compatibility.

## Code Constraints

```yaml
version: 1
eligibility: eligible
reason: "The public identity and publication path have stable source anchors."
rules:
- id: registry-keeps-canonical-name
kind: require_pattern
path_glob: "server.json"
pattern: '"name": "io.github.asdecided/core"'
message: "The MCP Registry manifest must retain the canonical AsDecided identity."
- id: crate-keeps-registry-ownership-token
kind: require_pattern
path_glob: "rust/decided-mcp/README.md"
pattern: 'mcp-name: io.github.asdecided/core'
message: "The published crate must retain the Registry ownership token."
- id: registry-publish-keeps-oidc
kind: require_pattern
path_glob: ".github/workflows/mcp-registry-publish.yml"
pattern: 'login github-oidc'
message: "Registry publication must authenticate without a long-lived token."
```

## Related Decisions

- adr-039
- adr-111
- adr-121

## Related Requirements

- asdecided-public-mcp-distribution
97 changes: 97 additions & 0 deletions decisions/designs/mcp-registry-cargo-publishing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
---
schema_version: 1
id: RAC-KYVTHR4WNT8T
type: design
---
# MCP Registry Cargo Publishing

## Context

ADR-124 selects Cargo as the package authority behind AsDecided's official MCP
Registry record. The release design must coordinate three immutable values—the
workspace version, crates.io version, and Registry version—without making a
Registry outage force a duplicate crate publication.

## User Need

An MCP user needs to find the canonical AsDecided server, understand that it
runs locally over their repository, and install the actual Rust implementation.
A maintainer needs a release path that is inspectable, secretless at the
Registry boundary, and safe to retry after partial external failure.

## Design

The repository contains four connected surfaces:

1. `rust/decided-mcp/Cargo.toml` defines a publishable crates.io package whose
README contains `mcp-name: io.github.asdecided/core`.
2. `server.json` points `io.github.asdecided/core` at that exact Cargo package
version using stdio transport.
3. The crates.io workflow publishes `asdecided-core`, waits for it to reach the
index, then publishes both executable crates, `decided` and `decided-mcp`.
4. A separate Registry workflow accepts an exact version, proves it matches the
workspace and both manifest version fields, waits for that crate on
crates.io, installs an exact checksum-pinned `mcp-publisher`, validates the
listing, authenticates with GitHub OIDC, and publishes.

The first publication is made in the next patch release. v0.26.0 is not rebuilt
after its immutable release tag. Each subsequent release updates the workspace,
exact engine dependencies, lockfile, changelog, and both `server.json` version
fields together before the tag is created.

## Constraints

- Registry metadata cannot publish before its package exists and carries the
ownership token.
- crates.io versions and Git tags are immutable; recovery must move forward or
retry metadata publication, never overwrite an artifact.
- The Registry is metadata-only and in preview. Local installation and use must
remain viable if it is unavailable.
- Current identity is `decided-mcp`; ADR-121's legacy `lore` response bytes must
not change.

## Rationale

A dedicated Cargo package makes the Registry record resolve directly to the
binary users run. Splitting Registry publication from crate publication gives
the non-transactional release chain a safe recovery point: once crates.io has
accepted a version, the Registry step can be retried without touching it.

## Alternatives

- **One monolithic workflow:** rejected because a Registry failure leaves no
safe way to rerun after crates.io has accepted immutable versions.
- **OCI-only package:** rejected because the current image launches the CLI by
default and Cargo is the native distribution surface for this Rust server.
- **Hosted remote entry:** rejected because AsDecided has not decided to host
user corpora or introduce the associated tenancy, authentication, and trust
boundary.

## Accessibility

The title and description use plain product language rather than internal
component names. Installation and configuration examples remain text, are
copyable, and do not depend on images or colour.

## Style Guidance

Describe capability and authority precisely: “read-only,” “local repository,”
and “deterministic” are useful; “hosted,” “AI-powered,” or universal client
compatibility are unsupported claims. Keep AsDecided as the display name and
reserve `decided-mcp` for the executable and package.

## Open Questions

- Which MCP clients will consume Cargo package records automatically while the
Registry remains in preview?
- When the Registry reaches general availability, should publication move from
manual dispatch to an automatically gated release job?

## Related Decisions

- adr-124
- adr-121

## Related Requirements

- asdecided-public-mcp-distribution
Loading
Loading