You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
PAGS has no check on what tools/list publishes — adopt the spec schema plus the directory bar, and skip the official conformance suite (it cannot authenticate and grades SDKs) #562
The gaps in #561 were found by a person reading someone else's settings page
Nothing PAGS runs would have found them. There is no check anywhere in the repo that looks at what tools/list actually publishes, so 135 tools shipped with no annotations, no titles and no output
schemas, and the build stayed green throughout. The question this issue answers is not "what is
wrong with the tool surface" (#561 owns that) but "what does PAGS run so the next one fails a
build instead of a user."
I went looking for a published gold standard. One exists, it is first-party, and it is not the
right tool for PAGS — and the reason why is the finding that decides this issue.
What exists today, from primary sources
1. @modelcontextprotocol/inspector — first-party, and it is now genuinely scriptable
Still first-party and actively maintained (repo,
last push 2026-08-12). Since my last look it has grown a documented CLI with stable exit codes
and CI recipes (docs):
Each CLI run connects to a server, invokes the single request you name with --method, prints the
result, and exits.
Code
Meaning
3
Server requires authentication (401/403, WWW-Authenticate, OAuth).
4
Server unreachable.
5
Tool error: tools/call returned isError: true, or the tool wasn't found.
and, for non-interactive runs:
--stored-auth-only: never start interactive OAuth or step-up… Use tokens from the shared store if
present, otherwise fail immediately with auth_required. This is the flag CI wants.
But it validates nothing. It invokes and prints; the documented CI recipe is a jq -e assertion
the caller writes:
So the Inspector is a reachability and presence probe, not a conformance checker. It is the only
one of the four things here that can talk to mcp.proagentstore.online as it is deployed.
2. There IS a machine-checkable artefact, and a first-party conformance suite
The schema.schema/<revision>/schema.json is published for every revision
(spec repo).
Measured today:
Revision
$schema
defs
Tool.required
2025-06-18
draft-07
91
["inputSchema","name"]
2025-11-25
2020-12
145
["inputSchema","name"]
2026-07-28
—
—
(exists; our SDK cannot speak it, see below)
The suite.modelcontextprotocol/conformance
— "Conformance Tests for MCP", first-party, MIT, 101 stars, created 2025-07-10, last push
2026-08-10. Published as @modelcontextprotocol/conformance (latest stable 0.1.16, 2026-03-30; 0.2.0-alpha.11 on 2026-08-07 — pre-1.0). It ships a composite GitHub Action, an
expected-failures baseline with regression and stale-baseline exit codes, and per-revision
requirement sets (requirements/2025-11-25.yaml, requirements/2026-07-28.yaml).
It also does the thing you would want most:
Every scenario also validates each JSON-RPC message on the wire against the spec's JSON schema for
the negotiated spec version… wire-schema-valid — fails when a message the implementation under
test sent violates the spec JSON schema.
I ran it against production. Measured, 2026-08-14:
$ npx @modelcontextprotocol/conformance@latest server \
--url https://mcp.proagentstore.online/mcp --scenario server-initialize
[server-initialize] FAILURE Server responds to initialize request with valid structure
Error: Failed to initialize: Streamable HTTP error: Error POSTing to endpoint:
{"error":"invalid_token","error_description":"Missing or invalid access token"}
Passed: 0/1, 1 failed
Confirmed against the server directly — an unauthenticated initialize returns HTTP 401 with WWW-Authenticate: Bearer realm="OAuth", …, which is correct behaviour from OAuthProvider({ apiRoute: "/mcp" }) (workers/mcp/src/index.ts:1143-1147).
The server subcommand has no way to authenticate. Read from src/index.ts:529-558: --url (required), --scenario, --suite, --expected-failures, -o, --spec-version, --force, --requirements, --verbose. No --header, no --token, no OAuth. Server-side auth
testing is open work upstream: #64, #105, #106 — all open.
And most of what it requires, PAGS does not implement and should not. The required server list
in requirements/2025-11-25.yaml
has 31 entries. It includes resources-list, resources-read-text, resources-subscribe, prompts-list, prompts-get-with-args, logging-set-level, completion-complete, tools-call-sampling, tools-call-elicitation — features #561 already recorded as a deliberate
non-goal. Worse, the tool scenarios expect a reference fixture, not a product: src/scenarios/server/tools.ts requires the server under test to implement a tool literally named test_simple_text returning an exact payload; stateless wants test_missing_capability.
That is the answer to "is this our gold standard": no. It is the conformance suite for SDKs — its own tier-check subcommand grades SDK repositories against SEP-1730. Pointed at a
product server it would score roughly 4/31 and the number would mean nothing.
3. The real bar is the two directories — and it is stricter than the spec
Anthropic, whose MCP Directory Policy has been consolidated into the Anthropic Software Directory Policy
(the old URL now
just points at it). §5, Additional Requirements for Model Context Protocol Servers, quoted verbatim
from the page:
A. MCP servers must gracefully handle errors and provide helpful feedback rather than generic error messages.
B. MCP servers must be frugal with their use of tokens. The amount of tokens a given tool call uses should be roughly commensurate with the complexity or impact of the task…
C. MCP tool names must not exceed 64 characters.
D. Remote MCP servers that connect to a remote service and require authentication must use secure OAuth 2.0 with certificates from recognized authorities. E. MCP servers must provide all applicable annotations for their tools, in particular readOnlyHint, destructiveHint, and title.
F. Remote MCP servers should support the Streamable HTTP transport…
Plus, from §3: a standard testing account with sample data, and "at least three working examples of
prompts or use cases".
Item E is a hard listing requirement and it names the exact three fields #561 is adding —
including title, which #561 currently carries as an "adjacent gap, fold in or reject". This
upgrades it: it is a directory bar, not legibility.
OpenAI, App review — the submission
scan imports "tool names, titles, and descriptions; input and output schemas; security schemes; _meta fields; tool annotations; linked UI resource metadata…; and MCP server instructions", and
the review verifies "tool hint annotations accurately reflect behavior". App guidelines: "Incorrect or missing
action labels are a common cause of rejection." (Already established on #561's comment —
not re-derived here.)
4. Multi-host: no published "test against N hosts" rule that I could find
I looked and did not find one. What is published is a client extension support matrix — it
covers only the three official extensions (MCP Apps, OAuth Client Credentials, Enterprise-Managed
Authorization), not core features, and it is explicitly "maintained by the community". It does not
tell you which host exercises resources vs. prompts vs. sampling.
Since PAGS ships no resources, no prompts, and requires no sampling or elicitation (#561), the
features a second host would exercise that ChatGPT does not are close to nil. My recommendation is
to not chase multi-host coverage, and to treat "does it work in Claude Desktop / Claude Code" as a
manual check performed once, at the point of a directory submission. Recorded as a negative result
rather than left as an open question.
The measurement that decides the design
I built the real thing in a scratch spike: a real McpServer (SDK 1.29.0, the pinned version), tools
registered through the exact call shape PAGS uses — server.tool(name, desc, zodShape, handler) —
connected to a real Client over InMemoryTransport, then client.listTools(), then Ajv against the
spec's Tool definition.
A spec-schema conformance check passes cleanly on exactly the defect #561 found.Tool.required
is ["inputSchema","name"] — description, title, annotations and outputSchema are all
optional. So schema validation is real and worth having, but it is not the check that would have
caught today, and an issue that recommended it as the answer would be recommending a guard that
certifies ground it never walked (ADR 0002).
Two layers, and they must be labelled differently:
Spec conformance — "is this structurally a legal MCP tool surface?" Cheap, catches future
breakage, would have caught nothing today.
Directory conformance — "does this meet the published bar for listing?" This is the layer with
today's findings in it, and no published validator enforces it. PAGS has to assert it, and
PAGS is unusually well placed to, because safety.ts already classifies every tool.
What to do, cheapest first
The hook is workers/mcp/src/index.test.ts. Its harness constructs the real PagsMcp, replaces this.server with a capturing double (index.test.ts:122-127) and runs the real init(); the
suspension test at index.test.ts:496 asserts h.tools.size === MCP_TOOL_COUNT before iterating —
ADR 0002 names it "the hardest thing in this repo to fool". Verified that only this.server.tool
is ever called on that object (35 call sites in index.ts, plus the delegated groups), so the double
can be swapped for a real McpServer without touching production code.
Step 1 — a tools/list guard built on the REAL emitted objects (do this)
New file workers/mcp/src/conformance.test.ts. Same harness shape as index.test.ts, but mock only agents/mcp (not@modelcontextprotocol/sdk/server/mcp.js), assign a real McpServer, connect
an InMemoryTransport pair to a real Client, and call listTools(). That yields the byte-for-byte Tool[] a host receives. Proven to work in the spike above.
Assert, over the whole surface with every group gated on:
tools.length === MCP_TOOL_COUNT — ADR 0002 G1, and it reuses the constant that already
exists precisely because that number rotted once (tool-count.ts:23).
Every name is SEP-986 legal: 1–64 chars, ^[A-Za-z0-9_./-]+$. This is simultaneously the
spec's rule and Anthropic §5.C. Measured today by a source scan: 135 distinct names, 0
violations, max length 31 — so this arm goes green on day one and Step 4 below matters.
Every tool has a non-empty description. Not required by Tool.required, but required by the
conformance suite's own tools-list scenario and by Anthropic §2. Measured: 135/135 have one.
Annotation ⇄ scope agreement.destructive scope ⇒ destructiveHint: true; read/none ⇒ readOnlyHint: true; write/runtime ⇒ readOnlyHint: false; a contradiction fails. This is
the check no external tool can perform, because only PAGS knows safety.ts is the authority. The MCP server never publishes tool annotations or output schemas — it already classifies every tool read/write/destructive and tells no host #561's comment already worked out the shape (and the remove_repo exception, whose scope depends
on its arguments). Implement it there; this issue just requires it be asserted here, on the whole
registered surface rather than the ~90 instance tools contract.test.ts covers.
Print the denominator — ADR 0002 G2. e.g. ✓ 135 tools: 135 annotated, 135 titled, 12 with outputSchema, 0 SEP-986 name violations.
Cost: one test file, no new production code, no new CI step — workers/*/src/**/*.test.ts is already in the UNIT_TESTS glob (vitest.config.ts), so pnpm test
picks it up.
Step 2 — validate the same array against the published spec schema
On the same Tool[], plus the whole ListToolsResult. Vendor schema/2025-11-25/schema.json into workers/mcp/src/ (the upstream conformance suite vendors the
same files at src/spec-types/*.schema.json for the same reason: CI must not depend on the network),
validate $defs/Tool with Ajv2020 + ajv-formats.
Target 2025-11-25 — verified as the newest revision the pinned SDK speaks
(@modelcontextprotocol/sdk@1.29.0dist/esm/types.js:2-3: LATEST_PROTOCOL_VERSION = '2025-11-25', SUPPORTED_PROTOCOL_VERSIONS = [LATEST, '2025-06-18', '2025-03-26', '2024-11-05', '2024-10-07']). 2026-07-28 exists upstream but the SDK cannot negotiate it; validating against it would measure a
protocol PAGS does not speak.
ajv is not a direct dependency anywhere in the monorepo today, but is already resolved in the store
as a dep of the MCP SDK (ajv ^8.17.1, ajv-formats ^3.0.1) — add it as a devDependency on workers/mcp.
Cost: ~30 lines on Step 1's harness plus one vendored JSON file. Buys future protection for the
hand-written outputSchemas #561 introduces — a malformed one currently fails only at runtime, inside
the SDK's validateToolOutput.
State plainly, in the test header, that this arm passed before #561 and therefore does not cover it.
Step 3 — do NOT wire the official conformance suite into CI
Recommended against, on the evidence above: it cannot authenticate (measured 401; upstream #64/#105/#106
open), it is pre-1.0, and 26 of its 31 required server scenarios test features PAGS deliberately does
not implement or fixture tools it will never have. A red build carrying a meaningless number is worse
than no build step.
There is a generic subset worth one bounded spike, if and only if standing up a local
unauthenticated instance is cheap: server-initialize, tools-list, ping, server-session-lifecycle, http-header-validation, dns-rebinding-protection. The first three duplicate Step 1 in a weaker form.
The last two are the only genuinely new information, and they test HTTP-layer behaviour owned by @cloudflare/workers-oauth-provider and agents/mcp, not by PAGS code. Timebox it to an hour; if a
local unauthenticated entry point is not already trivially available, drop it — do not add a
production entry that bypasses OAuth in order to run a test. Re-evaluate when upstream #64 lands.
Step 4 — prove each arm fails (ADR 0002 G4)
Non-optional, and the reason arms 2 and 3 of Step 1 are worth writing at all despite going green
immediately. Before merge, for each arm: rename one tool to contain a :; blank one description;
delete one tool's annotations; flip cancel_instance's destructiveHint to false; add a 136th
tool without touching MCP_TOOL_COUNT. Record in the PR that each flipped the assertion red.
Optional, after the above — Inspector CLI as a post-deploy smoke check
mcp-inspector --cli https://mcp.proagentstore.online/mcp --transport http --stored-auth-only --method tools/list --format json | jq -e '.result.tools | length == 135'. It is the only tool here
that can reach the deployed server. Not in CI — --stored-auth-only needs a token in the shared
store, and putting a long-lived OAuth token in CI to assert a number Step 1 already asserts is a poor
trade. Worth documenting in platform-docs/mcp.md as the manual "is prod actually serving what main
says it serves" command, and worth using the exit codes (3 auth, 4 unreachable) if a scheduled
uptime probe is ever wanted.
Hand-write a conformance suite against schema.ts. Rejected outright — the spec publishes schema.json; transcribing it is the drift bug this issue exists to prevent. Steps 1–2 use the
published artefact.
Validate against 2026-07-28 / draft. Rejected: the pinned SDK cannot negotiate it. Revisit
when the SDK is bumped — and note the SDK bump itself is what would make Step 2's target stale, so
the vendored schema's revision belongs in the test's printed output.
A new ADR. Not warranted. ADR 0002 already supplies the constraint these guards must satisfy,
and docs/adr/README.md reserves ADRs for rules whose breach looks locally correct. This is an
implementation of an existing rule.
Acceptance criteria
workers/mcp/src/conformance.test.ts obtains its Tool[] from a realMcpServer + InMemoryTransport + real Client.listTools() driven by the real PagsMcp.init() — not from a
reconstruction of what the SDK would emit.
It asserts tools.length === MCP_TOOL_COUNT before iterating, and its passing output states that
number alongside the per-property tallies and the spec revision validated against (ADR 0002 G1+G2).
Every one of the 135 tools is asserted for: SEP-986 name legality, non-empty description, annotations.readOnlyHint + annotations.destructiveHint + title present, and agreement between
annotation and safety.ts scope.
The same array validates against the vendored schema/2025-11-25/schema.json$defs/Tool, and the
full response against ListToolsResult. Any tool whose validation errors (as opposed to fails)
is counted and asserted zero, never skipped (ADR 0002 G3).
The swap from the capturing double to a real McpServer is the only real risk. The real one
throws Tool ... is already registered on a duplicate name and validates the zod shape at
registration, so a latent duplicate would surface as a new red test. That is a genuine finding, not
a breakage — but it will look like one, so it belongs in the PR description. index.test.ts's own
double stays as it is; this is a second, separate harness.
Step 2 pins a spec revision. Bumping @modelcontextprotocol/sdk past 2025-11-25 makes the vendored
schema stale while the test stays green. Mitigation: assert the SDK's LATEST_PROTOCOL_VERSION
equals the vendored revision, so the bump fails the test rather than silently under-measuring it.
MCP_TOOL_COUNT is now asserted from two files. docs-drift.mjs already reads it too; adding a
third reader raises the cost of a legitimate tool addition by one line and is the intended trade.
Open question for the owner
Is a directory listing an actual goal? Steps 1–2 are worth doing either way — annotations improve
host behaviour whether or not anyone reviews them. But if listing in Anthropic's Software Directory or
OpenAI's app directory is a real objective, then §3 of the Anthropic policy adds work this issue does
not cover: a standard testing account with sample data, a privacy-policy link, and three worked example
prompts. I would say yes, and treat #561 + this issue as its first two steps — the annotation
requirement is a bar PAGS has to clear regardless, and clearing it deliberately is cheaper than
clearing it under review feedback. Filed as a question rather than assumed.
Verified vs. inferred
Verified (measured today, 2026-08-14/15): the production 401 and the conformance CLI's failure
against it; the conformance CLI's complete server-mode option list (no auth flag); the 31-entry
required server set and its fixture-tool dependency; the two spec schemas' Tool.required; the spike
showing PAGS's registration shape emits Tool objects that validate at both revisions with 0
annotations/titles/outputSchemas; 135 tool names scanned with 0 SEP-986 violations, max length 31;
135/135 non-empty descriptions; LATEST_PROTOCOL_VERSION = '2025-11-25' in the pinned SDK; InMemoryTransport present in the installed SDK; this.server.tool being the only method PAGS calls
on the server object; workers/*/src/**/*.test.ts already in the vitest unit glob.
Verified (external, quoted): the Inspector CLI's exit codes, --stored-auth-only and CI recipe;
the conformance suite's wire-schema checks, baseline exit-code table and requirement-set semantics;
Anthropic Software Directory Policy §5 A–G verbatim; OpenAI's app-review scan inventory.
Not established: whether any host beyond ChatGPT currently reads PAGS's tool surface at all — I
did not test Claude Desktop, Claude Code or Cursor against mcp.proagentstore.online, and did not
find published guidance requiring it. Recorded as a non-reproduction, not a gap.
Related: #561 (the gaps themselves — do not duplicate), ADR 0002 (docs/adr/0002-a-guard-states-what-it-measured.md).
The gaps in #561 were found by a person reading someone else's settings page
Nothing PAGS runs would have found them. There is no check anywhere in the repo that looks at what
tools/listactually publishes, so 135 tools shipped with no annotations, no titles and no outputschemas, and the build stayed green throughout. The question this issue answers is not "what is
wrong with the tool surface" (#561 owns that) but "what does PAGS run so the next one fails a
build instead of a user."
I went looking for a published gold standard. One exists, it is first-party, and it is not the
right tool for PAGS — and the reason why is the finding that decides this issue.
What exists today, from primary sources
1.
@modelcontextprotocol/inspector— first-party, and it is now genuinely scriptableStill first-party and actively maintained (repo,
last push 2026-08-12). Since my last look it has grown a documented CLI with stable exit codes
and CI recipes (docs):
3WWW-Authenticate, OAuth).45tools/callreturnedisError: true, or the tool wasn't found.and, for non-interactive runs:
But it validates nothing. It invokes and prints; the documented CI recipe is a
jq -eassertionthe caller writes:
So the Inspector is a reachability and presence probe, not a conformance checker. It is the only
one of the four things here that can talk to
mcp.proagentstore.onlineas it is deployed.2. There IS a machine-checkable artefact, and a first-party conformance suite
The schema.
schema/<revision>/schema.jsonis published for every revision(spec repo).
Measured today:
$schemaTool.required2025-06-18["inputSchema","name"]2025-11-25["inputSchema","name"]2026-07-28The suite.
modelcontextprotocol/conformance— "Conformance Tests for MCP", first-party, MIT, 101 stars, created 2025-07-10, last push
2026-08-10. Published as
@modelcontextprotocol/conformance(latest stable 0.1.16, 2026-03-30;0.2.0-alpha.11on 2026-08-07 — pre-1.0). It ships a composite GitHub Action, anexpected-failures baseline with regression and stale-baseline exit codes, and per-revision
requirement sets (
requirements/2025-11-25.yaml,requirements/2026-07-28.yaml).It also does the thing you would want most:
I ran it against production. Measured, 2026-08-14:
Confirmed against the server directly — an unauthenticated
initializereturnsHTTP 401withWWW-Authenticate: Bearer realm="OAuth", …, which is correct behaviour fromOAuthProvider({ apiRoute: "/mcp" })(workers/mcp/src/index.ts:1143-1147).The
serversubcommand has no way to authenticate. Read fromsrc/index.ts:529-558:--url(required),--scenario,--suite,--expected-failures,-o,--spec-version,--force,--requirements,--verbose. No--header, no--token, no OAuth. Server-side authtesting is open work upstream:
#64,
#105,
#106 — all open.
And most of what it requires, PAGS does not implement and should not. The required server list
in
requirements/2025-11-25.yamlhas 31 entries. It includes
resources-list,resources-read-text,resources-subscribe,prompts-list,prompts-get-with-args,logging-set-level,completion-complete,tools-call-sampling,tools-call-elicitation— features #561 already recorded as a deliberatenon-goal. Worse, the tool scenarios expect a reference fixture, not a product:
src/scenarios/server/tools.tsrequires the server under test to implement a tool literally namedtest_simple_textreturning an exact payload;statelesswantstest_missing_capability.That is the answer to "is this our gold standard": no. It is the conformance suite for
SDKs — its own
tier-checksubcommand grades SDK repositories againstSEP-1730. Pointed at a
product server it would score roughly 4/31 and the number would mean nothing.
3. The real bar is the two directories — and it is stricter than the spec
Anthropic, whose MCP Directory Policy has been consolidated into the
Anthropic Software Directory Policy
(the old URL now
just points at it). §5, Additional Requirements for Model Context Protocol Servers, quoted verbatim
from the page:
Plus, from §3: a standard testing account with sample data, and "at least three working examples of
prompts or use cases".
Item E is a hard listing requirement and it names the exact three fields #561 is adding —
including
title, which #561 currently carries as an "adjacent gap, fold in or reject". Thisupgrades it: it is a directory bar, not legibility.
OpenAI, App review — the submission
scan imports "tool names, titles, and descriptions; input and output schemas; security schemes;
_metafields; tool annotations; linked UI resource metadata…; and MCP serverinstructions", andthe review verifies "tool hint annotations accurately reflect behavior".
App guidelines: "Incorrect or missing
action labels are a common cause of rejection." (Already established on
#561's comment —
not re-derived here.)
4. Multi-host: no published "test against N hosts" rule that I could find
I looked and did not find one. What is published is a
client extension support matrix — it
covers only the three official extensions (MCP Apps, OAuth Client Credentials, Enterprise-Managed
Authorization), not core features, and it is explicitly "maintained by the community". It does not
tell you which host exercises resources vs. prompts vs. sampling.
Since PAGS ships no resources, no prompts, and requires no sampling or elicitation (#561), the
features a second host would exercise that ChatGPT does not are close to nil. My recommendation is
to not chase multi-host coverage, and to treat "does it work in Claude Desktop / Claude Code" as a
manual check performed once, at the point of a directory submission. Recorded as a negative result
rather than left as an open question.
The measurement that decides the design
I built the real thing in a scratch spike: a real
McpServer(SDK 1.29.0, the pinned version), toolsregistered through the exact call shape PAGS uses —
server.tool(name, desc, zodShape, handler)—connected to a real
ClientoverInMemoryTransport, thenclient.listTools(), then Ajv against thespec's
Tooldefinition.Emitted wire object:
{ "name": "list_agents", "description": "List published agents", "inputSchema": { "type": "object", "properties": { "limit": { "type": "number" } }, "additionalProperties": false, "$schema": "http://json-schema.org/draft-07/schema#" }, "execution": { "taskSupport": "forbidden" } }Result:
A spec-schema conformance check passes cleanly on exactly the defect #561 found.
Tool.requiredis
["inputSchema","name"]—description,title,annotationsandoutputSchemaare alloptional. So schema validation is real and worth having, but it is not the check that would have
caught today, and an issue that recommended it as the answer would be recommending a guard that
certifies ground it never walked (ADR 0002).
Two layers, and they must be labelled differently:
breakage, would have caught nothing today.
today's findings in it, and no published validator enforces it. PAGS has to assert it, and
PAGS is unusually well placed to, because
safety.tsalready classifies every tool.What to do, cheapest first
The hook is
workers/mcp/src/index.test.ts. Its harness constructs the realPagsMcp, replacesthis.serverwith a capturing double (index.test.ts:122-127) and runs the realinit(); thesuspension test at
index.test.ts:496assertsh.tools.size === MCP_TOOL_COUNTbefore iterating —ADR 0002 names it "the hardest thing in this repo to fool". Verified that only
this.server.toolis ever called on that object (35 call sites in
index.ts, plus the delegated groups), so the doublecan be swapped for a real
McpServerwithout touching production code.Step 1 — a
tools/listguard built on the REAL emitted objects (do this)New file
workers/mcp/src/conformance.test.ts. Same harness shape asindex.test.ts, but mock onlyagents/mcp(not@modelcontextprotocol/sdk/server/mcp.js), assign a realMcpServer, connectan
InMemoryTransportpair to a realClient, and calllistTools(). That yields the byte-for-byteTool[]a host receives. Proven to work in the spike above.Assert, over the whole surface with every group gated on:
tools.length === MCP_TOOL_COUNT— ADR 0002 G1, and it reuses the constant that alreadyexists precisely because that number rotted once (
tool-count.ts:23).^[A-Za-z0-9_./-]+$. This is simultaneously thespec's rule and Anthropic §5.C. Measured today by a source scan: 135 distinct names, 0
violations, max length 31 — so this arm goes green on day one and Step 4 below matters.
Tool.required, but required by theconformance suite's own
tools-listscenario and by Anthropic §2. Measured: 135/135 have one.annotations.readOnlyHint,annotations.destructiveHint, and atitle—Anthropic §5.E verbatim. This arm is red until The MCP server never publishes tool annotations or output schemas — it already classifies every tool read/write/destructive and tells no host #561 lands, which is the point: it converts
The MCP server never publishes tool annotations or output schemas — it already classifies every tool read/write/destructive and tells no host #561 from a fix into an invariant.
destructivescope ⇒destructiveHint: true;read/none⇒readOnlyHint: true;write/runtime⇒readOnlyHint: false; a contradiction fails. This isthe check no external tool can perform, because only PAGS knows
safety.tsis the authority.The MCP server never publishes tool annotations or output schemas — it already classifies every tool read/write/destructive and tells no host #561's comment already worked out the shape (and the
remove_repoexception, whose scope dependson its arguments). Implement it there; this issue just requires it be asserted here, on the whole
registered surface rather than the ~90 instance tools
contract.test.tscovers.✓ 135 tools: 135 annotated, 135 titled, 12 with outputSchema, 0 SEP-986 name violations.Cost: one test file, no new production code, no new CI step —
workers/*/src/**/*.test.tsis already in theUNIT_TESTSglob (vitest.config.ts), sopnpm testpicks it up.
Step 2 — validate the same array against the published spec schema
On the same
Tool[], plus the wholeListToolsResult. Vendorschema/2025-11-25/schema.jsonintoworkers/mcp/src/(the upstream conformance suite vendors thesame files at
src/spec-types/*.schema.jsonfor the same reason: CI must not depend on the network),validate
$defs/ToolwithAjv2020+ajv-formats.Target
2025-11-25— verified as the newest revision the pinned SDK speaks(
@modelcontextprotocol/sdk@1.29.0dist/esm/types.js:2-3:LATEST_PROTOCOL_VERSION = '2025-11-25',SUPPORTED_PROTOCOL_VERSIONS = [LATEST, '2025-06-18', '2025-03-26', '2024-11-05', '2024-10-07']).2026-07-28exists upstream but the SDK cannot negotiate it; validating against it would measure aprotocol PAGS does not speak.
ajvis not a direct dependency anywhere in the monorepo today, but is already resolved in the storeas a dep of the MCP SDK (
ajv ^8.17.1,ajv-formats ^3.0.1) — add it as a devDependency onworkers/mcp.Cost: ~30 lines on Step 1's harness plus one vendored JSON file. Buys future protection for the
hand-written
outputSchemas #561 introduces — a malformed one currently fails only at runtime, insidethe SDK's
validateToolOutput.State plainly, in the test header, that this arm passed before #561 and therefore does not cover it.
Step 3 — do NOT wire the official conformance suite into CI
Recommended against, on the evidence above: it cannot authenticate (measured 401; upstream #64/#105/#106
open), it is pre-1.0, and 26 of its 31 required server scenarios test features PAGS deliberately does
not implement or fixture tools it will never have. A red build carrying a meaningless number is worse
than no build step.
There is a generic subset worth one bounded spike, if and only if standing up a local
unauthenticated instance is cheap:
server-initialize,tools-list,ping,server-session-lifecycle,http-header-validation,dns-rebinding-protection. The first three duplicate Step 1 in a weaker form.The last two are the only genuinely new information, and they test HTTP-layer behaviour owned by
@cloudflare/workers-oauth-providerandagents/mcp, not by PAGS code. Timebox it to an hour; if alocal unauthenticated entry point is not already trivially available, drop it — do not add a
production entry that bypasses OAuth in order to run a test. Re-evaluate when upstream #64 lands.
Step 4 — prove each arm fails (ADR 0002 G4)
Non-optional, and the reason arms 2 and 3 of Step 1 are worth writing at all despite going green
immediately. Before merge, for each arm: rename one tool to contain a
:; blank one description;delete one tool's annotations; flip
cancel_instance'sdestructiveHinttofalse; add a 136thtool without touching
MCP_TOOL_COUNT. Record in the PR that each flipped the assertion red.Optional, after the above — Inspector CLI as a post-deploy smoke check
mcp-inspector --cli https://mcp.proagentstore.online/mcp --transport http --stored-auth-only --method tools/list --format json | jq -e '.result.tools | length == 135'. It is the only tool herethat can reach the deployed server. Not in CI —
--stored-auth-onlyneeds a token in the sharedstore, and putting a long-lived OAuth token in CI to assert a number Step 1 already asserts is a poor
trade. Worth documenting in
platform-docs/mcp.mdas the manual "is prod actually serving what mainsays it serves" command, and worth using the exit codes (
3auth,4unreachable) if a scheduleduptime probe is ever wanted.
Alternatives considered and rejected
any of The MCP server never publishes tool annotations or output schemas — it already classifies every tool read/write/destructive and tells no host #561's gaps, and being manual it finds nothing twice.
@modelcontextprotocol/conformanceintoci.ymlvia its GitHub Action. Rejected: cannotauthenticate, pre-1.0, and its required set is an SDK reference-fixture surface. Reconsider if
upstream [Catalog] Audit every published agent — keep / fix / unpublish #64/Generic HTTP/REST connector — call any API as config (#95) #105/Declarative pipeline definition + durable runner (#97) #106 land AND the requirement sets grow a product-server profile.
schema.ts. Rejected outright — the spec publishesschema.json; transcribing it is the drift bug this issue exists to prevent. Steps 1–2 use thepublished artefact.
2026-07-28/draft. Rejected: the pinned SDK cannot negotiate it. Revisitwhen the SDK is bumped — and note the SDK bump itself is what would make Step 2's target stale, so
the vendored schema's revision belongs in the test's printed output.
ruled their absence a design choice, not a standards failure. Passing a suite is not a reason to
grow a protocol surface.
and
docs/adr/README.mdreserves ADRs for rules whose breach looks locally correct. This is animplementation of an existing rule.
Acceptance criteria
workers/mcp/src/conformance.test.tsobtains itsTool[]from a realMcpServer+InMemoryTransport+ realClient.listTools()driven by the realPagsMcp.init()— not from areconstruction of what the SDK would emit.
tools.length === MCP_TOOL_COUNTbefore iterating, and its passing output states thatnumber alongside the per-property tallies and the spec revision validated against (ADR 0002 G1+G2).
annotations.readOnlyHint+annotations.destructiveHint+titlepresent, and agreement betweenannotation and
safety.tsscope.schema/2025-11-25/schema.json$defs/Tool, and thefull response against
ListToolsResult. Any tool whose validation errors (as opposed to fails)is counted and asserted zero, never skipped (ADR 0002 G3).
validation alone passed before The MCP server never publishes tool annotations or output schemas — it already classifies every tool read/write/destructive and tells no host #561 and does not cover annotations, titles or output schemas.
workers/mcp/src/outside what The MCP server never publishes tool annotations or output schemas — it already classifies every tool read/write/destructive and tells no host #561 already requires. In particular,no unauthenticated entry point is added.
Regression risk
McpServeris the only real risk. The real onethrows
Tool ... is already registeredon a duplicate name and validates the zod shape atregistration, so a latent duplicate would surface as a new red test. That is a genuine finding, not
a breakage — but it will look like one, so it belongs in the PR description.
index.test.ts's owndouble stays as it is; this is a second, separate harness.
annotation arms behind an explicit
it.todothat The MCP server never publishes tool annotations or output schemas — it already classifies every tool read/write/destructive and tells no host #561's PR flips on — notskip, which reads aspassing.
@modelcontextprotocol/sdkpast 2025-11-25 makes the vendoredschema stale while the test stays green. Mitigation: assert the SDK's
LATEST_PROTOCOL_VERSIONequals the vendored revision, so the bump fails the test rather than silently under-measuring it.
MCP_TOOL_COUNTis now asserted from two files.docs-drift.mjsalready reads it too; adding athird reader raises the cost of a legitimate tool addition by one line and is the intended trade.
Open question for the owner
Is a directory listing an actual goal? Steps 1–2 are worth doing either way — annotations improve
host behaviour whether or not anyone reviews them. But if listing in Anthropic's Software Directory or
OpenAI's app directory is a real objective, then §3 of the Anthropic policy adds work this issue does
not cover: a standard testing account with sample data, a privacy-policy link, and three worked example
prompts. I would say yes, and treat #561 + this issue as its first two steps — the annotation
requirement is a bar PAGS has to clear regardless, and clearing it deliberately is cheaper than
clearing it under review feedback. Filed as a question rather than assumed.
Verified vs. inferred
against it; the conformance CLI's complete server-mode option list (no auth flag); the 31-entry
required server set and its fixture-tool dependency; the two spec schemas'
Tool.required; the spikeshowing PAGS's registration shape emits Tool objects that validate at both revisions with 0
annotations/titles/outputSchemas; 135 tool names scanned with 0 SEP-986 violations, max length 31;
135/135 non-empty descriptions;
LATEST_PROTOCOL_VERSION = '2025-11-25'in the pinned SDK;InMemoryTransportpresent in the installed SDK;this.server.toolbeing the only method PAGS callson the server object;
workers/*/src/**/*.test.tsalready in the vitest unit glob.--stored-auth-onlyand CI recipe;the conformance suite's wire-schema checks, baseline exit-code table and requirement-set semantics;
Anthropic Software Directory Policy §5 A–G verbatim; OpenAI's app-review scan inventory.
caught the annotation class by construction; I have not run them against a planted defect, which is
exactly what Step 4 exists to fix before merge.
did not test Claude Desktop, Claude Code or Cursor against
mcp.proagentstore.online, and did notfind published guidance requiring it. Recorded as a non-reproduction, not a gap.
Related: #561 (the gaps themselves — do not duplicate), ADR 0002 (
docs/adr/0002-a-guard-states-what-it-measured.md).