Commit graph registry/internal/api
Author SHA1 Message Date
Radoslav Dimitrov
4e147072f0
metrics: stop counting client-cancelled requests as server errors (#1255)
## Summary

- Remap recorded status to `499` (NGINX-style "client closed") when
`ctx.Context().Err()` is `context.Canceled` after the handler returns
- Skip the `http_errors_total` increment for `499`s so the availability
metric reflects server-visible errors only

## Why

Under scraper-driven load, clients with short timeouts paginate
`/v0.1/servers`, give up while PG is busy, and close the TCP connection.
The handler's DB iteration returns `context canceled`, the handler
converts that to `huma.Error500InternalServerError`, and tries to write
a response to the now-closed socket. NGINX records `499` (client closed)
and never delivers anything to the client — but the registry's
middleware records `status_code=500` and bumps
`mcp_registry_http_errors_total`.

That's what's been firing the `Availability dropped below 95%` alert
during the daily 17:00 UTC bursts. Recent prod numbers from one pod
(~19h uptime):

```
mcp_registry_http_errors_total{path="/v0.1/servers", status_code="500"} 7396
mcp_registry_http_errors_total{path="/v0.1/servers/{serverName}/versions", status_code="500"} 283
mcp_registry_http_errors_total{path="/v0/servers", status_code="500"} 352
```

…against zero 5xx in NGINX ingress logs over the same window. The alert
was correct *given the data it had*; the data was misclassifying client
cancellations as server errors.

## What this does

In `internal/api/router/router.go` (the metric middleware): after
`next(ctx)`, check `ctx.Context().Err()`. If it's `context.Canceled`,
override `statusCode` to `499` and skip the error counter. Requests
counter and duration histogram still record so the cancellation rate
stays visible.

`context.DeadlineExceeded` is intentionally **not** remapped — that
would indicate a server-side timeout if we ever add per-request
deadlines, and should still count as a server error.

## What this does *not* do

- Doesn't reduce the underlying load (ServiceNow + notion catalog
scrapers driving the daily peak — separate work, see the `updated_since`
outreach we're doing).
- Doesn't change handler behaviour — handlers still call
`huma.Error500…` on `context.Canceled`. We could short-circuit earlier
(return without writing) to save the work, but that's a bigger refactor
and the metric fix unblocks the alert immediately.

## Companion change (no code, just a Grafana annotation tweak)

The `Availability dropped below 95%` alert (UID `bexrc60etvhmoa`)
currently has the annotation:

> No. of 5xx are increased from acceptable limit i.e. 5% of total
throughput

The query is on `mcp_registry_http_errors_total`, not 5xx specifically.
After this change the count is meaningful again, but the wording is
still misleading. Suggest updating to:

> Internal error rate exceeded 5% of total throughput

(no PromQL change required.)

## Test plan

- [x] `go test ./internal/api/router/...` — new test passes
- [x] `golangci-lint run ./internal/...` — clean
- [ ] After merge, watch a daily 17:00 UTC burst —
`mcp_registry_http_errors_total{status_code="500",
path="/v0.1/servers"}` should stay flat while
`mcp_registry_http_requests_total{status_code="499",
path="/v0.1/servers"}` grows. Availability alert should not fire on
cancellation-only bursts.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-05 05:58:05 -04:00
Radoslav Dimitrov
f5f40bd980
fix(auth/http): extend SSRF blocklist to IPv6 6to4/NAT64/site-local prefixes (#1250)
The HTTP namespace-verification fetcher's `isBlockedIP` composed its
blocklist from Go stdlib's `Is*` helpers plus a manual CGNAT range. That
covers IPv4 cloud metadata, RFC1918, ULA, and link-local — but Go's
classification helpers don't recognise IPv6 prefix families that embed
or tunnel to arbitrary IPv4:

  - 2002::/16       RFC 3056 6to4 — bits 16-47 are an arbitrary IPv4
  - 64:ff9b::/96    RFC 6052 NAT64 well-known — low 32 bits are IPv4
  - 64:ff9b:1::/48  RFC 8215 NAT64 local-use — same IPv4-embedding shape
  - fec0::/10       RFC 3879 site-local (deprecated, still routed by
                    some stacks)

On hosts with 6to4 routing or a NAT64 next-hop (the default on
IPv6-only GKE node pools, AWS IPv6-only EC2, Azure IPv6 VMs with
NAT64, and many corporate DNS64/NAT64 networks), an attacker who
controls DNS for a publisher domain can cause the registry to dial
`169.254.169.254`, `10.0.0.0/8`, etc. via the IPv6 wrapper. Same
SSRF class as GHSA-56c3-vfp2-5qqj on n8n-mcp.

Define the four prefixes via a new `mustCIDR` helper (panic on
malformed CIDR at init so a typo can't fail-open silently) and iterate
them in `isBlockedIP` after the stdlib gates. Refactor the existing
CGNAT IIFE to use the same helper for consistency. IPv4-mapped IPv6
(::ffff:0:0/96) needs no special-casing: Go's `Is*` helpers honour the
`To4()` fast-path, so e.g. `::ffff:10.0.0.1` is already classified as
`IsPrivate`.

Adds 17 test cases to `TestIsBlockedIP` covering each new prefix
family, IPv4-mapped IPv6 sanity checks, and just-outside-the-range
positive controls (`2001::1`, `2003::1`, `64:ff9c::1`).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 09:41:23 -04:00
Radoslav Dimitrov
fbfd63ced8
fix(ui): encode publisher-controlled URLs for href attribute context (#1248)
The catalogue page interpolates publisher-supplied URL fields
(`server.repository.url`, `server.websiteUrl`) inside `href="..."`
attributes via `innerHTML`, escaping them with a
`textContent`→`innerHTML`
helper. Per the HTML5 fragment-serialisation algorithm, that round-trip
encodes only `&`, `<`, `>`, and U+00A0 inside text nodes — it does not
encode `"` or `'`. The helper is therefore safe in element-text
contexts (where it is used for `name`, `version`, `description`, etc.)
but unsafe inside an attribute value.

Add an attribute-safe `escapeAttr` helper that follows the OWASP
attribute-encoding rule (also encodes `"` and `'`) and use it for the
two `href` interpolations. Element-text usages keep the existing
`escapeHtml` and remain unchanged. A short comment on each helper
documents the contexts they cover so future fields are wired up
correctly.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 09:25:41 -04:00
Radoslav Dimitrov
7a4216c528
refactor: rename readBody to readErrorBody and cap body size (#1246)
## Summary

Follow-up to #1241. Two small tweaks to the `readBody` helper:

- **Rename `readBody` → `readErrorBody`.** The helper is only ever
called on the error path (after a non-2xx status check), where we want a
small string for diagnostics. The generic name made it look reusable on
the success path, where streaming JSON decoding is the right approach.
The new name pins the intended use down.
- **Cap the read with `io.LimitReader` (8 KiB).** Defense in depth —
error diagnostics never need more than a few KB, and we shouldn't buffer
an arbitrarily large body from an upstream just to format an error
message. 8 KiB is well above any realistic GitHub API or JWKS error
response.

No behavior change on the happy path. On the unhappy path, error
messages are now bounded.

## Test plan

- [x] `go build ./...`
- [x] `go test ./internal/api/handlers/v0/auth/...`

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-05-04 07:09:57 -04:00
Mesut Oezdil
6f9bd6661c
refactor: extract readBody helper to remove duplicate io.ReadAll calls (#1241)
The same pattern for reading an error response body appeared in three
places across the auth handlers:

- `internal/api/handlers/v0/auth/github_at.go` (twice)
- `internal/api/handlers/v0/auth/github_oidc.go` (once)

A shared `readBody` helper function added to `common.go` now handles
this in one place.
The `io` import is removed from both files since it is no longer used
directly there.

---------

Co-authored-by: Radoslav Dimitrov <radoslav@stacklok.com>
2026-05-04 06:57:02 -04:00
Radoslav Dimitrov
dfaa8f3b82
auth/oidc: test slice-typed extra claims comparison (#1245)
## Summary
Follow-up to #1238 (closes #988). Adds direct unit-test coverage for the
`claimMatches` helper, which was the actual change in #1238.

`claimMatches` and `toAnySlice` are unexported, so the tests live in
`oidc_internal_test.go` (`package auth`) — same pattern as
`http_internal_test.go`. Cases covered:

- scalar/scalar match and mismatch
- scalar actual against array expected (both directions of overlap)
- array actual against scalar expected
- **array/array** — pre-#1238 this combination panicked with `comparing
uncomparable type []interface {}`
- `[]string` typed slice (exercises the typed-slice branch of
`toAnySlice`, which `[]any` skips)
- non-string scalars (int, bool)
- empty actual array

Test values use single letters (`x`, `y`, `p`, `q`) rather than role
labels — only the relationships matter, and short tokens keep `goconst`
quiet.

## Test plan
- [x] `go test ./internal/api/handlers/v0/auth/...` — all 13 subtests
pass
- [x] `golangci-lint run ./internal/api/handlers/v0/auth/...` — no new
issues vs main
2026-05-04 06:52:12 -04:00
Sai Asish Y
811046c4ce
auth/oidc: support slice-typed extra claims via overlap match (#1238)
Closes #988. Direct `actualValue != expectedValue` fails when the token
claim is an array (groups, roles, scp, aud). Switch to a slice-aware
any-of overlap so OIDC list claims are validated correctly.
2026-05-04 06:24:34 -04:00
Radoslav Dimitrov
3f89fc2b1f
auth: bind GitHub OIDC token exchange to a per-deployment audience (#1229)
## What this does

The publisher CLI now derives the OIDC audience to request from the
`--registry` URL instead of using a fixed constant, and the server
validates the token's `aud` claim against a new per-deployment env var
(`MCP_REGISTRY_GITHUB_OIDC_AUDIENCE`).

Production sets this env var to
`https://registry.modelcontextprotocol.io` (and per-environment URL
elsewhere), so the deployment only accepts tokens that were requested
for it.

## Compatibility

This is a breaking change for older `mcp-publisher` binaries that still
request the legacy hardcoded audience. After this rolls to a deployment,
users on old `mcp-publisher` will see a clear `invalid audience:
expected …, got […]` error and need to upgrade. Users following the
documented `releases/latest` install pattern get the new binary
automatically.

## Test plan

- [ ] CI green
- [ ] Run a synthetic publish from a real GitHub Actions workflow
against staging after deploy
- [ ] Confirm an older `mcp-publisher` against the same staging deploy
returns the expected `invalid audience` error

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 20:27:26 -04:00
Radoslav Dimitrov
1201cbd82b
security: fix open redirect and add small hardening (#1227)
## What this does

Fixes an open redirect plus a handful of small, mostly-invisible
hardening changes around it.

## What's in it

- **The fix**: trailing-slash redirects no longer let `//evil.com/`
redirect off-host.
- **SSRF block** on the DNS/HTTP namespace verification — refuses to
dial loopback, RFC1918, link-local, multicast, CGNAT, or
IP-literal/single-label \"domains\".
- **Slow-client guard**: `ReadTimeout` and `IdleTimeout` on the HTTP
server (no `WriteTimeout` — would risk cutting off legitimate
multi-package publishes).
- **Defence-in-depth headers** on the UI (`/`) and 404 responses.
- **5xx responses** stop leaking raw DB error text to clients (logged
server-side instead).
- **Search filter** stops treating `%` and `_` as wildcards.
- **Validators**: PyPI/NuGet identifiers URL-escaped before fetching;
MCPB no longer follows redirects.
- **Misc**: `Version` field bounded to 255 chars; logs quote user input;
`BlockedNamespaces` denylist now catches subdomain claimants.

Each change is its own commit so they can be reviewed (or reverted)
independently.

## Test plan

- [ ] CI green
- [ ] Synthetic publish in staging for each package type (npm, pypi,
nuget, oci, mcpb) — confirms validator changes don't regress
- [ ] Quick browser check of `/` after deploy to confirm UI still loads
with the new CSP
- [ ] `curl https://<host>//evil.com/` — confirm the open redirect is
closed

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 19:14:00 -04:00
Radoslav Dimitrov
c5f50e817b
fix(auth): better DNS verification errors and wrong-selector hint (#1202)
## Summary

DNS auth users have repeatedly hit two failure modes that both produce
the same opaque "Ed25519 signature verification failed" error, making
them indistinguishable to debug. This PR makes both diagnosable
end-to-end without changing the auth protocol or any successful path.

The two recurring failure modes:

1. **Wrong placement** — TXT record put under a selector
(`_mcp-auth.<domain>` or `_mcp-registry.<domain>`) instead of the apex.
DKIM-style intuition, but MCP DNS auth uses SPF-style apex placement.
See #385 (PR #388 fixed the docs but the failure recurred), #1103,
#1126.
2. **Stale apex record** — old TXT record left behind after a key
rotation, silently tried first and rejected.

## Changes

**`internal/api/handlers/v0/auth/dns.go`** — when the apex has no
`v=MCPv1` record, probe `_mcp-auth.<domain>` and
`_mcp-registry.<domain>` in parallel (2s individual timeout). If a
record is found there, return a targeted error pointing the user at the
apex. Costs zero extra DNS work in the success path.

**`internal/api/handlers/v0/auth/common.go`** — when verification fails
against records that *are* present, include short fingerprints of every
key the registry tried (`ed25519:YJLsHFhu`-style 8-char prefix). A new
`ErrSignatureMismatch` sentinel keeps structural errors (wrong signature
size, unsupported algorithm) passing through unchanged via `errors.Is`,
so existing assertions on those messages still hold.

**`docs/modelcontextprotocol-io/authentication.mdx`** — anti-pattern
`<Warning>` callout in the DNS section explicitly warning against
selector placement and stale records.

**Tests** — two new tests:
- `TestDNSAuthHandler_WrongSelectorProbe` — verifies the probe surfaces
records found at `_mcp-auth.` / `_mcp-registry.` and falls through to
the standard error when nothing is found anywhere
- `TestDNSAuthHandler_StaleKeyFingerprintInError` — verifies the
fingerprint hint appears in the failure message for #1126's exact
scenario

One existing test assertion was updated from a generic `"signature
verification failed"` substring to the more specific `"invalid signature
size for ECDSA P-384"` it should always have been asserting; the new
sentinel-based logic surfaces structural errors verbatim.

## Test plan

- [x] `make lint` — clean
- [x] `make test-unit` — all auth tests pass (existing + 2 new)
- [x] Verified existing single-key and multi-key paths preserve their
original (more specific) error messages where they were already specific
- [ ] Reviewer to sanity-check the docs callout renders well in the
Mintlify theme

## Out of scope

The publisher-side preflight DNS check (cross-link in #845) is
intentionally **not** in this PR — that belongs in the broader CLI UX
work tracked by #845.

Refs #845, #385, #1103
Fixes #1126

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 11:22:06 -04:00
Radoslav Dimitrov
0fc4606be2
Stop pinning Go to patch versions, use stable in CI (#1137)
<!-- Provide a brief summary of your changes -->

## Motivation and Context
<!-- Why is this change needed? What problem does it solve? -->
We keep bumping `go 1.26.x` across the repo every time a new Go patch
drops
## How Has This Been Tested?
<!-- Have you tested this in a real application? Which scenarios were
tested? -->
- **`go.mod` / `deploy/go.mod`**: `go 1.25.8` → `go 1.26` (minor only —
"this module needs Go 1.26")
- **All CI workflows**: `go-version-file: 'go.mod'` → `go-version:
'stable'` — CI always uses the latest stable Go, no more
  bump PRs
- **govulncheck**: removed hardcoded `go-version-input: '1.26.1'` →
`go-version-input: 'stable'`
## Breaking Changes
<!-- Will users need to update their code or configurations? -->

## Types of changes
<!-- What types of changes does your code introduce? Put an `x` in all
the boxes that apply: -->
- [ ] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing
functionality to change)
- [ ] Documentation update

## Checklist
<!-- Go over all the following points, and put an `x` in all the boxes
that apply. -->
- [ ] I have read the [MCP
Documentation](https://modelcontextprotocol.io)
- [ ] My code follows the repository's style guidelines
- [ ] New and existing tests pass locally
- [ ] I have added appropriate error handling
- [ ] I have added or updated documentation as needed

## Additional context
<!-- Add any other context, implementation notes, or design decisions
-->

Signed-off-by: Radoslav Dimitrov <radoslav@stacklok.com>
2026-04-09 11:57:21 -04:00
Preeti Dewani
bb84ea69e8
Publisher-managed "deleted" support (#893)
(Updated auto-generated PR description from @tadasant / Claude on Feb
22, 2026)

## Motivation and Context

Fixes #637

Server lifecycle management (deprecation and yanking) is a standard
requirement for package registries. Use cases like rebranding, security
vulnerabilities, and end-of-life servers demand the ability to mark
servers as deprecated or deleted, with optional messaging to guide
consumers toward alternatives.

## Changes

### New API endpoints

- **`PATCH /v0/servers/{serverName}/versions/{version}/status`** —
Update the lifecycle status of a single server version
- **`PATCH /v0/servers/{serverName}/status`** — Update the lifecycle
status of all versions of a server in a single transaction

Both endpoints accept:
```json
{
  "status": "active|deprecated|deleted",
  "statusMessage": "Optional message (max 500 chars) explaining the change"
}
```

### New CLI command

- **`mcp-publisher status <serverName> [version]`** — Update server
lifecycle status from the command line
  - `--status` (required): `active`, `deprecated`, or `deleted`
  - `--message`: Optional status message
- `--all-versions`: Apply to all versions of the server (replaces the
`<version>` argument)

### API response changes

`RegistryExtensions` now includes two new fields:
- `statusChangedAt` — Timestamp of the last status change
- `statusMessage` — Optional free-form message (e.g., deprecation
reason, migration guidance)

### List endpoint changes

- New `include_deleted` query parameter on `GET /v0/servers` — defaults
to `false`, hiding deleted servers from listings
- When `updated_since` is provided, `include_deleted` is automatically
set to `true` for incremental sync

### Database

- Migration `013_add_status_fields.sql`: Adds `status_changed_at` (NOT
NULL, initialized from `published_at`) and `status_message` columns to
the `servers` table

### Design decisions (from review discussion)

- **Dedicated status endpoints** rather than overloading the existing
edit endpoint
- **Bidirectional status transitions** — all transitions between
`active`, `deprecated`, and `deleted` are allowed
- **`statusMessage` as free-form text** — no structured `nextName` or
`alternativeUrl` fields (per maintainer feedback, to avoid complexity
around chaining, trust transfer, etc.)
- **Publish permission** grants status change access (not limited to
admin/edit)
- **Status message auto-cleared** when transitioning back to `active`
Good, now I have the full picture. Here's the addendum:

### Consumer-facing API changes

The new status fields live inside
`_meta["io.modelcontextprotocol.registry/official"]` on every
`ServerResponse`. This is the existing `RegistryExtensions` object — two
new fields are added alongside the existing ones:

**Before:**
```json
{
  "server": { "name": "com.example/my-server", "...": "..." },
  "_meta": {
    "io.modelcontextprotocol.registry/official": {
      "status": "active",
      "publishedAt": "2025-06-01T00:00:00Z",
      "updatedAt": "2025-06-15T00:00:00Z",
      "isLatest": true
    }
  }
}
```

**After:**
```json
{
  "server": { "name": "com.example/my-server", "...": "..." },
  "_meta": {
    "io.modelcontextprotocol.registry/official": {
      "status": "deprecated",
      "statusChangedAt": "2026-02-01T12:00:00Z",
      "statusMessage": "Deprecated in favor of com.example/my-server-v2",
      "publishedAt": "2025-06-01T00:00:00Z",
      "updatedAt": "2026-02-01T12:00:00Z",
      "isLatest": true
    }
  }
}
```

| New field | Type | Present when |
|-----------|------|-------------|
| `statusChangedAt` | `string` (RFC3339) | Always — initialized to
`publishedAt` for existing records |
| `statusMessage` | `string` (max 500 chars) | Only when set —
`null`/omitted for active servers (auto-cleared on transition to active)
|

These fields appear on **every endpoint that returns a
`ServerResponse`**: `GET /v0/servers`, `GET /v0/servers/{name}`, `GET
/v0/servers/{name}/versions/{version}`, and the new PATCH status
endpoints.

### List endpoint filtering

`GET /v0/servers` gains an `include_deleted` query parameter:

| Scenario | Behavior |
|----------|----------|
| No `include_deleted` param | Deleted servers hidden (default `false`)
|
| `include_deleted=true` | Deleted servers included in results |
| `updated_since` provided | `include_deleted` forced to `true` for
incremental sync |

## How Has This Been Tested?

- Unit tests for status handler (`status_test.go`) covering status
transitions, permission checks, validation errors, and the all-versions
endpoint
- Unit tests for the CLI command (`status_test.go`)
- Integration with existing server/edit handler tests
- Database layer tests for the new status update and query methods
- Tested locally with `make dev-compose`

## Types of changes

- [x] New feature (non-breaking change which adds functionality)

## Checklist

- [x] I have read the [MCP
Documentation](https://modelcontextprotocol.io)
- [x] My code follows the repository's style guidelines
- [x] New and existing tests pass locally
- [x] I have added appropriate error handling
- [x] I have added or updated documentation as needed

## Open items from review

- [ ] `include_deleted` silent override: When `updated_since` is
provided and `include_deleted=false` is explicitly passed, should return
a 400 error rather than silently overriding
([thread](https://github.com/modelcontextprotocol/registry/pull/893#discussion_r2100879360))
— agreed upon but may not yet be implemented
2026-02-27 09:00:07 -08:00
Bob Dickinson
f06d1eda8a
Added /validate endpoint and modified mcp-publish so that all validation happens there (#896)
## Motivation and Context

Per discussion with @rdimitrov - all validation now happens in a new
`/validate` endpoint. For details, see below.

## Breaking Changes

None. Validation on /publish only validates schema version and semantic
checks (manual checks) as before. We can add schema validation by
changing a flag in the called to `ValidateJSONSchema` when we're ready.

## Types of changes
- [X] New feature (non-breaking change which adds functionality)

## Checklist
- [X] I have read the [MCP
Documentation](https://modelcontextprotocol.io)
- [X] My code follows the repository's style guidelines
- [X] New and existing tests pass locally
- [X] I have added appropriate error handling
- [X] I have added or updated documentation as needed

## Additional context

## API Changes

### 1. New `/validate` Endpoint

A new public endpoint was added for validating `server.json` files
without publishing them.

**Details:**
- **Paths:** `/v0/validate` and `/v0.1/validate`
- **Method:** `POST`
- **Authentication:** None required (public endpoint)
- **Request:** JSON body containing `ServerJSON`
- **Response:** `200 OK` with `ValidationResult` containing:
  - `valid` (boolean)
- `issues` (array of validation issues with type, path, message,
severity, reference)

**Implementation:**
- New handler file: `internal/api/handlers/v0/validate.go`
- Performs comprehensive validation using `ValidationAll` option:
  - Schema version validation
  - Full schema validation
  - Semantic validation
- Always returns `200 OK` - validity is indicated in `result.Valid`
field
- Registered in router for both `/v0` and `/v0.1` prefixes

### 2. `/publish` Endpoint Validation Flow Changes

The publish endpoint now validates earlier in the request flow and
provides clearer error responses.

**Before:** Validation was mixed into `ValidatePublishRequest()` (which
also handled publisher extensions and registry ownership).

**After:**
1. Validates `ServerJSON` structure first using `ValidateServerJSON()`
(schema version + semantic validation)
2. Returns `422 Unprocessable Entity` with message: "Failed to publish
server, invalid schema: call /validate for details"
3. Only proceeds to publisher extensions/registry validation if schema
validation passes
4. No longer calls `ValidatePublishRequest()` internally for schema
validation

**Key Changes:**
- Validation happens before `CreateServer()` call
- Returns `422` on schema/semantic validation failures
- Error message directs users to `/validate` endpoint for details
- `ValidatePublishRequest()` no longer performs schema validation (added
note in comments)

### 3. `/edit` Endpoint Validation Flow Changes

Similar validation changes were applied to the edit endpoint.

**Changes:**
- Added `ValidateServerJSON()` call before server update
- Returns `422` with same error message format on validation failure
- Matches the `/publish` endpoint behavior

### 4. Validator Functions Refactoring

Updated validator functions to reflect that schema validation is now
done separately.

**Changes to `ValidatePublishRequest()`:**
- Removed internal `ValidateServerJSON()` call
- Added note: "ValidateServerJSON should be called separately before
this function"
- Now only validates publisher extensions and registry ownership

**Changes to `ValidateUpdateRequest()`:**
- Same change (removed schema validation, added note)
- Now only validates registry ownership

## CLI Changes

### 1. `validate` Command: Now Uses API Endpoint

**Before:** Performed local validation using
`validators.ValidateServerJSON()`.

**After:** Calls the `/v0/validate` endpoint on the registry.

**Details:**
- Reads `server.json` and sends it to the registry API
- Displays validation results returned by the server
- Uses the same error formatting as before
- Registry URL comes from token file (or default)
- No authentication required (public endpoint)

**Implementation:**
- Added `validateViaAPI()` function that POSTs to `/v0/validate`
- Parses `ValidationResult` response from server
- Uses shared `printValidationIssues()` for formatting

### 2. `publish` Command: Validation Only on 422 Errors

**Before:** Performed upfront local schema validation before publishing.

**After:** Attempts publish first, then validates only if server returns
422.

**Details:**
- Attempts publish immediately (no upfront validation)
- If publish returns 422 (Unprocessable Entity):
  - Calls `/v0/validate` endpoint to get detailed validation errors
  - Formats and displays them using same logic as `validate` command
  - Returns error with migration links
- For non-422 errors (401, 403, etc.), returns original error (no
validation call)

**Implementation Changes:**
- Removed upfront `runValidationAndPrintIssues()` call
- Modified `publishToRegistry()` to return HTTP status code alongside
response/error
- Added 422 detection logic that calls `validateViaAPI()`

### 3. Shared Validation Error Formatting

**Before:** `runValidationAndPrintIssues()` function handled both
validation and printing together.

**After:** Refactored to separate printing logic.

**Details:**
- Created `printValidationIssues()` function - just prints validation
issues (no validation logic)
- Removed `runValidationAndPrintIssues()` function (no longer used)
- Both `validate` and `publish` commands use `printValidationIssues()`
for consistent output

**Behavior:**
- Prints schema validation errors with migration guidance
- Prints all other validation issues with formatting
- Returns formatted error message string for schema issues

## Summary

Both CLI commands now use the `/validate` API endpoint for validation
instead of local validation. The `publish` command validates only after
receiving a 422 error response, and both commands share the same error
formatting via `printValidationIssues()`.

### Benefits

- **Centralized validation logic** - All validation happens on the
server
- **Always up-to-date** - CLI always uses current server validation
rules
- **Consistent experience** - Same validation behavior whether using CLI
or API directly
- **Better separation of concerns** - Schema validation separated from
publisher-specific validation
2026-02-04 15:52:07 +02:00
Radoslav Dimitrov
ad207f2390
Handle URL-encoded NUL bytes (%00) in URL path and query (#877)
<!-- Provide a brief summary of your changes -->

## Motivation and Context
<!-- Why is this change needed? What problem does it solve? -->
Follow up on #869 

## How Has This Been Tested?
<!-- Have you tested this in a real application? Which scenarios were
tested? -->

## Breaking Changes
<!-- Will users need to update their code or configurations? -->

## Types of changes
<!-- What types of changes does your code introduce? Put an `x` in all
the boxes that apply: -->
- [ ] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing
functionality to change)
- [ ] Documentation update

## Checklist
<!-- Go over all the following points, and put an `x` in all the boxes
that apply. -->
- [ ] I have read the [MCP
Documentation](https://modelcontextprotocol.io)
- [ ] My code follows the repository's style guidelines
- [ ] New and existing tests pass locally
- [ ] I have added appropriate error handling
- [ ] I have added or updated documentation as needed

## Additional context
<!-- Add any other context, implementation notes, or design decisions
-->

Signed-off-by: Radoslav Dimitrov <radoslav@stacklok.com>
2026-01-13 11:19:49 +02:00
lif
ab6a5a6b6d
fix(api): reject requests with NUL bytes in URL (#869)
## Summary
- Added `NulByteValidationMiddleware` to validate incoming requests
- Returns 400 Bad Request when NUL bytes (%00) detected in URL path or
query
- Prevents PostgreSQL encoding errors from being exposed as 500 errors

## Before
```
GET /v0.1/servers?cursor=%00
→ 500 Internal Server Error
→ "invalid byte sequence for encoding UTF8: 0x00"
```

## After
```
GET /v0.1/servers?cursor=%00
→ 400 Bad Request
→ "Invalid request: query parameters contain null bytes"
```

Fixes #862

Signed-off-by: majiayu000 <1835304752@qq.com>
Co-authored-by: Radoslav Dimitrov <radoslav@stacklok.com>
2026-01-03 19:14:44 +02:00
Benedikt Reinartz
4338ec157a
Default to the local registry in the UI (#799)
<!-- Provide a brief summary of your changes -->
Change the UI default to the locally hosted registry.

## Motivation and Context
<!-- Why is this change needed? What problem does it solve? -->
Was quite confusing at first :)

## Types of changes
<!-- What types of changes does your code introduce? Put an `x` in all
the boxes that apply: -->
- [ ] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing
functionality to change)
- [ ] Documentation update

## Checklist
<!-- Go over all the following points, and put an `x` in all the boxes
that apply. -->
- [x] I have read the [MCP
Documentation](https://modelcontextprotocol.io)
- [x] My code follows the repository's style guidelines
- [x] New and existing tests pass locally
- [x] I have added appropriate error handling
- [ ] I have added or updated documentation as needed
2025-12-16 21:59:39 +00:00
Radoslav Dimitrov
c54a5ac277
Ensure tests are failing CI (#840) 2025-12-11 09:56:57 +02:00
adam jones
dcb5a6f696
Add 5s timeout to DNS auth lookup to prevent resource exhaustion (#825)
The DNS auth endpoint was vulnerable to resource exhaustion if an
attacker controlled a domain with a slow/non-responding authoritative
DNS server. Without a timeout, handler goroutines would block on DNS
resolution indefinitely, allowing an attacker to pile up goroutines
until OOM.

Production is protected by NGINX ingress's default 60s proxy timeout,
but this adds defense-in-depth with a tighter 5s bound.
2025-12-10 16:22:27 +00:00
adam jones
e7de7541c7
improve: Add hint about making GitHub org membership public in error messages (#772)
When users try to publish to a GitHub organization but don't have
permission, the error message now includes a helpful hint suggesting
they may need to make their organization membership public in GitHub
settings.

This addresses the issue where users couldn't understand why they were
unable to publish servers from organization repositories, even when they
were members of the organization.

Fixes #398

Generated with [Claude Code](https://claude.ai/code)

---------

Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
Co-authored-by: adam jones <domdomegg@users.noreply.github.com>
2025-11-17 13:59:19 +00:00
adam jones
8cf317af68
Add simple web UI for browsing the registry (#757)
## Summary

Adds a clean, minimal web interface at `/` for browsing MCP servers in
the registry. Built as a single self-contained HTML file embedded in the
Go binary.

## Features

- Responsive grid layout (1/2/3 columns based on screen size)
- Search servers by name
- Filter to show only latest versions (default on)
- "Recently Updated" section for servers updated in the last hour
- Expandable cards with full server metadata and JSON viewer
- Debug modal to switch between production/staging/local/custom API
endpoints
- Full URL parameter support and browser history navigation

## Implementation

- Single HTML file (~400 lines) with Tailwind CSS via CDN
- Zero build dependencies
- Embedded using `//go:embed` 
- Served directly from router at root path
- Uses stable v0.1 API endpoints

Fixes #99

---

AI disclosure: this is 99% vibed with Claude. It's intended as a v0 to
get something up and working, and then maybe at a later date we can
replace it with something more real.
2025-11-10 12:37:36 +00:00
Radoslav Dimitrov
ef4a60505e
Make Repository field properly optional in server.json (#717)
<!-- Provide a brief summary of your changes -->

## Motivation and Context
<!-- Why is this change needed? What problem does it solve? -->
The Repository field in ServerJSON was not properly omitting empty
values
when marshaling to JSON. Even when a server had no repository
configured,
the JSON would include `"repository": {"url": "", "source": ""}` due to:

1. Repository struct fields (URL, Source) lacking omitempty tags
2. Repository being a value type instead of pointer in ServerJSON

This caused validation warnings for servers without repository URLs.

Changes:
- Add omitempty tags to Repository.URL and Repository.Source fields
- Change ServerJSON.Repository from model.Repository to
*model.Repository
- Update validateRepository to handle nil pointers
- Update all code creating Repository instances to use pointers

Now, servers without repository information correctly omit the field
entirely from JSON output, eliminating spurious validation warnings.
## How Has This Been Tested?
<!-- Have you tested this in a real application? Which scenarios were
tested? -->

## Breaking Changes
<!-- Will users need to update their code or configurations? -->

## Types of changes
<!-- What types of changes does your code introduce? Put an `x` in all
the boxes that apply: -->
- [ ] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing
functionality to change)
- [ ] Documentation update

## Checklist
<!-- Go over all the following points, and put an `x` in all the boxes
that apply. -->
- [ ] I have read the [MCP
Documentation](https://modelcontextprotocol.io)
- [ ] My code follows the repository's style guidelines
- [ ] New and existing tests pass locally
- [ ] I have added appropriate error handling
- [ ] I have added or updated documentation as needed

## Additional context
<!-- Add any other context, implementation notes, or design decisions
-->

Signed-off-by: Radoslav Dimitrov <radoslav@stacklok.com>
2025-10-29 13:23:47 +00:00
Terry Nederveld
73d2174cec
feat: add CORS support to registry API (#711)
Fixes #710

This PR adds CORS middleware to enable browser-based clients and web
applications to access the registry API.

## Changes

- Add CORSMiddleware in internal/api/cors.go to handle preflight OPTIONS
requests and inject CORS headers
- Integrate CORS middleware into the server middleware stack
- Add CORS configuration options (CORS_ENABLED and CORS_ALLOWED_ORIGIN)

## Testing

- All existing tests pass
- Manual testing confirms CORS headers are present in responses
- OPTIONS preflight requests now return 204 with proper headers

## Configuration

New environment variables:
- MCP_REGISTRY_CORS_ENABLED (default: true)
- MCP_REGISTRY_CORS_ALLOWED_ORIGIN (default: *)
2025-10-27 18:07:13 +00:00
Joel Verhagen
6c0f085708
Add support for ECDSA P-384 (#685)
## Motivation and Context

See
https://github.com/modelcontextprotocol/registry/issues/482#issuecomment-3436993157
for full details.

This adds ECDSA P-384 (a popular NIST approved curve) in addition to
Ed25519.

## How Has This Been Tested?

I have tested with a local deployment against `mcp.joelverhagen.com`.
Also, I have added significant tests.

## Breaking Changes

None. (hopefully)

## Types of changes
<!-- What types of changes does your code introduce? Put an `x` in all
the boxes that apply: -->
- [ ] Bug fix (non-breaking change which fixes an issue)
- [x] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing
functionality to change)
- [x] Documentation update

## Checklist
<!-- Go over all the following points, and put an `x` in all the boxes
that apply. -->
- [x] I have read the [MCP
Documentation](https://modelcontextprotocol.io)
- [x] My code follows the repository's style guidelines
- [x] New and existing tests pass locally
- [x] I have added appropriate error handling
- [x] I have added or updated documentation as needed

## Additional context
<!-- Add any other context, implementation notes, or design decisions
-->

@domdomegg and I spoke a bit about this on Discord:
https://discord.com/channels/1358869848138059966/1369487942862504016/1423710918180606052

---------

Co-authored-by: adam jones <domdomegg+git@gmail.com>
2025-10-24 12:02:33 -06:00
adam jones
f870a4c000
Improve golang type documentation and examples (#631)
Enhanced Go struct tags throughout `pkg/api/v0/types.go` and
`pkg/model/types.go` to better align with the OpenAPI specification.

## Changes

- Added comprehensive `doc` tags with detailed field descriptions
matching OpenAPI
- Added `example` tags showing valid values for fields (e.g.,
`"io.github.user/weather"`, `"npx"`)
- Added `format` tags (`uri`, `date-time`) for better type specification
- Added `pattern` and `enum` tags for validation constraints
- Added `placeholder` field to `Input` struct matching OpenAPI spec
- Removed redundant comments in favor of inline struct tags

These changes improve code clarity and make the types more
self-documenting, while maintaining compatibility with the OpenAPI
schema definitions in `docs/reference/api/openapi.yaml`.

---------

Signed-off-by: Radoslav Dimitrov <radoslav@stacklok.com>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Radoslav Dimitrov <radoslav@stacklok.com>
2025-10-16 21:55:52 +01:00
Radoslav Dimitrov
2a80690063
Introduce a /version API (#667)
<!-- Provide a brief summary of your changes -->

## Motivation and Context
<!-- Why is this change needed? What problem does it solve? -->
The following PR introduces a `/version` API that serves the build
details along with the version of the API server being served. The
`/version` endpoint allows users to programmatically query which exact
version of the registry is running in production for debugging.
~~auditing, and compatibility checks~~.

## How Has This Been Tested?
<!-- Have you tested this in a real application? Which scenarios were
tested? -->

## Breaking Changes
<!-- Will users need to update their code or configurations? -->

## Types of changes
<!-- What types of changes does your code introduce? Put an `x` in all
the boxes that apply: -->
- [ ] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing
functionality to change)
- [ ] Documentation update

## Checklist
<!-- Go over all the following points, and put an `x` in all the boxes
that apply. -->
- [ ] I have read the [MCP
Documentation](https://modelcontextprotocol.io)
- [ ] My code follows the repository's style guidelines
- [ ] New and existing tests pass locally
- [ ] I have added appropriate error handling
- [ ] I have added or updated documentation as needed

## Additional context
<!-- Add any other context, implementation notes, or design decisions
-->

Signed-off-by: Radoslav Dimitrov <radoslav@stacklok.com>
2025-10-15 17:24:46 +03:00
Radoslav Dimitrov
3592b2cc0c
Add the rest of the tag descriptions (#661)
<!-- Provide a brief summary of your changes -->

## Motivation and Context
<!-- Why is this change needed? What problem does it solve? -->

## How Has This Been Tested?
<!-- Have you tested this in a real application? Which scenarios were
tested? -->

## Breaking Changes
<!-- Will users need to update their code or configurations? -->

## Types of changes
<!-- What types of changes does your code introduce? Put an `x` in all
the boxes that apply: -->
- [ ] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing
functionality to change)
- [ ] Documentation update

## Checklist
<!-- Go over all the following points, and put an `x` in all the boxes
that apply. -->
- [ ] I have read the [MCP
Documentation](https://modelcontextprotocol.io)
- [ ] My code follows the repository's style guidelines
- [ ] New and existing tests pass locally
- [ ] I have added appropriate error handling
- [ ] I have added or updated documentation as needed

## Additional context
<!-- Add any other context, implementation notes, or design decisions
-->

Signed-off-by: Radoslav Dimitrov <radoslav@stacklok.com>
2025-10-14 15:28:10 +03:00
Radoslav Dimitrov
087d8085b7
Replace / with - for operation IDs (#660)
<!-- Provide a brief summary of your changes -->

## Motivation and Context
<!-- Why is this change needed? What problem does it solve? -->
Follow up from #658 addressing the feedback from @tadasant 

## How Has This Been Tested?
<!-- Have you tested this in a real application? Which scenarios were
tested? -->

## Breaking Changes
<!-- Will users need to update their code or configurations? -->

## Types of changes
<!-- What types of changes does your code introduce? Put an `x` in all
the boxes that apply: -->
- [ ] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing
functionality to change)
- [ ] Documentation update

## Checklist
<!-- Go over all the following points, and put an `x` in all the boxes
that apply. -->
- [ ] I have read the [MCP
Documentation](https://modelcontextprotocol.io)
- [ ] My code follows the repository's style guidelines
- [ ] New and existing tests pass locally
- [ ] I have added appropriate error handling
- [ ] I have added or updated documentation as needed

## Additional context
<!-- Add any other context, implementation notes, or design decisions
-->

Signed-off-by: Radoslav Dimitrov <radoslav@stacklok.com>
2025-10-14 15:26:43 +03:00
Aleksandr Beshkenadze
cce5e5df46
feat: add OpenAPI tags for improved SDK generation and API organization (#655)
## Summary
Adds OpenAPI tags to improve SDK generation and API documentation
organization, as discussed in #644.

## Changes
- **Go Code**: Define tag metadata (name + description) in
`internal/api/router/router.go` using Huma's `api.OpenAPI().Tags`
- **Reference Spec**: Update `docs/reference/api/openapi.yaml` to match
the generated spec
- **Tags Added**: servers, publish, auth, admin, health, ping

## Benefits
- ✅ Improved SDK generation with proper service grouping (e.g.,
`serversService`, `publishService`)
- ✅ Better API documentation organization in Scalar UI, Swagger UI, and
other tools
- ✅ Code-first approach ensures reference spec stays in sync via
compliance tests

## Testing
- ✅ Compliance test passes: `go test -v ./internal/api -run
TestOpenAPIEndpointCompliance`
- ✅ All 4 reference endpoints verified as implemented
- ✅ TypeScript SDK regenerated successfully with proper tag-based
grouping

## Related
Closes #644
2025-10-14 14:30:36 +03:00
Radoslav Dimitrov
1a7cd97099
Introduce v0.1 version of the API (#658)
<!-- Provide a brief summary of your changes -->

## Motivation and Context
<!-- Why is this change needed? What problem does it solve? -->
The following PR adds a new v0.1 API version. 

The goal is to mark v0.1 as a "freeze" point while keeping v0 as the
moving target for new features so integrators can start building on top
of something stable(r).

Details:
* This change is quite simple - we re-register the same handlers twice,
once as `v0` and once as `v0.1`
* Going forward, we'll keep v0.1 frozen while we add changes either on
top of both (non-breaking) or we'll have to branch if breaking changes
are necessary
* We all acknowledge this is not exactly best-practice, but it gives us
a way to gather feedback and allow clients to start integrating with the
API without breaking them (**note** this implies a follow up PR that
would branch the handlers used in v0.1).
* For integrators/clients: please provide your feedback - what works
well, what doesn't so we can take this into account going towards GA.

## How Has This Been Tested?
<!-- Have you tested this in a real application? Which scenarios were
tested? -->

## Breaking Changes
<!-- Will users need to update their code or configurations? -->
No, this just adds a new API version alongside the existing one.

## Types of changes
<!-- What types of changes does your code introduce? Put an `x` in all
the boxes that apply: -->
- [ ] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing
functionality to change)
- [ ] Documentation update

## Checklist
<!-- Go over all the following points, and put an `x` in all the boxes
that apply. -->
- [ ] I have read the [MCP
Documentation](https://modelcontextprotocol.io)
- [ ] My code follows the repository's style guidelines
- [ ] New and existing tests pass locally
- [ ] I have added appropriate error handling
- [ ] I have added or updated documentation as needed

## Additional context
<!-- Add any other context, implementation notes, or design decisions
-->
Fixes: #649

Signed-off-by: Radoslav Dimitrov <radoslav@stacklok.com>
2025-10-14 11:04:48 +03:00
Radoslav Dimitrov
632cbce815
Refactor package types to use dedicated schema types per package type (NPM, PyPI, NuGet, OCI, MCPB) (#634) 2025-10-11 10:53:45 +03:00
adam jones
4f453ff2bd
Drop GET /v0/servers/{serverName} endpoint (#618)
Remove redundant endpoint as suggested in #617. Clients should now use
`GET /v0/servers/{serverName}/versions/latest` instead (using `latest`
as a special version value in the existing endpoint).

This simplifies the API surface and reduces implementation burden for
subregistries, while maintaining a clear migration path for consumers.

## Changes
- Removed `GET /v0/servers/{serverName}` endpoint
- Updated `GET /v0/servers/{serverName}/versions/{version}` to accept
`latest` as special version
- Updated all tests to use new endpoint pattern
- Updated documentation and OpenAPI spec
- Added breaking change notice to CHANGELOG

Fixes #617

Co-authored-by: Claude <noreply@anthropic.com>
2025-10-07 13:56:45 +03:00
Radoslav Dimitrov
44950c23bb
Enforce schema validation on the publish and edit API level (#598)
<!-- Provide a brief summary of your changes -->

## Motivation and Context
<!-- Why is this change needed? What problem does it solve? -->
The following PR:
* Enforces schema validation on the API level (requests without schema
or older schema will fail)
* Updates the unit tests and examples to match that

## How Has This Been Tested?
<!-- Have you tested this in a real application? Which scenarios were
tested? -->

## Breaking Changes
<!-- Will users need to update their code or configurations? -->
**Note this can be considered breaking if clients have not included it
before.**

## Types of changes
<!-- What types of changes does your code introduce? Put an `x` in all
the boxes that apply: -->
- [ ] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing
functionality to change)
- [ ] Documentation update

## Checklist
<!-- Go over all the following points, and put an `x` in all the boxes
that apply. -->
- [ ] I have read the [MCP
Documentation](https://modelcontextprotocol.io)
- [ ] My code follows the repository's style guidelines
- [ ] New and existing tests pass locally
- [ ] I have added appropriate error handling
- [ ] I have added or updated documentation as needed

## Additional context
<!-- Add any other context, implementation notes, or design decisions
-->

---------

Signed-off-by: Radoslav Dimitrov <radoslav@stacklok.com>
2025-10-05 13:57:10 +01:00
Tadas Antanavicius
8bd13d5ed7
Apply gofmt formatting to all Go files (#588)
## Summary
- Applied `gofmt -s -w` across the entire codebase to ensure consistent
Go formatting
- This will trigger a deployment with minimal code changes

## Test plan
- [x] Pre-commit hooks pass
- [x] No functional changes, formatting only

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude <noreply@anthropic.com>
2025-09-30 17:32:22 +03:00
tadasant
a0bc09dbe1 Revert "docs: clarify release deployment process and enable manual deploy trigger"
This reverts commit c40374c280.
2025-09-29 18:57:40 -07:00
tadasant
c40374c280 docs: clarify release deployment process and enable manual deploy trigger
- Add workflow_dispatch to deploy.yml for manual deployment triggers
- Document that releases don't auto-deploy to production
- Add instructions for deploying releases to production
- Add verification steps for deployment

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-29 18:54:56 -07:00
Joel Verhagen
df018fc43b
Share more code between DNS and HTTP auth flows (#552)
<!-- Provide a brief summary of your changes -->

## Motivation and Context

Currently the `dns` and `http` based auth flows share code well in the
publisher tool. However there is a lot of duplication in the auth
endpoint. This on it's own is not a problem but it does make
enhancements to the proof record (the `v=MCPv1; ...` string) harder to
keep in sync.

This PR adds a `common.go` file to share logic between the two.

This supports work I am prototyping for
https://github.com/modelcontextprotocol/registry/issues/482.

## How Has This Been Tested?

Added new test cases for the permissions. Ran them before and after the
refactoring to ensure no behavior change.
Ran existing UTs.
Ran the integration tests.
Manually exercised the flows locally.

## Breaking Changes

None.

## Types of changes
<!-- What types of changes does your code introduce? Put an `x` in all
the boxes that apply: -->
- [ ] Bug fix (non-breaking change which fixes an issue)
- [x] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing
functionality to change)
- [ ] Documentation update

## Checklist
<!-- Go over all the following points, and put an `x` in all the boxes
that apply. -->
- [x] I have read the [MCP
Documentation](https://modelcontextprotocol.io)
- [x] My code follows the repository's style guidelines
- [x] New and existing tests pass locally
- [x] I have added appropriate error handling
- [ ] I have added or updated documentation as needed

## Additional context
<!-- Add any other context, implementation notes, or design decisions
-->
2025-09-29 23:43:27 +01:00
Radoslav Dimitrov
3762542ddc
Ensure registry API implements the reference openapi schema (#568)
<!-- Provide a brief summary of your changes -->

## Motivation and Context
<!-- Why is this change needed? What problem does it solve? -->
The following PR adds a simple unit test to ensure the served API
handlers are a superset of the reference OpenAPI schema.

## How Has This Been Tested?
<!-- Have you tested this in a real application? Which scenarios were
tested? -->

## Breaking Changes
<!-- Will users need to update their code or configurations? -->

## Types of changes
<!-- What types of changes does your code introduce? Put an `x` in all
the boxes that apply: -->
- [ ] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing
functionality to change)
- [ ] Documentation update

## Checklist
<!-- Go over all the following points, and put an `x` in all the boxes
that apply. -->
- [ ] I have read the [MCP
Documentation](https://modelcontextprotocol.io)
- [ ] My code follows the repository's style guidelines
- [ ] New and existing tests pass locally
- [ ] I have added appropriate error handling
- [ ] I have added or updated documentation as needed

## Additional context
<!-- Add any other context, implementation notes, or design decisions
-->
Fixes: https://github.com/modelcontextprotocol/registry/issues/567

---------

Signed-off-by: Radoslav Dimitrov <radoslav@stacklok.com>
2025-09-29 13:53:54 -07:00
Radoslav Dimitrov
b21c6f564d
Changes on the server.json format and the API (#562) 2025-09-29 23:30:35 +03:00
Ville Vesilehto
76365a61e3
fix: use io.LimitReader in key fetcher (#558)
## Motivation and Context

Replace server-only `http.MaxBytesReader(nil, ...)` with
`io.LimitReader` to cap the fetched body to 4KB in key fetcher. This
avoids passing a `nil` ResponseWriter and aligns with Go idioms. If more
than 4KB is received, return a clear error. Previously `MaxBytesReader`
returned a misleading `http: request body too large` error, which is
server-oriented. With this change the error becomes `HTTP auth key
response too large`.

## How Has This Been Tested?
<!-- Have you tested this in a real application? Which scenarios were
tested? -->

Add tests covering success, oversized bodies, connection failure,
non-200 status, and read failures. This covers almost all lines of
`FetchKey` method of `DefaultHTTPKeyFetcher`. For tests, I also added a
new initialiser `NewDefaultHTTPKeyFetcherWithClient` so that the HTTP
client could be modified for test use.

Tests use `httptest.NewTLSServer` plus a helper to construct a client
pinned to the test server.

`auth` package test coverage increased from 56.8% to 59.3%.

## Breaking Changes
<!-- Will users need to update their code or configurations? -->

None.

## Types of changes
<!-- What types of changes does your code introduce? Put an `x` in all
the boxes that apply: -->
- [x] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing
functionality to change)
- [ ] Documentation update

## Checklist
<!-- Go over all the following points, and put an `x` in all the boxes
that apply. -->
- [x] I have read the [MCP
Documentation](https://modelcontextprotocol.io)
- [x] My code follows the repository's style guidelines
- [x] New and existing tests pass locally
- [x] I have added appropriate error handling
- [ ] I have added or updated documentation as needed

## Additional context
<!-- Add any other context, implementation notes, or design decisions
-->

Signed-off-by: Ville Vesilehto <ville@vesilehto.fi>
2025-09-26 11:24:07 +01:00
Radoslav Dimitrov
ddcc586842
Remove the in-memory database (#537)
<!-- Provide a brief summary of your changes -->

## Motivation and Context
<!-- Why is this change needed? What problem does it solve? -->
The follow PR removes the in-memory database option from the registry.

We had a chat about this with @domdomegg and although it is supposed to
be convenient, it isn't that useful in reality. We want to test and
develop the registry in a setup that is closer to the environment it is
supposed to be run. One of the main reasons are:
* We don't use it anywhere else besides unit testing and potentially
local development. Instead we should use postgresql for reliable results
(in-memory was actually giving false positive results)
* We have to keep maintaining it and make sure it mimics the behaviour
we have for postgresql which is not yielding a lot of value

## How Has This Been Tested?
<!-- Have you tested this in a real application? Which scenarios were
tested? -->
Yes, I've updated the test unit make target to run in the postgresql
environment and now we can see the race condition issue happening.

## Breaking Changes
<!-- Will users need to update their code or configurations? -->
No

## Types of changes
<!-- What types of changes does your code introduce? Put an `x` in all
the boxes that apply: -->
- [ ] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing
functionality to change)
- [ ] Documentation update

## Checklist
<!-- Go over all the following points, and put an `x` in all the boxes
that apply. -->
- [ ] I have read the [MCP
Documentation](https://modelcontextprotocol.io)
- [ ] My code follows the repository's style guidelines
- [ ] New and existing tests pass locally
- [ ] I have added appropriate error handling
- [ ] I have added or updated documentation as needed

## Additional context
<!-- Add any other context, implementation notes, or design decisions
-->

---------

Signed-off-by: Radoslav Dimitrov <radoslav@stacklok.com>
2025-09-23 16:40:20 +01:00
adam jones
5746e7e99b
Remove OIDC authorization flow to fix data race vulnerability (#526)
## Summary

Fixes a concurrent map access vulnerability in the OIDC handler that
could cause remote unauthenticated DoS.

## Changes

- Removed OIDC authorization flow endpoints ( and )
- Removed the unsynchronized  map that caused the data race
- Kept only the OIDC token exchange endpoint ()
- Simplified  by removing OAuth2 flow dependencies

The map was being accessed concurrently by the start (write) and
callback (read/delete) endpoints without synchronization, causing
runtime panics. Since the token exchange flow doesn't require session
state, removing the authorization flow eliminates the vulnerability
entirely.
2025-09-22 13:41:32 +01:00
Tadas Antanavicius
56e8b4879b
Introduce serverId, rename id to versionId (#465)
Wielded Claude Code to do this; across many iterations and have reviewed
all the code + largely rewritten the PR commentary here.

This PR implements the server ID consistency changes from
[#396](https://github.com/modelcontextprotocol/registry/issues/396). Our
goal is to:
- Rename `id` to `versionId` to make it clear that it is the ID for a
specific version, not a server
- Introduce `serverId` as a net new concept. This will be 1:1 with
`name`. While not strictly necessary (we could just use name as a PK),
the convention of having a UUID as an ID on our objects is just a
helpful subjective architecture decision. Plus also leaves open the door
to reconsidering the "should we allow renames" question.

## Key Changes

### 🗄️ Database Schema Changes
- **Renamed**: `id` column → `version_id` (unique identifier for each
server version)

### 📋 Metadata Structure Updates
- **JSON Schema**: Updated
`_meta.io.modelcontextprotocol.registry/official` to include:
  - `serverId`: Consistent ID across all versions of a server
  - `versionId`: Unique ID for this specific version (renamed from `id`)

### 🔧 API Endpoint Updates
- **New endpoint structure**: Changed from version-ID-based to
server-ID-based URLs:
  - `GET /v0/servers/{server_id}` - Get latest version of server
  - `GET /v0/servers/{server_id}?version=X.X.X` - Get specific version  
- `GET /v0/servers/{server_id}/versions` - List all versions of a server
  - `PUT /v0/servers/{server_id}?version=X.X.X` - Edit specific version
- **Consistent URL patterns**: All server endpoints now use `server_id`
as the primary identifier
 
### Database Migration Strategy
The migration (`005_add_server_id_rename_version_id.sql`):
1. Adds new `version_id` column  
2. Generates deterministic UUIDs for servers with the same name
3. Updates JSON metadata to add `serverId` and rename `id` to
`versionId`
4. Copies existing `id` values to `version_id` column
5. Drops old `id` column and makes `version_id` the new primary key

## Testing

Besides of course automated tests passing, I:
1. Set up my local db with the pre-005-migration state, imported all the
production data
2. Ran the migration
3. Spot checked some diffs of the data that resulted --

```json
// before
{
  "name": "io.github.ruvnet/ruv-swarm",
  "_meta": {
    "io.modelcontextprotocol.registry/official": {
      "id": "0a9cda7f-2b73-4839-844b-6582f335de42",
      "is_latest": false,
      "updated_at": "2025-09-10T17:21:34.92137012Z",
      "published_at": "2025-09-10T17:15:32.69690433Z"
    }
  },
  "version": "1.0.18",
  "repository": {
    "url": "",
    "source": ""
  },
  "description": "Neural network swarm orchestration with WebAssembly acceleration and MCP integration"
}

// after
{
  "name": "io.github.ruvnet/ruv-swarm",
  "_meta": {
    "io.modelcontextprotocol.registry/official": {
      "serverId": "e8538952-96af-4a33-9e31-7fbed012521e",
      "is_latest": false,
      "versionId": "0a9cda7f-2b73-4839-844b-6582f335de42",
      "updated_at": "2025-09-10T17:21:34.92137012Z",
      "published_at": "2025-09-10T17:15:32.69690433Z"
    }
  },
  "version": "1.0.18",
  "repository": {
    "url": "",
    "source": ""
  },
  "description": "Neural network swarm orchestration with WebAssembly acceleration and MCP integration"
}

```

Servers that have multiple versions stayed consistent in their
`serverId`:

```json
// First version
{
  "name": "app.getdialer/dialer",
  "_meta": {
    "io.modelcontextprotocol.registry/official": {
      "serverId": "80f08bdb-f3e4-4f19-b072-1676ae49b663",
      "is_latest": false,
      "versionId": "04f25b17-331d-48d7-b08b-37baece208a7",
      "updated_at": "2025-09-09T00:16:49.162620072Z",
      "published_at": "2025-09-08T23:47:09.452137971Z"
    }
  },
  "status": "active",
  "$schema": "https://static.modelcontextprotocol.io/schemas/2025-07-09/server.schema.json",
  "remotes": [
    {
      "url": "https://getdialer.app/mcp",
      "type": "streamable-http"
    },
    {
      "url": "https://getdialer.app/sse",
      "type": "sse"
    }
  ],
  "version": "1.0.0",
  "repository": {
    "url": "",
    "source": ""
  },
  "description": "An MCP server that provides your you make outbound phone calls using your own phone number"
}

// Second version
{
  "name": "app.getdialer/dialer",
  "_meta": {
    "io.modelcontextprotocol.registry/official": {
      "serverId": "80f08bdb-f3e4-4f19-b072-1676ae49b663",
      "is_latest": true,
      "versionId": "faa26cb9-3aad-415e-aece-f715fff5fe5e",
      "updated_at": "2025-09-09T00:16:49.107842808Z",
      "published_at": "2025-09-09T00:16:49.107842808Z"
    }
  },
  "status": "active",
  "$schema": "https://static.modelcontextprotocol.io/schemas/2025-07-09/server.schema.json",
  "remotes": [
    {
      "url": "https://getdialer.app/mcp",
      "type": "streamable-http"
    },
    {
      "url": "https://getdialer.app/sse",
      "type": "sse"
    }
  ],
  "version": "1.0.1",
  "repository": {
    "url": "",
    "source": ""
  },
  "description": "An MCP server that provides lets you make outbound phone calls using your own phone number"
}
```

And I also tried publishing a test server anew; looked good:

```bash
⏺ Bash(curl -s "http://localhost:8080/v0/servers/102dc4e4-6ca8-40e6-aa11-cd45763d3352" | jq '.')
  ⎿  {                                                                                             
       "$schema": "https://static.modelcontextprotocol.io/schemas/2025-07-09/server.schema.json",
       "name": "io.modelcontextprotocol.anonymous/test-claude-migration-server",
       "description": "A test server to verify the new migration and API endpoints work correctly",
       "status": "active",
       "repository": {
         "url": "https://github.com/example/test-claude-migration-server",
         "source": "github",
         "id": "example/test-claude-migration-server"
       },
       "version": "1.0.0",
       "_meta": {
         "io.modelcontextprotocol.registry/official": {
           "serverId": "102dc4e4-6ca8-40e6-aa11-cd45763d3352",
           "versionId": "48c9cd55-7bc4-4ac2-896e-59e81eb653e4",
           "published_at": "2025-09-12T18:33:23.264203-07:00",
           "updated_at": "2025-09-12T18:33:23.264203-07:00",
           "is_latest": true
         }
       }
     }

⏺ Bash(curl -s "http://localhost:8080/v0/servers/102dc4e4-6ca8-40e6-aa11-cd45763d3352/versions" | jq '.')
  ⎿  {                                                                                                 
       "servers": [
         {
           "$schema": "https://static.modelcontextprotocol.io/schemas/2025-07-09/server.schema.json",
           "name": "io.modelcontextprotocol.anonymous/test-claude-migration-server",
           "description": "A test server to verify the new migration and API endpoints work correctly",
           "status": "active",
           "repository": {
             "url": "https://github.com/example/test-claude-migration-server",
             "source": "github",
             "id": "example/test-claude-migration-server"
           },
           "version": "1.0.0",
           "_meta": {
             "io.modelcontextprotocol.registry/official": {
               "serverId": "102dc4e4-6ca8-40e6-aa11-cd45763d3352",
               "versionId": "48c9cd55-7bc4-4ac2-896e-59e81eb653e4",
               "published_at": "2025-09-12T18:33:23.264203-07:00",
               "updated_at": "2025-09-12T18:33:23.264203-07:00",
               "is_latest": true
             }
           }
         }
       ],
       "metadata": {
         "count": 1
       }
     }
```

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Preeti Dewani <preetidewani1990@gmail.com>
Co-authored-by: adam jones <adamj+git@anthropic.com>
Co-authored-by: Adam Jones <adamj@anthropic.com>
2025-09-21 15:00:09 +03:00
Ossama Lafhel
63cf08efad
fix: validate server names to allow only single slash (#471) (#476)
## Summary
Added validation to reject server names with multiple slashes, ensuring
consistency between JSON schema pattern and API validation.

  - Count slashes in parseServerName() and reject if > 1
  - Add ErrMultipleSlashesInServerName error constant
  - Add unit tests in validators_test.go
  - Add integration test in publish_test.go

  ## Motivation and Context
Fixes #471. The JSON schema regex pattern `^[^/]+/[^/]+$` only allows a
single slash in server names, but the API validation was not enforcing
this constraint. This inconsistency
allowed invalid server names like `com.example/server/path` to be
published.

  ## How Has This Been Tested?
  - Unit tests added to verify slash validation logic
- Integration tests added to verify the publish endpoint rejects
multi-slash names
- Tested various edge cases: trailing slashes, consecutive slashes,
URL-like paths
  - All existing tests pass

  ## Breaking Changes
None. This change only adds validation to reject previously invalid
server names that shouldn't have been accepted.


## Types of changes
- [x] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing
functionality to change)
- [ ] Documentation update

## Checklist
- [x] I have read the [MCP
Documentation](https://modelcontextprotocol.io)
- [x] My code follows the repository's style guidelines
- [x] New and existing tests pass locally
- [x] I have added appropriate error handling
- [x] I have added or updated documentation as needed
 
## Additional context
- The fix ensures server names follow the intended format:
`namespace/name` (e.g., `com.example/my-server`)
- This validation aligns the API behavior with the existing JSON schema
regex pattern
- Test coverage includes edge cases like `com.example//server`,
`com.example/server/`, and `a/b/c/d/e/f`
- The error message clearly indicates the issue: "server name cannot
contain multiple slashes"
2025-09-15 12:05:51 +01:00
adam jones
256c730198
Add test for filtered servers (#426)
Claude wrote this extra test while I was doing some other work
experimenting with stuff, seems useful to have!

Also deleted the unnecessary expectedServers, because we basically never
assert on it.
2025-09-11 07:51:57 +00:00
adam jones
6bb62d160e
fix: add HTTP middleware to redirect trailing slash URLs to canonical form (#420)
Resolves #419 where URLs with trailing slashes like `/v0/servers/` would
return empty responses instead of proper data.

**Changes**:
- Added TrailingSlashMiddleware that redirects trailing slash URLs with
301 status
- Preserves root path ('/') without redirecting
- Uses HTTP 301 Moved Permanently to indicate canonical form
- Added comprehensive test coverage for the middleware

This follows REST API best practices and is consistent with how major Go
web frameworks handle trailing slash normalization.

Generated with [Claude Code](https://claude.ai/code)

---------

Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
Co-authored-by: adam jones <domdomegg@users.noreply.github.com>
2025-09-10 13:14:59 -07:00
adam jones
62f4453f37
feat: Add proper 404 error handling with helpful suggestions (#421)
Fixes #418

This PR addresses the issue where invalid API endpoints (like
`/servers`) were returning blank 200 responses instead of proper 404
errors.

## Changes

- Added catch-all 404 handler for unmatched routes
- Return RFC 7807 compliant JSON error responses
- Provide smart suggestions for common mistakes:
  - `/servers` → suggest `/v0/servers`
  - `/health` → suggest `/v0/health`
  - `/ping` → suggest `/v0/ping`
  - `/v1`, `/v2` → inform only v0 is supported
- Fixes blank 200 responses for invalid endpoints
- Improves API discoverability and debugging experience

## Testing

- ✅ Unit tests pass
- ✅ Integration tests pass
- ✅ Manual testing confirms proper 404 responses with helpful messages

Generated with [Claude Code](https://claude.ai/code)

---------

Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
Co-authored-by: adam jones <domdomegg@users.noreply.github.com>
Co-authored-by: Radoslav Dimitrov <radoslav@stacklok.com>
2025-09-10 13:14:49 -07:00
adam jones
1236f3732d
Improve publish permission error message to show available permissions (#417)
## Summary
The permission error now displays what namespaces the user has
permission to publish and what namespace they attempted to publish. This
helps users debug permission mismatches more easily.

Helps mitigate #398 to enable us to figure out what's going on
2025-09-10 12:28:22 -07:00
adam jones
0eada9b3b4
refactor: consolidate server list response types (#387)
## Summary
- Consolidate ServerListResponse and Metadata types into pkg/api/v0
- Remove duplication between handler and API types
- Change metadata from optional pointer to required value
- Remove unused Total field from Metadata
2025-09-09 11:47:27 +00:00
adam jones
f975e68cf2
Clean up schema by simplifying version structure (#379)
## Summary

Simplified the schema structure by:
- Replacing `version_detail.version` with direct `version` field  
- Removing unused `release_date` field from seed data
- Updating all API types, models, and tests to match new structure
- Maintaining backwards compatibility in API responses

This change makes the schema more intuitive and reduces nesting depth.
2025-09-08 14:32:59 -07:00
adam jones
a85456a9b9
Add filtering functionality to server list endpoint (#374)
## Summary
- Adds `updated_since`, `search`, and `version` query parameters to `GET
/v0/servers`
- Enables incremental synchronization via RFC3339 timestamp filtering
- Provides simple substring search on server names
- Supports latest version filtering with `version=latest`

This implementation provides the core filtering capabilities needed for
efficient registry synchronization and improved server discovery.

Fixes #291
Fixes #135
2025-09-08 13:55:33 -07:00