Compare commits

..
222 changed files with 7369 additions and 25941 deletions

1
.dockerignore Normal file
View file

@ -0,0 +1 @@
.db

View file

@ -11,11 +11,6 @@ MCP_REGISTRY_DATABASE_URL=postgres://username:password@localhost:5432/mcp-regist
# For offline development, use: data/seed.json
MCP_REGISTRY_SEED_FROM=https://registry.modelcontextprotocol.io/v0/servers
# Validate seed data against the registry rules on import (default: true)
# Set to false for offline development when seeding from a local file, since
# data/seed.json is not guaranteed to pass validation
MCP_REGISTRY_ENABLE_REGISTRY_VALIDATION=true
# GitHub OAuth configuration
# These creds are for local development with the 'MCP Registry Login (Local)' GitHub App
# They don't provide any real privileged access, hence why it's okay that they're here
@ -32,14 +27,6 @@ MCP_REGISTRY_JWT_PRIVATE_KEY=bb2c6b424005acd5df47a9e2c87f446def86dd740c888ea3efb
# This should be disabled in prod
MCP_REGISTRY_ENABLE_ANONYMOUS_AUTH=false
# GitHub OIDC token exchange (for `mcp-publisher login github-oidc`)
# Expected `aud` claim on incoming GitHub Actions OIDC tokens. Must equal the
# scheme + host that publishers pass via `--registry` (e.g. `https://registry.example.com`).
# The publisher CLI derives the audience from `--registry` automatically; the server
# rejects all OIDC exchanges if this is unset, so set it on every deployment that
# accepts GitHub Actions publishes.
MCP_REGISTRY_GITHUB_OIDC_AUDIENCE=
# Google Cloud Identity OIDC configuration for admin access
# Enable OIDC authentication for @modelcontextprotocol.io admin accounts
MCP_REGISTRY_OIDC_ENABLED=false

View file

@ -1,13 +0,0 @@
# Copilot Instructions for MCP Registry
## Important: Publishing MCP servers
The `data/seed.json` file is seed data for local development only. Do NOT create pull requests that add or modify server entries in `data/seed.json` to publish a server.
To publish an MCP server to the registry, use the `mcp-publisher` CLI tool. See `docs/modelcontextprotocol-io/quickstart.mdx` for instructions.
## Development
- Use `make` targets where possible (run `make help` to see available targets)
- Run `make check` to run lint, schema validation, and all tests (this also stops any running dev-compose environment)
- Run `make dev-compose` to start the local development environment

View file

@ -2,29 +2,21 @@ version: 2
updates:
- package-ecosystem: gomod
directory: /
groups:
go-dependencies:
patterns:
- "*"
schedule:
interval: weekly
interval: daily
- package-ecosystem: gomod
directory: /deploy
groups:
deploy-go-dependencies:
patterns:
- "*"
schedule:
interval: weekly
interval: daily
- package-ecosystem: github-actions
directory: /
groups:
github-actions:
actions:
patterns:
- "*"
schedule:
interval: weekly
- package-ecosystem: docker
directory: /
interval: daily
- package-ecosystem: "docker"
directory: "/"
schedule:
interval: weekly
interval: "daily"

View file

@ -24,10 +24,10 @@ jobs:
environment: ${{ inputs.environment }}
steps:
- name: Checkout code
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8
- name: Setup Pulumi
uses: pulumi/actions@8e5e406f4007fca908480587cb9893c07090f58d
uses: pulumi/actions@d7ceb0215da5a14ec84f50b703365ddf0194a9c8
with:
pulumi-version: ${{ env.PULUMI_VERSION }}

View file

@ -16,18 +16,18 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8
- name: Set up Go
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e
uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00
with:
go-version: 'stable'
go-version-file: 'go.mod'
cache: true
- name: Run lint
uses: golangci/golangci-lint-action@ba0d7d2ec06a0ea1cb5fa41b2e4a3ab91d21278a
uses: golangci/golangci-lint-action@4afd733a84b1f43292c63897423277bb7f4313a9
with:
version: v2.13.1
version: v2.4.0
- name: Validate schemas and examples
run: make validate
@ -35,12 +35,10 @@ jobs:
- name: Build application
run: make build
- name: Run govulncheck
uses: golang/govulncheck-action@v1
with:
go-version-input: 'stable'
go-package: ./...
repo-checkout: false
- name: Check for vulnerabilities
run: |
go install golang.org/x/vuln/cmd/govulncheck@latest
govulncheck ./...
# All Tests
tests:
@ -48,22 +46,19 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8
- name: Set up Go
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e
uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00
with:
go-version: 'stable'
go-version-file: 'go.mod'
cache: true
- name: Set up ko
uses: ko-build/setup-ko@v0.10
- name: Run all tests
run: make test-all
- name: Upload coverage artifacts
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02
with:
name: coverage-report
path: |

View file

@ -26,13 +26,13 @@ jobs:
actions: read
steps:
- name: Checkout repository
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8
with:
fetch-depth: 1
- name: Run Claude Code
id: claude
uses: anthropics/claude-code-action@v1
uses: anthropics/claude-code-action@beta
with:
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
@ -42,7 +42,10 @@ jobs:
# Trigger when assigned to an issue
assignee_trigger: "claude"
claude_args: |
--allowedTools Bash
--system-prompt "If posting a comment to GitHub, give a concise summary of the comment at the top and put all the details in a <details> block."
# Allow Claude to run bash
# This should be safe given the repo is already public
allowed_tools: "Bash"
custom_instructions: |
If posting a comment to GitHub, give a concise summary of the comment at the top and put all the details in a <details> block.

View file

