Skip to content

feat(mcp): list the hosted MCP server in the MCP Registry at each release, and guard the tool annotations directories require - #4994

Merged
simlarsen merged 3 commits into
masterfrom
claude/intelligent-mcnulty-ae67e8
Oct 18, 2026
Merged

simlarsen merged 3 commits into
masterfrom
claude/intelligent-mcnulty-ae67e8

Conversation

@simlarsen

Copy link
Copy Markdown
Contributor

Gets OneUptime Cloud's MCP server ready for MCP directories, starting with the official MCP Registry. Nothing is published or submitted by this PR.

What changes

  • packages/App/FeatureSet/MCP/server.json: the registry entry, com.oneuptime/mcp, on the registry's current schema (2025-12-11). One remote: streamable HTTP at https://oneuptime.com/mcp. Clients sign in with OAuth, or send a project API key in the optional, secret x-api-key header. A client that leaves the header empty gets the normal sign-in challenge. The entry also carries the title, description, docs URL, repository (with its folder) and the app icon. Its version is a 0.0.0 placeholder, which the release replaces, as helm package --version does for the charts.

  • publish-mcp-registry job in release.yml, running Scripts/MCP/PublishToMcpRegistry.sh: after finalize-github-release, it publishes server.json at the release's version.

    • It skips a version the registry already has, the way Scripts/NPM/PublishAllPackages.sh skips versions npm has, so a re-run publishes nothing twice.
    • Only a clean 404 counts as "not published". The registry is a preview service and returned a transient 500 for a missing name while I was testing, so any other answer is retried. If it persists, the job fails without publishing.
    • It installs mcp-publisher 1.8.1, pinned by the archive's sha256 (checked against the release's checksums file).
    • The job may only read the repository.
  • ToolAnnotationsForDirectories.test.ts: lists the tools the way a client does (tools/list through the MCP SDK's Client). It fails, naming the tool, when any of the 193 tools:

    • has no title;
    • has no explicit readOnlyHint;
    • writes without saying whether it is destructiveHint.

    It also checks three more things: the read-only hint agrees with what RequestGate lets a read-only sign-in call, every delete tool is destructive, and names are at most 64 characters, the Claude Connectors Directory's other per-tool rule. All of this holds today. I also ran it against deliberately broken tools to confirm it fails.

  • Tests for the rest: RegistryServerJson.test.ts checks the entry against the server, and Tests/Ops/McpRegistryRelease.test.js runs the script with a fake curl and mcp-publisher and checks the job's shape. The MCP README and the Ops README document them.

Why DNS instead of GitHub OIDC

The name com.oneuptime/mcp was chosen so the name matches the domain of the URL it lists. GitHub's OIDC token can only prove io.github.<owner>/*. So the job signs in with mcp-publisher login dns and an Ed25519 key whose public key sits in a TXT record on oneuptime.com. The key is a secret of the mcp-registry-publish environment, and no other job names that environment.

Before the next release

The job fails (publishing nothing, blocking nothing: no job waits for it) until these exist. The MCP README has the commands:

  1. An Ed25519 key pair (OpenSSL 3).
  2. A TXT record on the apex oneuptime.com: v=MCPv1; k=ed25519; p=<public key>.
  3. The environment mcp-registry-publish, limited to the release branch, with the secret MCP_REGISTRY_PRIVATE_KEY (the private key in hex).

Not changed here

  • All 27 update_* tools are annotated destructiveHint: false. Anthropic's review criteria ask for destructiveHint: true on tools that "modify or delete data". ChatGPT's app guidelines call overwriting destructive, and also want explicit destructiveHint and openWorldHint on every tool. Changing this is a product decision: MCP_ALLOW_DESTRUCTIVE=false would then drop the update tools.
  • The MCP README's tool counts are stale: about 155 tools and 22 models, against 193 and 27 today.

Tests

  • packages/App: the whole FeatureSet/MCP/Tests folder, and npm run compile.
  • Tests/Ops: the new suite, and every suite that reads release.yml (ReleaseE2ePipeline, ReleaseCriticalPath, GomplateSetupCoverage and the rest): 15 suites.
  • server.json was validated against the published 2025-12-11 JSON schema.

… for

Anthropic's Claude Connectors Directory flags any tool without a title or
a readOnlyHint/destructiveHint annotation, and holds tool names to 64
characters. The test lists the tools the way a client does - tools/list
through the MCP SDK's Client - and fails naming each tool that lacks a
title, says nothing about being read-only, or writes without saying
whether it is destructive. It also holds the read-only hint to the
server's own read/write rule (RequestGate) and every delete tool to
destructiveHint: true.
server.json lists OneUptime Cloud's MCP server in the MCP Registry
(registry.modelcontextprotocol.io) as com.oneuptime/mcp: streamable HTTP
at https://oneuptime.com/mcp, signed in to with OAuth, or given a project
API key in the optional x-api-key header. It follows the registry's
2025-12-11 schema. Its version is a placeholder: the release publishes it
at the release's own version.

The test holds the entry to the server it describes - the route, the
header the server reads an API key from, a client that leaves the header
empty being asked to sign in, the source folder - and to the limits the
registry checks only when a release publishes it.
…lease

The publish-mcp-registry job runs Scripts/MCP/PublishToMcpRegistry.sh
once finalize-github-release has published the release. The script
publishes packages/App/FeatureSet/MCP/server.json as com.oneuptime/mcp
at the release's version, and skips a version the registry already has,
as Scripts/NPM/PublishAllPackages.sh does on npm. Only a clean 404 counts
as not published: the registry is a preview service that answers a
lookup with a 500 now and then, so any other answer is asked again and,
if it persists, fails the job having published nothing.

The registry lets only whoever controls oneuptime.com publish under
com.oneuptime. GitHub's OIDC token cannot prove a domain, so the job signs
in with an Ed25519 key whose public key is in a TXT record on
oneuptime.com. The key is a secret of the mcp-registry-publish
environment, which no other job names, and the job may only read the
repository. It installs mcp-publisher 1.8.1 and checks the archive's
sha256 before unpacking it.

Until the TXT record and the secret exist, the job fails with a message
that points at the setup steps in the MCP README.
@simlarsen

Copy link
Copy Markdown
Contributor Author

✅ Snyk checks have passed. No issues have been found so far.

Status Scan Engine Critical High Medium Low Total (0)
✅ Open Source Security 0 0 0 0 0 issues

💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse.

@simlarsen
simlarsen merged commit d9f753f into master Oct 18, 2026
15 of 62 checks passed
@simlarsen
simlarsen deleted the claude/intelligent-mcnulty-ae67e8 branch October 18, 2026 17:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants