Hosted conformance: deploy + scheduled client runs (CI) and conformance remote - #502
Draft
claude[bot] wants to merge 4 commits into
Draft
Hosted conformance: deploy + scheduled client runs (CI) and conformance remote#502claude[bot] wants to merge 4 commits into
conformance remote#502claude[bot] wants to merge 4 commits into
Conversation
`conformance remote --url <RS> --scenario <name> --command "<client>"` mints a run via GET <RS>/s/<scenario>, spawns the client through the same executeClient() path `conformance client --command` uses (MCP URL as the last argv, MCP_CONFORMANCE_SCENARIO / MCP_CONFORMANCE_CONTEXT in env), reads <RS>/results/<runId> back, prints it with the usual client reporter plus the HTML report link, and exits 1 when summary.failed > 0 (or the client timed out). `-o` writes the JSON result for CI aggregation. The hosted mint route now accepts ?runId=<id> so --run-id can pick a predictable id. The val.town manifest now carries only the two org vals (mcp-client-conformance / mcp-client-conformance-as) so deploys update the live deployment in place. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FXWixCiyW8eEfwFeZcADEK
…narios deploy-hosted.yml pushes the rs + relay vals on every push to main that touches src/**, examples/hosted/** or package*.json (environment `hosted-conformance`, secret VAL_TOWN_TOKEN), then smoke-tests the live service with GET /scenarios and a `conformance remote --scenario initialize` run using the vendored TypeScript everything-client. hosted-conformance.yml runs an 8-scenario matrix (core + auth) against the live deployment 4x/day, after each deploy, on demand, and on PRs touching the hosted code. A gate job skips the matrix (never red) when the service is unreachable; each leg retries once with a fresh run id; a summary job writes a scenario/result table with links to every HTML report. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FXWixCiyW8eEfwFeZcADEK
commit: |
…n's file cap
Two live-deployment failures surfaced by the first hosted-conformance matrix
run against the mcp_conformance vals:
1. The AS relay copied the upstream content-length while fetch() had already
transparently decompressed the body, so clients received the full AS
metadata JSON with the *compressed* length and truncated it
("Unterminated string in JSON at position N"). Buffer the upstream body,
drop every framing/encoding header, ask upstream for identity encoding,
and let the runtime derive content-length. Regression test with a
gzipping mock RS.
2. The merge of main into the hosted branch pulled src/spec-types/draft.ts
(98K chars) and the four spec JSON schemas (90-180K) into the RS import
closure; val.town rejects any file over 80,000 characters, which aborted
the deploy halfway through the upload. deploy-valtown.ts now strips
comments from oversized TS modules (draft.ts drops to 20K, keeping its
runtime constants), stages .json imports as generated .json.ts modules
split into string chunks when needed, and refuses to push anything if a
staged file is still over the cap. Stage-only regression test checks
sizes and that the generated modules round-trip.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FXWixCiyW8eEfwFeZcADEK
…ed elsewhere On val.town the isolate answering GET /results is usually not the one that served the OAuth flow. SessionManager.results() re-judges the persisted raw log in a fresh scenario instance, but the metadata-discovery and token-endpoint-auth scenarios kept their `resource` observations in private fields, so the fresh instance reported "Client MUST include resource parameter" even though the persisted incoming-auth-request checks show the client sent it in both the authorize query and the token body (live runs QFi03fSC, XueJLrTc, WL8NG1m2). observeResourceParameters() reads the same facts back from the log: the /authorize query, the /token body, and the identifier the PRM route served (now recorded in prm-pathbased-requested.details.resource). getChecks() falls back to it when the private fields are empty, so single-process judging is unchanged and the multi-isolate re-judge agrees with it. The hosted relay test snapshots the raw log before results and re-judges it via finalizeChecks(); the assertion fails without the fallback. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FXWixCiyW8eEfwFeZcADEK
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Requested by Paul Carleton · Slack thread
Before: the hosted client-conformance server (#501) is deployed to val.town by hand with
examples/hosted/deploy-valtown.ts, nothing exercises the live deployment after a deploy, and the only way to drive a client against it is to mint a run with curl and wire the URL/env into the client yourself.After: pushes to
mainthat touchsrc/**,examples/hosted/**orpackage*.jsonredeploy thersandrelayvals in place and smoke-test them; a scheduled matrix of eight scenarios (core +auth/*) runs the vendored TypeScript everything-client against the live deployment four times a day, after every deploy, on demand, and on PRs that touch the hosted code, with a scenario/result table linking every HTML report in the step summary. A newconformance remotesubcommand is the reusable piece both workflows (and humans) use.This PR adds the CI for the hosted deployment plus the
conformance remoteCLI command that drives a client against it, and fixes three live-deployment bugs the first matrix runs surfaced.How:
conformance remote --url <RS> --scenario <name> --command "<client>" [--run-id <id>] [--timeout <ms>] [-o result.json]doesGET <RS>/s/<scenario>to mint a run, then spawns the client through the very sameexecuteClient()pathconformance client --commanduses (MCP URL appended as the last argument,MCP_CONFORMANCE_SCENARIO/MCP_CONFORMANCE_PROTOCOL_VERSIONin env,MCP_CONFORMANCE_CONTEXTset to the minted context), reads<RS>/results/<runId>back, prints it with the existingprintClientResultsreporter plus the/results/<runId>.htmllink, and exits 1 whensummary.failed > 0(or the client timed out). The hosted mint route gained?runId=<id>so--run-idcan pick a predictable id.deploy-hosted.yml(concurrencyhosted-deploy, environmenthosted-conformance) runsnpm ci && npm test, thendeploy-valtown.ts --push rs relay, then curls/scenariosand runs oneremote --scenario initialize.hosted-conformance.ymlhas agatejob that probes/scenariosand turns an outage into a skipped (never red) run, afail-fast: falsematrix overinitialize,tools_call,json-schema-ref-no-deref,auth/metadata-default,auth/metadata-var1,auth/metadata-var3,auth/basic-cimd,auth/pre-registration(each leg retries once with a fresh run id and uploads its JSON result), and asummaryjob that aggregates the artifacts into a table. Thevaltown-manifest.jsonnow carries only the twomcp_conformanceorg vals (rs,relay) with their live ids so deploys update in place. Unit tests coverremoteagainst a mocked hosted server (mint, URL-as-last-arg, env,?runId=, results origin, failure exit, unreachable/404) and end to end against the realcreateHostedApp().Found by the PR-triggered matrix runs against the live deployment and fixed here:
examples/hosted/valtown-relay.ts) forwarded the upstreamcontent-lengthafterfetch()had transparently decompressed the body, so every AS-metadata fetch reached the client as full JSON with the compressed length and was truncated (Unterminated string in JSON at position N). The relay now buffers the body, drops the framing/encoding headers, and asks upstream for identity encoding; regression test with a gzipping mock RS.maininto the hosted branch pulledsrc/spec-types/draft.ts(98K chars) and the four spec JSON schemas (90–180K) into the RS closure, and val.town rejects files over 80,000 characters, so a deploy aborted halfway through the upload.deploy-valtown.tsnow strips comments from oversized TS modules, stages.jsonimports as generated.json.tsmodules split into string chunks when needed, and refuses to push if anything is still over the cap; stage-only regression test. The liversandrelayvals were redeployed from this branch, which also brought theissuerfield (added onmainin fix(auth): add issuer to pre-registration context #423) into the hostedauth/pre-registrationcontext that the vendored client requires.GET /resultsis usually not the one that saw the OAuth flow;SessionManager.results()re-judges the persisted raw log in a fresh scenario instance. The metadata-discovery and token-endpoint-auth scenarios kept their RFC 8707resourceobservations in private fields, so the fresh instance reportedresource-parameter-in-authorization/resource-parameter-in-tokenas "not provided" even though the persistedincoming-auth-requestchecks show the client sent it in both places.observeResourceParameters()now reads those facts back from the log (the PRM route also records the identifier it served), andgetChecks()falls back to it; single-process judging is unchanged. The hosted relay test snapshots the raw log and re-judges it throughfinalizeChecks(), failing without the fallback.Setup needed from a maintainer:
hosted-conformancewith secretVAL_TOWN_TOKEN(a val.town API token for themcp_conformanceorg)HOSTED_RS_URL(RS origin) andHOSTED_AS_URL(AS relay origin); both workflows fall back to the current RS val URL whenHOSTED_RS_URLis unsetworkflow_runandscheduletriggers only fire once the workflow files are onmain; thepull_requesttrigger on this PR runs the matrix against the live deployment nowStacked on #501 (
claude/hosted-client-conformance); merge that first.🤖 Generated with Claude Code
https://claude.ai/code/session_01FXWixCiyW8eEfwFeZcADEK