@ -1,151 +0,0 @@
name: Close invalid publish PRs
# Stage 2 of 2 (see detect-invalid-publish-prs.yml for stage 1).
#
# Runs in the trusted base-repo context (triggered by workflow_run, not by the
# PR itself), so it has a write token. It picks up the PR number flagged by
# stage 1 and, after independently re-validating the PR from the API, posts a
# comment pointing at the publishing docs, labels it `invalid`, and closes it.
#
# Trust boundary: the ONLY thing crossing from the untrusted stage is the PR
# number (sanitized to digits below). We re-derive every decision (state,
# author, changed files) here, so a wrong/stale artifact can never cause us to
# close a PR that doesn't actually match.
on:
workflow_run:
workflows: ["Detect invalid publish PRs"]
types: [completed]
permissions:
contents: read
pull-requests: write
issues: write # labels on PRs go through the issues API
actions: read # needed to list/download the stage-1 artifact
jobs:
close:
if: github.event.workflow_run.conclusion == 'success'
runs-on: ubuntu-latest
env:
GH_TOKEN: ${{ github.token }}
GH_REPO: ${{ github.repository }}
RUN_ID: ${{ github.event.workflow_run.id }}
COMMENT_BODY: |
Hi @__AUTHOR__ 👋 — thanks for your interest in the MCP Registry!
It looks like this PR is trying to publish an MCP server by adding or editing server files in this repository (under `servers/`, `data/servers/`, or in `data/seed.json`). That isn't how servers get published, so I'm closing this PR automatically.
**Servers are published with the [`mcp-publisher`](https://github.com/modelcontextprotocol/registry/blob/main/docs/modelcontextprotocol-io/quickstart.mdx) CLI**, not by opening a pull request against this repo. The CLI verifies that you own your namespace and submits your `server.json` directly to the live registry API.
To publish your server, follow the **[Publishing Quickstart](https://github.com/modelcontextprotocol/registry/blob/main/docs/modelcontextprotocol-io/quickstart.mdx)**. In short:
```bash
# Build the publisher CLI
make publisher
# Authenticate (e.g. via GitHub) and publish your server.json
./bin/mcp-publisher login github
./bin/mcp-publisher publish
```
Note: `data/seed.json` is seed data for **local development only** — adding entries there does not publish anything to the registry.
If you believe this was closed in error, please leave a comment and a maintainer will take a look. 🙇
_This is an automated message._
steps:
- name: Check for flagged-pr artifact
id: check
run: |
set -euo pipefail
names="$(gh api "repos/$GH_REPO/actions/runs/$RUN_ID/artifacts" --jq '.artifacts[].name' || true)"
if printf '%s\n' "$names" | grep -qx "flagged-pr"; then
echo "found=true" >> "$GITHUB_OUTPUT"
else
echo "No flagged-pr artifact on run $RUN_ID; nothing to do."
echo "found=false" >> "$GITHUB_OUTPUT"
fi
- name: Download flagged PR number
if: steps.check.outputs.found == 'true'
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: flagged-pr
run-id: ${{ github.event.workflow_run.id }}
github-token: ${{ github.token }}
- name: Re-validate and close
if: steps.check.outputs.found == 'true'
run: |
set -euo pipefail
# Sanitize: the only cross-boundary input. Keep digits only.
PR_NUMBER="$(tr -dc '0-9' < pr-number.txt)"
if [ -z "$PR_NUMBER" ]; then
echo "No valid PR number in artifact; skipping."
exit 0
fi
echo "Re-validating PR #$PR_NUMBER"
state="$(gh pr view "$PR_NUMBER" --json state --jq '.state')"
if [ "$state" != "OPEN" ]; then
echo "PR #$PR_NUMBER is $state, not OPEN; skipping."
exit 0
fi
# Use the REST endpoint: `gh pr view --json` does not expose an
# authorAssociation field, so calling it there exits non-zero and,
# under `set -euo pipefail`, aborts the job before we ever close.
assoc="$(gh api "repos/$GH_REPO/pulls/$PR_NUMBER" --jq '.author_association')"
case "$assoc" in
MEMBER|OWNER|COLLABORATOR)
echo "Author association '$assoc' is trusted; skipping."
exit 0
;;
esac
# Idempotency: if a maintainer reopens after we labeled it, leave it be.
# Capture into a var first so a transient gh failure can't fail open
# (an errored fetch piped to grep would otherwise look "not labeled").
labels="$(gh pr view "$PR_NUMBER" --json labels --jq '.labels[].name' || true)"
if printf '%s\n' "$labels" | grep -qx "invalid"; then
echo "PR #$PR_NUMBER already labeled 'invalid'; skipping."
exit 0
fi
# Paginated REST endpoint (gh pr view --json files caps at 100, no pagination).
mapfile -t FILES < <(gh api --paginate "repos/$GH_REPO/pulls/$PR_NUMBER/files" --jq '.[].filename')
if [ "${#FILES[@]}" -eq 0 ]; then
echo "No files for PR #$PR_NUMBER; skipping."
exit 0
fi
# Keep this list in sync with the copy in stage 1.
publish_attempt=0
other=0
for f in "${FILES[@]}"; do
if [[ "$f" == servers/* \
|| "$f" == data/servers/* \
|| "$f" == data/data/servers/* \
|| "$f" =~ ^data/[^/]*server\.json$ \
|| "$f" == "data/seed.json" \
|| "$f" == "server.json" \
|| "$f" == "servers.json" ]]; then
publish_attempt=1
else
other=1
fi
done
if [ "$publish_attempt" -ne 1 ] || [ "$other" -ne 0 ]; then
echo "PR #$PR_NUMBER no longer matches the publish-attempt pattern; skipping."
exit 0
fi
author="$(gh pr view "$PR_NUMBER" --json author --jq '.author.login')"
body="${COMMENT_BODY//__AUTHOR__/$author}"
echo "Commenting on, labeling, and closing PR #$PR_NUMBER"
gh pr comment "$PR_NUMBER" --body "$body"
gh pr edit "$PR_NUMBER" --add-label "invalid" || echo "Could not add label; continuing."
gh pr close "$PR_NUMBER"

View file

@ -23,16 +23,16 @@ jobs:
cancel-in-progress: false
steps:
- name: Checkout code
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8
- name: Setup Go
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e
uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00
with:
go-version: 'stable'
go-version-file: 'go.mod'
cache: true
- name: Setup Pulumi
uses: pulumi/actions@8e5e406f4007fca908480587cb9893c07090f58d
uses: pulumi/actions@d7ceb0215da5a14ec84f50b703365ddf0194a9c8
with:
pulumi-version: ${{ env.PULUMI_VERSION }}

View file

@ -14,72 +14,75 @@ env:
PULUMI_VERSION: "3.188.0"
jobs:
ko-push:
name: Build and Push Image with ko
docker-push:
name: Build Docker Image
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- name: Checkout repository
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8
- name: Set up Go
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e
with:
go-version: 'stable'
cache: true
- name: Set up ko
uses: ko-build/setup-ko@v0.10
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435
- name: Log in to Container Registry
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Get build timestamp and tags
id: build-info
run: |
echo "timestamp=$(date -u +%Y-%m-%dT%H:%M:%SZ)" >> $GITHUB_OUTPUT
echo "date=$(date -u +%Y%m%d)" >> $GITHUB_OUTPUT
echo "short_sha=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT
- name: Get build timestamp
id: build-time
run: echo "timestamp=$(date -u +%Y-%m-%dT%H:%M:%SZ)" >> $GITHUB_OUTPUT
- name: Build and push with ko
env:
KO_DOCKER_REPO: ghcr.io/${{ github.repository }}
VERSION: main-${{ steps.build-info.outputs.date }}-${{ steps.build-info.outputs.short_sha }}
GIT_COMMIT: ${{ github.sha }}
BUILD_TIME: ${{ steps.build-info.outputs.timestamp }}
run: |
# Build and push multi-platform image
ko build ./cmd/registry \
--bare \
--platform=linux/amd64,linux/arm64 \
--tags=main-${{ steps.build-info.outputs.date }}-${{ steps.build-info.outputs.short_sha }},main
- name: Extract metadata
id: meta
uses: docker/metadata-action@c1e51972afc2121e065aed6d45c65596fe445f3f
with:
images: ghcr.io/${{ github.repository }}
tags: |
type=sha,prefix=main-{{date 'YYYYMMDD'}}-,enable={{is_default_branch}}
type=raw,value=main,enable={{is_default_branch}}
- name: Build and push Docker image
uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83
with:
context: .
file: ./Dockerfile
platforms: linux/amd64,linux/arm64
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
build-args: |
VERSION=${{ steps.meta.outputs.version }}
GIT_COMMIT=${{ github.sha }}
BUILD_TIME=${{ steps.build-time.outputs.timestamp }}
deploy-staging:
name: Deploy to Staging
runs-on: ubuntu-latest
environment: staging
needs: ko-push
needs: docker-push
concurrency:
group: deploy-staging
cancel-in-progress: false
steps:
- name: Checkout code
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8
- name: Setup Go
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e
uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00
with:
go-version: 'stable'
go-version-file: 'go.mod'
cache: true
- name: Setup Pulumi
uses: pulumi/actions@8e5e406f4007fca908480587cb9893c07090f58d
uses: pulumi/actions@d7ceb0215da5a14ec84f50b703365ddf0194a9c8
with:
pulumi-version: ${{ env.PULUMI_VERSION }}

View file

@ -1,96 +0,0 @@
name: Detect invalid publish PRs
# Stage 1 of 2 (see close-invalid-publish-prs.yml for stage 2).
#
# Some contributors try to "publish" a server by opening a PR that adds files
# under servers/ or edits data/seed.json. That is not how publishing works -
# servers are published with the mcp-publisher CLI against the live registry
# API. This job only DETECTS such PRs. It runs from the (untrusted) PR context
# with a read-only token, so it cannot comment on or close anything. When it
# finds a match it records just the PR number as an artifact; the privileged
# stage-2 workflow picks that up via workflow_run and acts on it.
#
# We never check out or run the PR's code - we only read changed file paths.
on:
pull_request:
types: [opened, reopened]
permissions:
contents: read
pull-requests: read
jobs:
detect:
runs-on: ubuntu-latest
env:
GH_TOKEN: ${{ github.token }}
GH_REPO: ${{ github.repository }}
PR_NUMBER: ${{ github.event.pull_request.number }}
AUTHOR_ASSOCIATION: ${{ github.event.pull_request.author_association }}
steps:
- name: Classify PR
id: classify
run: |
set -euo pipefail
match=false
# Maintainers may legitimately touch these files (e.g. dev seed data),
# so never flag their PRs.
case "$AUTHOR_ASSOCIATION" in
MEMBER|OWNER|COLLABORATOR)
echo "Author association '$AUTHOR_ASSOCIATION' is trusted; not flagging."
;;
*)
# Use the paginated REST endpoint: `gh pr view --json files` caps
# at 100 files with no pagination, which could truncate a large PR
# and cause a false match.
mapfile -t FILES < <(gh api --paginate "repos/$GH_REPO/pulls/$PR_NUMBER/files" --jq '.[].filename')
if [ "${#FILES[@]}" -gt 0 ]; then
# Flag only if the PR touches *exclusively* publish-attempt files
# and nothing else, so that legit PRs touching seed data
# alongside real code are left alone.
#
# Keep this list in sync with the copy in stage 2. It stays an
# explicit list rather than a broad `**/server.json` glob:
# because we require *every* file to match, an over-broad
# pattern would auto-close a legitimate single-file PR.
# data/servers/** is here because #1524 put its server.json
# there - a natural guess next to data/seed.json - and slipped
# through the original servers/**-only list.
publish_attempt=0
other=0
for f in "${FILES[@]}"; do
if [[ "$f" == servers/* \
|| "$f" == data/servers/* \
|| "$f" == data/data/servers/* \
|| "$f" =~ ^data/[^/]*server\.json$ \
|| "$f" == "data/seed.json" \
|| "$f" == "server.json" \
|| "$f" == "servers.json" ]]; then
publish_attempt=1
else
echo "Non-publish file changed: $f"
other=1
fi
done
if [ "$publish_attempt" -eq 1 ] && [ "$other" -eq 0 ]; then
match=true
fi
fi
;;
esac
echo "PR #$PR_NUMBER match=$match"
echo "match=$match" >> "$GITHUB_OUTPUT"
if [ "$match" = "true" ]; then
echo "$PR_NUMBER" > pr-number.txt
fi
- name: Record flagged PR number
if: steps.classify.outputs.match == 'true'
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v4
with:
name: flagged-pr
path: pr-number.txt
retention-days: 1

View file

@ -14,24 +14,24 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8
with:
fetch-depth: 0
- name: Set up Go
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e
uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00
with:
go-version: 'stable'
go-version-file: 'go.mod'
cache: true
- name: Install cosign
uses: sigstore/cosign-installer@6f9f17788090df1f26f669e9d70d6ae9567deba6 # v4.1.2
uses: sigstore/cosign-installer@7e8b541eb2e61bf99390e1afd4be13a184e9ebc5
- name: Install Syft
uses: anchore/sbom-action/download-syft@v0.24.2
uses: anchore/sbom-action/download-syft@v0.20.9
- name: Run GoReleaser
uses: goreleaser/goreleaser-action@f06c13b6b1a9625abc9e6e439d9c05a8f2190e94
uses: goreleaser/goreleaser-action@e435ccd777264be153ace6237001ef4d979d3a7a
with:
distribution: goreleaser
version: v2.12.0
@ -39,48 +39,48 @@ jobs:
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
ko-push:
name: Build and Push Image with ko
docker:
runs-on: ubuntu-latest
needs: goreleaser
steps:
- name: Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8
- name: Set up Go
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e
with:
go-version: 'stable'
cache: true
- name: Set up ko
uses: ko-build/setup-ko@v0.10
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435
- name: Log in to Container Registry
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Get build timestamp and version
id: build-info
run: |
echo "timestamp=$(date -u +%Y-%m-%dT%H:%M:%SZ)" >> $GITHUB_OUTPUT
# Extract version from tag and strip 'v' prefix (e.g., refs/tags/v1.2.3 -> 1.2.3)
VERSION="${GITHUB_REF#refs/tags/}"
VERSION="${VERSION#v}"
echo "version=$VERSION" >> $GITHUB_OUTPUT
- name: Get build timestamp
id: build-time
run: echo "timestamp=$(date -u +%Y-%m-%dT%H:%M:%SZ)" >> $GITHUB_OUTPUT
- name: Build and push with ko
env:
KO_DOCKER_REPO: ghcr.io/${{ github.repository }}
VERSION: ${{ steps.build-info.outputs.version }}
GIT_COMMIT: ${{ github.sha }}
BUILD_TIME: ${{ steps.build-info.outputs.timestamp }}
run: |
# Build and push multi-platform image with version tag and latest
ko build ./cmd/registry \
--bare \
--platform=linux/amd64,linux/arm64 \
--tags=${{ steps.build-info.outputs.version }},latest
- name: Extract metadata
id: meta
uses: docker/metadata-action@c1e51972afc2121e065aed6d45c65596fe445f3f
with:
images: ghcr.io/${{ github.repository }}
tags: |
type=semver,pattern={{version}}
type=raw,value=latest
- name: Build and push Docker image
uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83
with:
context: .
file: ./Dockerfile
platforms: linux/amd64,linux/arm64
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
build-args: |
VERSION=${{ steps.meta.outputs.version }}
GIT_COMMIT=${{ github.sha }}
BUILD_TIME=${{ steps.build-time.outputs.timestamp }}

View file

@ -1,69 +0,0 @@
name: Sync Schema
on:
workflow_dispatch: # Manual trigger
# TODO: Add daily schedule later
# schedule:
# - cron: '0 2 * * *' # Run daily at 2 AM UTC
permissions:
contents: write
jobs:
sync-schema:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v7
- name: Checkout static repo
uses: actions/checkout@v7
with:
repository: modelcontextprotocol/static
path: static-repo
- name: Sync schemas from static repo
run: |
echo "🔍 Syncing schemas from modelcontextprotocol/static..."
mkdir -p internal/validators/schemas
# Copy all versioned schema files
for dir in static-repo/schemas/*/; do
if [ -f "$dir/server.schema.json" ]; then
version=$(basename "$dir")
# Skip draft directory if it exists
if [ "$version" != "draft" ]; then
output_file="internal/validators/schemas/${version}.json"
if [ ! -f "$output_file" ] || ! cmp -s "$dir/server.schema.json" "$output_file"; then
echo "⬇ Adding/updating ${version}/server.schema.json -> ${version}.json"
cp "$dir/server.schema.json" "$output_file"
else
echo "✓ ${version} is already up to date"
fi
fi
fi
done
echo "✅ Schema sync complete"
- name: Check for changes
id: changes
run: |
# Check for both modified and untracked files
if [ -n "$(git status --porcelain internal/validators/schemas/)" ]; then
echo "changed=true" >> $GITHUB_OUTPUT
git status --porcelain internal/validators/schemas/
else
echo "changed=false" >> $GITHUB_OUTPUT
echo "No changes to schemas"
fi
- name: Commit and push changes
if: steps.changes.outputs.changed == 'true'
run: |
git config --local user.email "action@github.com"
git config --local user.name "GitHub Action"
git add internal/validators/schemas/
git commit -m "Sync schemas from modelcontextprotocol/static [skip ci]"
git push

2
.gitignore vendored
View file

@ -12,4 +12,4 @@ validate-schemas
coverage.out
coverage.html
deploy/infra/infra
registry
registry

View file

@ -22,7 +22,7 @@ linters:
- gocyclo
- godox
- gomoddirectives
- gomodguard_v2
- gomodguard
- goprintffuncname
- gosec
- grouper
@ -55,10 +55,6 @@ linters:
max-complexity: 20
dupl:
threshold: 300
goconst:
# Repeated literals in tests are fixture data ("1.0.0", "Test server", ...);
# hoisting them into constants hurts readability more than it helps.
ignore-tests: true
gocognit:
min-complexity: 51
nestif:
@ -70,11 +66,6 @@ linters:
- common-false-positives
- legacy
- std-error-handling
rules:
# httptest.NewRequest is idiomatic in tests; noctx is not useful there.
- path: _test\.go
linters:
- noctx
paths:
- third_party$
- builtin$

View file

@ -95,24 +95,26 @@ signs:
cmd: cosign
args:
- "sign-blob"
- "--bundle=${signature}" # cosign v3+: bundles signature and certificate together
- "--output-signature=${signature}"
- "--output-certificate=${certificate}"
- "${artifact}"
- "--yes" # needed on cosign 2.0.0+
artifacts: archive
output: true
signature: "${artifact}.sigstore.json"
certificate: '{{ trimsuffix (trimsuffix .Env.artifact ".zip") ".tar.gz" }}.pem'
# Also sign checksums file for additional verification
- id: checksums
cmd: cosign
args:
- "sign-blob"
- "--bundle=${signature}" # cosign v3+: bundles signature and certificate together
- "--output-signature=${signature}"
- "--output-certificate=${certificate}"
- "${artifact}"
- "--yes"
artifacts: checksum
output: true
signature: "${artifact}.sigstore.json"
certificate: '{{ trimsuffix .Env.artifact ".txt" }}.pem'
# This section defines the release format.
archives:

View file

@ -1,52 +0,0 @@
# Build artifacts
bin/
*.exe
*.dll
*.so
*.dylib
# Test and coverage files
coverage.out
coverage.html
*.test
# Database files
*.db
.db/
# Deployment files
deploy/
# Documentation
docs/
# Tests
tests/
# Git
.git/
.gitignore
# GitHub Actions
.github/
# Docker files (no longer used)
Dockerfile
.dockerignore
docker-compose.yml
# IDE and editor files
.vscode/
.idea/
*.swp
*.swo
*~
# Temporary files
tmp/
temp/
*.tmp
# OS files
.DS_Store
Thumbs.db

View file

@ -1,32 +0,0 @@
# ko configuration for MCP Registry
# Documentation: https://ko.build/configuration/
# Default base image for all builds
# Using Chainguard's static image for minimal, secure containers (~2MB)
defaultBaseImage: cgr.dev/chainguard/static:latest
# Default platforms for multi-architecture builds
defaultPlatforms:
- linux/amd64
- linux/arm64
# Build configuration
builds:
- id: registry
# Main package to build
main: ./cmd/registry
# Inject version information at build time via ldflags
ldflags:
- -s -w
- -X main.Version={{ .Env.VERSION }}
- -X main.GitCommit={{ .Env.GIT_COMMIT }}
- -X main.BuildTime={{ .Env.BUILD_TIME }}
env:
- CGO_ENABLED=0
# SBOM (Software Bill of Materials) configuration
sbom: spdx
# Base import path handling
# Set to false to preserve full import paths in image names
baseImportPaths: false

View file

@ -2,9 +2,3 @@
_Guidance for Claude Code (claude.ai/code) when working in this repository. If it's also useful to humans (probably most things!), put the instructions in README.md instead._
Import @README.md
## Important: Publishing MCP servers
The `data/seed.json` file is seed data for local development only. Do NOT create pull requests or commits that add server entries to `data/seed.json` as a way to publish a server to the registry.
To publish an MCP server, use the `mcp-publisher` CLI tool. See `docs/modelcontextprotocol-io/quickstart.mdx` for instructions.

View file

@ -1,40 +0,0 @@
# Contributing to the MCP Registry
Thank you for your interest in contributing!
## Want to publish an MCP server?
**Do NOT open a pull request to add your server to `data/seed.json`.**
The `data/seed.json` file is seed data used only for local development. Modifying it will not publish your server to the registry.
To publish an MCP server, use the official `mcp-publisher` CLI tool. See the [publishing quickstart guide](docs/modelcontextprotocol-io/quickstart.mdx) for step-by-step instructions.
## Contributing to the registry codebase
We welcome contributions to the registry itself! Here's how to get started:
### Communication channels
We use multiple channels for collaboration - see [modelcontextprotocol.io/community/communication](https://modelcontextprotocol.io/community/communication).
Often (but not always) ideas flow through this pipeline:
- **[Discord](https://modelcontextprotocol.io/community/communication)** - Real-time community discussions
- **[Discussions](https://github.com/modelcontextprotocol/registry/discussions)** - Propose and discuss product/technical requirements
- **[Issues](https://github.com/modelcontextprotocol/registry/issues)** - Track well-scoped technical work
- **[Pull Requests](https://github.com/modelcontextprotocol/registry/pulls)** - Contribute work towards issues
### Development setup
See the [README](README.md#quick-start) for prerequisites and instructions on running the server locally.
### Running checks
```bash
# Run lint, schema validation, and all tests
# Note: this also runs `make dev-down`, stopping any running dev-compose environment
make check
```
Run `make help` for more available commands.

Some files were not shown because too many files have changed in this diff Show more