Skip to content

feat(mcp): server.json for the official MCP registry — one remote entry on the user's own deployment, no npm entry (#21494) - #21530

Merged
objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/issue-21494-mcp-registry-server-json
Oct 3, 2026
Merged

objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/issue-21494-mcp-registry-server-json

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Fixes #21494

Clause-②: no (registry metadata; no accept-set or published API surface change)

What this adds

packages/mcp/server.json is one server.json for the official MCP registry, written from what ships. It is metadata only. It does not change code, any manifest or any workflow, and it adds no CI gate.

It carries one entry: a streamable-http remote. The host in that URL is the user's own deployment: https://{host}/api/v1/mcp. It carries no npm package entry.

The submission (mcp-publisher login and publish under the project's account) stays with the maintainer, on the Maintainer-action: line of #20791. So do the mcp.so, Smithery and PulseMCP listings. #20791 remains open.

Measured first: which entry the registry can truthfully carry

Remote entry: yes

Measured on the published @objectstack/cli@17.6.0. The command was npx -y @objectstack/cli@17.6.0 start --home SCRATCH -p RANDOM --no-ui. It booted the empty kernel and was torn down by its recorded process group.

probe answer
GET /api/v1/discovery "mcp": "/api/v1/mcp"
initialize on POST /api/v1/mcp, anonymous 401, with WWW-Authenticate: Bearer realm="ObjectStack MCP", resource_metadata=".../.well-known/oauth-protected-resource"
initialize with x-api-key: osk_… (key minted by POST /api/v1/keys after an email sign-up) 200, serverInfo {"name":"objectstack","version":"1.0.0"}
initialize with Authorization: Bearer osk_… 200
  • The path is the same on every CLI-served deployment. createDispatcherPlugin defaults prefix to /api/v1 (packages/runtime/src/dispatcher-plugin.ts), and os serve constructs it with no prefix.
  • No hosted URL is invented. There is no single hosted URL, so the host is a required URL template variable. The registry documents remote URL template variables for this case: "multi-tenant deployments where a single server definition can support multiple endpoints".
  • The registry's semantic validator accepts it. It accepts {host} in a remote URL and requires https (IsValidRemoteURL in internal/validators/utils.go, registry repo at bf4e88cb).
  • x-api-key is declared optional and secret. On https deployments the OAuth track is live. plugin-auth refuses it only on public plain HTTP.

npm package entry over stdio: no

  • @objectstack/mcp cannot be run with npx. None of its 64 published versions declares a bin (npm packument). npx -y @objectstack/mcp@17.6.0 exits 1 with npm error could not determine executable to run.
  • The stdio transport runs only as a mode of the user's app. It starts with OS_MCP_STDIO_ENABLED=true OS_MCP_STDIO_API_KEY=osk_… os start, run in the project directory (content/docs/ai/connect-mcp.mdx, packages/cli/src/commands/start.ts).
  • The CLI package resolves, but the registry cannot set its working directory. npx -y @objectstack/cli@17.6.0 --version does resolve a bin, because both bins point at one file. The registry's Package definition has no working-directory field. Its properties are environmentVariables, fileSha256, identifier, packageArguments, registryBaseUrl, registryType, runtimeArguments, runtimeHint, transport and version.
  • Launched from a client's directory, the published CLI never serves the user's app. Measured: it boots the empty kernel (Artifact: none (empty kernel — install apps via the Console marketplace)). The stdio plugin then refuses the key and the boot exits 1: OS_MCP_STDIO_API_KEY did not resolve to a valid identity … Refusing to start stdio (ADR-0101).

So an npm entry would name nothing that runs today. Whether to ship a standalone stdio launcher is a product question, not this card's.

Ownership marker (A3): not owed, manifest untouched

  • npm entries are checked at publish time. The registry reads mcpName from registry.npmjs.org/{identifier}/{version} (internal/validators/registries/npm.go).
  • Remotes have no ownership check. They need only namespace authentication at publish.
  • No published version carries the marker. @objectstack/mcp@17.6.0 has no mcpName.

With no npm entry, no marker is owed. So package.json is not edited, and no changeset is added.

Placement (A4)

  • What reads the file. mcp-publisher publish and mcp-publisher validate read ./server.json from the working directory by default, or the path they are given.
  • Nothing here reads it. No step in this repository's release flow reads a server.json (git grep found zero before this PR).
  • Where it sits. With no measured reader, it goes in packages/mcp/server.json, beside the package whose surface it describes, and repository.subfolder points there.
  • How to submit. Run mcp-publisher publish packages/mcp/server.json, or run it from packages/mcp.
  • It does not ship. npm pack --dry-run in packages/mcp lists CHANGELOG.md, LICENSE, README.md and package.json, with README.md as the positive control. server.json is absent, because files names only dist, README.md and CHANGELOG.md. This is why the PR carries skip-changeset.

Validation

All runs were at HEAD 9eda36398c. Each was a one-off local run. Nothing is committed.

validator against result
mcp-publisher validate packages/mcp/server.json. mcp-publisher 1.8.1 (commit f52dc85) is the latest release. the live registry's POST /v0/validate, which runs schema and semantic validation (registry /v0/version: 1.8.1) ✅ server.json is valid, exit 0
direct POST https://registry.modelcontextprotocol.io/v0/validate the same {"valid":true,"issues":[]}
ajv 8.20.0 with ajv-formats 3.0.1, draft-07 https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json, schema version 2025-12-11, the registry's CurrentSchemaVersion (fetched copy sha256 3fba0959…) VALID, exit 0

Negative controls. These show that both validators can fail:

  • {tenant} in the URL, with only host declared. mcp-publisher exits 1 with invalid-templated-url. ajv says VALID, because the schema alone cannot see this.
  • An http://{host}/… URL. mcp-publisher exits 1 with invalid-remote-url. ajv says VALID.
  • A 101-character description. mcp-publisher exits 1 (422, expected length at most 100). ajv says INVALID and exits 1.

The registry validator is the stronger of the two, and the file passes both.

NOT MEASURED: namespace authentication at publish. It needs the project's account.

Choices the maintainer may change before submitting

  • name: io.github.objectstack-ai/objectstack. This is the GitHub-org namespace, and it matches repository.url. The registry docs say a login must be an Owner of the objectstack-ai org to publish under it. The alternative is a domain namespace, ai.objectstack/objectstack, which needs a DNS TXT record on the apex. Only this one line changes.
  • version: 17.6.0. This is the npm latest release, the one whose surface this entry was written from. The registry needs a new version on every publish. Nothing here bumps it, because the card rules out a new gate.
  • websiteUrl: https://objectstack.ai/docs/ai/connect-mcp. It maps to content/docs/ai/connect-mcp.mdx under the docs site's /docs mount (apps/docs/lib/source.ts, the mapping check-published-readme-links.mjs reads). NOT MEASURED live: this container's egress proxy refuses CONNECT to objectstack.ai with 403.

Acceptance notes

  • The server's reported version disagrees with its documentation. The server reports serverInfo.version "1.0.0" (measured above). MCPServerPluginOptions.version is documented as "Defaults to package version." The registry calls server.json version the equivalent of MCP Implementation.version, so today the two differ: 17.6.0 here, 1.0.0 from the server. This is reported to the seat as a finding and is not touched here.
  • The duplicate-URL check matches the literal template string. The registry refuses a second server that publishes an identical remote URL string. Here that string is https://{host}/api/v1/mcp. GET /v0/servers?search=objectstack answered 0, and the control search=weather answered 1.

Gates

  • Derived gates. node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack at 9eda36398c named 42 commands. All 42 ran and exited 0. The --ran reconciliation printed "42 derived famil(ies) accounted for — 42 run, 0 NOT-MEASURED (a DERIVED zero …)". Four of them needed a workspace build first (turbo, 72 of 72 tasks): check:dts-closure, check:dual-build-cjs-loads, check:lean-entry-closure and check:sourcemap-no-sources-content.
  • @objectstack/mcp. vitest run passed 34 files and 382 tests. typecheck exited 0.
  • Lint. This is a proven narrowing, not a pnpm lint run:
    1. Population, from eslint.config.mjs: every files glob names only {ts,tsx,mts,cts,js,jsx,mjs,cjs}. ESLint's own verdict on this file is "File ignored because no matching configuration was supplied."
    2. Count, from --format json: 1 result with 0 errors and 1 warning (that notice). So 0 files were linted.
    3. Invariance: the config never enables type-aware linting. No block sets parserOptions.project or projectService. No source file references server.json (git grep exit 1, control exit 0). So this diff cannot change the verdict on any other file.

Generated by Claude Code

claude added 2 commits October 3, 2026 03:00
The registry can truthfully carry one entry for ObjectStack today: a
streamable-http remote whose host is the user's own deployment
(https://{host}/api/v1/mcp). Every CLI-served deployment mounts that route
default-on and admits OAuth or an osk_ key, measured on the published
@objectstack/cli 17.6.0.

No npm package entry: @objectstack/mcp declares no bin in any published
version, so `npx @objectstack/mcp` runs nothing, and the stdio transport
starts only as a mode of the user's app under `os start` in its project
directory, which a registry package entry has no field to express. With no
npm entry there is no mcpName ownership marker to publish, so no manifest
changes. The file is not in packages/mcp's files[], so nothing ships.

Validated with mcp-publisher 1.8.1 against the live registry and with ajv
against the published 2025-12-11 schema.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016GiHYRmLSNWTfbX9gVQkpz
@github-actions github-actions Bot added the size/s label Oct 3, 2026
@objectstack-fleet objectstack-fleet Bot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Oct 3, 2026
@github-actions

github-actions Bot commented Oct 3, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

⚠️ 1 changed file(s) yielded no anchor (packages/mcp/server.json), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files. Nothing else in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 1 changed package(s)).

What this run could not see
  • 1 changed file(s) yielded no anchor (packages/mcp/server.json) — pages documenting those are invisible to this run
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 12 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json ad7c3518983a1bb63fd4601954ac92d055124e42 → packageMentionDocs.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/s skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

mcp distribution: a server.json for the official MCP registry, in this repo, from the shipped surface (the dev half of #20791)

2 participants