Skip to content

docs: 📝 Add MCP Platform Testing Plan v1 - #1092

Open
mistryrn wants to merge 2 commits into
mainfrom
feat/mcp-platform-testing-plan
Open

docs: 📝 Add MCP Platform Testing Plan v1#1092
mistryrn wants to merge 2 commits into
mainfrom
feat/mcp-platform-testing-plan

Conversation

@mistryrn

@mistryrn mistryrn commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds v1 of the MCP server platfom testing plan document (.dev/docs/mcp-platform-testing.md).

Issues

Description of Changes

Adds a new dev doc which outlines the v1 plan for harnessed end-to-end testing of the entire Arranger MCP platform, including architecture, research, implementation, risks, and mitigation.

Docs

  • Added .dev/docs/mcp-platform-testing.md
    • Dev session added as well to track the numerous revisions and alternatives that were made and considered to get to the current state of the plan.

Special Instructions

N/A

Readiness Checklist

  • Self Review
    • I have performed a self review of code
    • I have run the application locally and manually tested the feature
    • I have checked all updates to correct typos and misspellings
  • Formatting
    • Code follows the project style guide
    • Autmated code formatters (ie. Prettier) have been run
  • Local Testing
    • Successfully built all packages locally
    • Successfully ran all test suites, all unit and integration tests pass
  • Updated Tests
    • Unit and integration tests have been added that describe the bug that was fixed or the features that were added
  • Documentation
    • All new environment variables added to .env.schema file and documented in the README
    • All changes to server HTTP endpoints have open-api documentation
    • All new functions exported from their module have TSDoc comment documentation

@justincorrigible justincorrigible left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

some recommended changes that I think will make a difference

Comment thread .dev/docs/mcp-platform-testing.md Outdated

Everything here goes into a **run manifest** written alongside every results file. Two runs are only comparable if their manifests differ in exactly the dimension being studied, so the comparison CLI warns loudly when they differ anywhere else.

**Test environment and case set.** The fingerprint covers the dataset and the catalogue configuration together, because either one changing invalidates a baseline the same way: per index, the document count, the mapping hash, and a checksum over a fixed aggregation, plus the `/introspection` output hashed. All of it obtained through Arranger, never ES ([§4.1](#41-working-against-the-frozen-dataset)). The case set is hashed separately.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A deployment's getServerSideFilter is neither the dataset nor catalogue configuration, so nothing in this section currently pins it.

Two suggested edits:

  • add the server-side filter to this section's test environment list
  • replicate that new parameter into R6 (line435)

Its detectability splits the four signals cleanly. A filter moves the document count and the aggregation checksum, both obtained through Arranger. It leaves the mapping hash and the introspection hash identical. So the only signals that catch it are the query-derived ones. That is the problem, because R6 pushes the other way. It asks for signals that are not "so sensitive that a harmless restart trips them", and the query-derived signals are exactly the ones that read as over-sensitive.

Drop them and a filter arriving on the testing instance becomes invisible. It then presents as a broad regression across every case at once. That is the failure § 4.1 exists to prevent.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another good call, I was unaware of getServerSideFilter and had to do some reading into that. I think it brings up other questions for me regarding how MCP should handle Arranger identity/auth. I'll need to spend some more time thinking about this before I action on this edit suggestion.

Will ping you with my understanding of it and my approach before I make any changes for this one 🙏


- **Models are open-weight, served from hardware the team controls**, in small, medium, and large tiers. Sampling parameters (temperature, top-p, seed) are configurable, and **the client is ours too, so sampling is fixed at greedy**: temperature 0, one fixed seed, everywhere. Query translation has one right answer, so sampling would add variance with no upside.
- **Data lives on a separate testing ES instance and Arranger server**, not in the repo. The dataset is real, not synthetic, and it is **frozen**: it is not reindexed or mutated between runs.
- **The MCP server may be local, or a separately deployed instance.** The suite must support both, selected by configuration.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Three suggested edits:

  • state somewhere here in §0 that expectations are derived under a single anonymous principal
  • pin that in §2, so a later change to it invalidates baselines instead of passing through
  • cross-reference the shared access-control conformance corpus, which expresses the same shape: which principal may see which record

There is no principal to define today (sans Usher), and that is the finding rather than an obstacle to it. The MCP server forwards no caller identity, so every session reaches Arranger as the same unauthenticated request (and therefor one same principal), and nothing in the case format at line 273 could set it differently... so this is an unrecorded assumption rather than a missing feature. MCP_API_KEY in § 5.4.1 does not change it either. An API key authenticates the transport. It does not necessarily make a request attributable to a principal.

The risk lands when identity arrives. outcomeMatch becomes principal relative, one intent has several correct totals depending on who asks, and every expectation derived before that point silently answers a different question. A pinned assumption turns that into an invalidated baseline. An unpinned one turns it into a suite that stays green through the change that broke it.

A field in the case format is the eventual shape, once there is something to put in it. It is not worth adding while it has one possible value. Two case sets of the same shape in two repositories will diverge unless they point at each other, which is what the third edit is for.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great call, I hadn't considered the future implications of Usher/identity on Arranger when hashing out this spec. Will make the suggested edits 🙏

Comment thread .dev/docs/mcp-platform-testing.md Outdated

### Phase 5: CI integration

- **Every PR: L1 only.** Contract tests plus the static-surface token budget per tier. No model server needed; the token budget needs only a tokenizer, which loads without a GPU.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two suggested edits:

  • state that the token budget resolves its tokenizer by pinned revision
  • state that a tokenizer change is a deliberate re-baseline rather than a build failure

staticSurfaceTokens (line 210) is measured in a tokenizer. §3 already notes that token metrics are tokenizer relative. So the suite's only hard gate depends on an artifact from outside this repository.

Phase 6 names the mechanism already: re-pulling a model tag can change behavior with no change to this repo. Applied to a reported metric, that is a re-baseline. Applied to a gate, it is a build failing on an empty diff. §1.3 argues that exact false failure is what turns a gate into a disabled gate.

§ 2's existing discipline covers the fix: "model repo plus revision, never a friendly name". It needs saying for the CI tokenizer rather than only for the served model.

Comment thread .dev/docs/mcp-platform-testing.md Outdated

This covers the "Did it get the right answer?" and "Did it follow the intended workflow?" groups, which share one piece of instrumentation. The MCP client sees every `tools/call`, so wrapping `client.callTool` to record `{ name, arguments, startedAt, durationMs, isError, resultTokens }` produces the whole trajectory, and every workflow metric is a query over that list.

- **★ `outcomeMatch`** asserts on the _structured_ result of the final `execute_query`, never the model's prose: exact `total`, exact set of returned primary keys, exact aggregation buckets. The frozen dataset makes these exact and the fingerprint check ([§4.1](#41-working-against-the-frozen-dataset)) licenses the exactness. The strongest single signal in the suite.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

one more! execute_query accepts aggregationsFilterThemselves as a tool parameter, defaulting to "false". if the model sets the value per call, that will change bucket counts. Two suggested edits:

  • record which value of aggregationsFilterThemselves the expectations were derived under
  • assert that value in the case's forbidden checks (line 277), so a run that used a different one is reported just as a finding rather than as an actual bucket mismatch

why: by default, a facet's own filter clauses are dropped from its own bucket counts, and every other clause still applies. e.g. filter on "sex" and on "disease", and the "sex" buckets are computed with the "sex" clause dropped while the "disease" clause is kept... set the flag "true" and every clause applies to every facet, including its own.

an exact bucket assertion is only defined against a fixed value of that flag. evals:bootstrap-expectations derives buckets by running a SQON directly... at run time the model chooses.
when those differ the buckets differ, and outcomeMatch records it as a comprehension failure. answerDispersion takes this worse. one phrasing leads the model to set the flag and another does not, so dispersion rises with nothing about the question misunderstood.

important: the same semantics are worth stating in §4.1 for whoever reviews the derivation diff. Unfiltered-looking counts beside a filtered query invite a correction that would break the case permanently. i.e "oh this looks broken, let me adjust it manually"

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great catch! Will update to explicitly state aggregationsFilterThemselves as part of the recorded values for each case, and suggest adding the inverse to a case's forbidden checks 🙂

@mistryrn mistryrn left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the feedback, @justincorrigible ! Initial responses below, before I start working on the changse you suggested 🙏


- **Models are open-weight, served from hardware the team controls**, in small, medium, and large tiers. Sampling parameters (temperature, top-p, seed) are configurable, and **the client is ours too, so sampling is fixed at greedy**: temperature 0, one fixed seed, everywhere. Query translation has one right answer, so sampling would add variance with no upside.
- **Data lives on a separate testing ES instance and Arranger server**, not in the repo. The dataset is real, not synthetic, and it is **frozen**: it is not reindexed or mutated between runs.
- **The MCP server may be local, or a separately deployed instance.** The suite must support both, selected by configuration.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great call, I hadn't considered the future implications of Usher/identity on Arranger when hashing out this spec. Will make the suggested edits 🙏

Comment thread .dev/docs/mcp-platform-testing.md Outdated

Everything here goes into a **run manifest** written alongside every results file. Two runs are only comparable if their manifests differ in exactly the dimension being studied, so the comparison CLI warns loudly when they differ anywhere else.

**Test environment and case set.** The fingerprint covers the dataset and the catalogue configuration together, because either one changing invalidates a baseline the same way: per index, the document count, the mapping hash, and a checksum over a fixed aggregation, plus the `/introspection` output hashed. All of it obtained through Arranger, never ES ([§4.1](#41-working-against-the-frozen-dataset)). The case set is hashed separately.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another good call, I was unaware of getServerSideFilter and had to do some reading into that. I think it brings up other questions for me regarding how MCP should handle Arranger identity/auth. I'll need to spend some more time thinking about this before I action on this edit suggestion.

Will ping you with my understanding of it and my approach before I make any changes for this one 🙏

Comment thread .dev/docs/mcp-platform-testing.md Outdated

This covers the "Did it get the right answer?" and "Did it follow the intended workflow?" groups, which share one piece of instrumentation. The MCP client sees every `tools/call`, so wrapping `client.callTool` to record `{ name, arguments, startedAt, durationMs, isError, resultTokens }` produces the whole trajectory, and every workflow metric is a query over that list.

- **★ `outcomeMatch`** asserts on the _structured_ result of the final `execute_query`, never the model's prose: exact `total`, exact set of returned primary keys, exact aggregation buckets. The frozen dataset makes these exact and the fingerprint check ([§4.1](#41-working-against-the-frozen-dataset)) licenses the exactness. The strongest single signal in the suite.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great catch! Will update to explicitly state aggregationsFilterThemselves as part of the recorded values for each case, and suggest adding the inverse to a case's forbidden checks 🙂

@mistryrn
mistryrn force-pushed the feat/mcp-platform-testing-plan branch from 35bbb46 to fe75785 Compare August 28, 2026 13:05
* Adds v1 of the MCP server platfom testing plan document (`.dev/docs/mcp-platform-testing.md`)
* Added anonymous-principal assumption
* Declared access-control posture
* Specified that tokenizer is pinned separately
* Updated introspection hashing to strip volatile fields
* Added `params` field to case sets
* Explicitly stated "human as a judge" for evaluating model-judged outputs
@mistryrn
mistryrn force-pushed the feat/mcp-platform-testing-plan branch from fe75785 to 494edef Compare August 28, 2026 13:06
@mistryrn
mistryrn marked this pull request as ready for review August 28, 2026 13:19
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