Skip to main content
Glama

experiment-mcp

An MCP server for designing and reading retail-offer experiments. It is a portfolio project: a small, offline, fully tested server that exercises the major Model Context Protocol features on synthetic offer data.

The statistics are ordinary two-sample calculations (sample size, hash assignment, lift with an optional CUPED adjustment, a chi-square sample-ratio check, and a percentile bootstrap). They are not a production experimentation platform, and the bundled rows are not real customers.

Architecture

flowchart LR
  subgraph clients [Clients]
    Desktop[Claude Desktop / Claude Code]
    Inspector[MCP Inspector]
    HTTPClient[HTTP MCP client]
  end
  subgraph server [experiment-mcp]
    MCP[MCPServer]
    Stats[stats]
    Store[(SQLite experiments)]
    CSV[synthetic CSV]
  end
  Desktop -->|stdio| MCP
  Inspector -->|stdio| MCP
  HTTPClient -->|Streamable HTTP + Bearer JWT| MCP
  MCP --> Stats
  MCP --> Store
  MCP --> CSV
  DevAuth[localhost dev token minter] -.->|JWKS| MCP

src/experiment_mcp/stats/ has no MCP imports. server.py is the protocol layer: tools, resources, prompts, completions, sampling, and elicitation.

The SDK is mcp 2.x. FastMCP was renamed to MCPServer; this project uses that decorator API (@mcp.tool, @mcp.resource, @mcp.prompt, @mcp.completion).

Related MCP server: bourne mcp

Why MCP instead of a plain API?

The calculations live in ordinary Python functions. MCP is the contract a model client uses to find and call them.

One server definition is enough for Claude Desktop, Claude Code, Cursor, and MCP Inspector. Each new client is configuration. Each new tool is one function. A hand-written function-calling adapter is per client, so N clients and M operations become N times M wrappers instead of N plus M.

Clients discover tools, resources, and prompts at runtime, with schemas generated from the function signatures. close_experiment can ask the user to confirm mid-call. draft_readout asks the client model to write prose, so this process has no model API key. Bootstrap analyze_lift reports progress. Creating or closing an experiment notifies subscribers. Resources and prompts are context, not just actions. readOnlyHint and destructiveHint are client hints. On HTTP, experiments:read and experiments:write are enforced per tool.

A plain HTTP call is the better fit when no model is involved, when a single request/response is enough, or when the extra session and protocol machinery is not worth it. MCP does not replace ordinary API review. The comparison, including a schema side by side, is in docs/mcp-vs-api.md. A short click-through is in docs/demo.md. How a call is dispatched, where the process runs, and how writes are gated is in docs/design-faq.md.

Feature matrix

MCP feature

Where

Tools with pydantic input validation and structured output

power_calc, assign_cells, analyze_lift, check_srm, list_experiments, create_experiment, close_experiment, draft_readout

Tool annotations

readOnlyHint on compute tools; destructiveHint on close_experiment

Static resources

dataset://schema, dataset://summary, experiment://index

Resource templates

experiment://{experiment_id}, experiment://{experiment_id}/results

List-changed and resource-updated notifications

create_experiment and close_experiment publish on the subscription bus and the legacy notification methods

Prompts

design_experiment, write_readout

Completions

prompt arguments metric, population, audience, experiment_id, and the experiment resource templates

Sampling

draft_readout returns Sample(...) when the client declares sampling

Sampling fallback

same tool returns a template when the client does not

Elicitation

close_experiment asks the user to confirm before it writes

Elicitation fallback

if the client cannot ask, the record stays open until the call is repeated with confirm=true

Progress

bootstrap analyze_lift reports replicate counts

Logging

the same tool writes Python logs and deprecated MCP notifications/message entries

Cancellation

the bootstrap loop checks for a cancel signal between batches and awaits, so a cancelled call stops

stdio

experiment-mcp serve

Streamable HTTP

experiment-mcp serve --transport http

OAuth 2.1 resource server

bearer JWT validation, WWW-Authenticate, protected-resource metadata

Scopes

experiments:read and experiments:write checked per tool

Data

src/experiment_mcp/data/synthetic/ is generated by python -m experiment_mcp.synthetic with seed 20261102. metadata.json and SYNTHETIC.md label it as synthetic. The generator plants a redemption-probability gap and a spend shift so the tools have a deterministic file to read. Those planted settings are simulation inputs, not measured lift.

The tables are customers.csv (segment, region, pre-period spend) and exposures.csv (offer WEEKEND_15, arm, redeemed, post-period spend). Assignment uses the same salted hash as assign_cells.

Optional: dunnhumby The Complete Journey

This server does not download that dataset. If you already have a local copy and accept dunnhumby's terms, a rough mapping onto these columns is:

  • customer id and any segment fields you derive, plus a pre-period spend sum, become customers.csv

  • campaign exposure, a redemption flag, and spend during the campaign window become exposures.csv

Point the server at that directory with --data-dir only after you add a metadata.json whose origin is honest about the source. The committed path stays synthetic.

Quickstart

Requires Python 3.12 and uv.

uv sync
uv run experiment-mcp serve

That is stdio. It does not check a token. The security boundary is the process that launched it. The SQLite file defaults to .experiment_mcp/experiments.db in the working directory.

Claude Desktop

{
  "mcpServers": {
    "experiment-mcp": {
      "command": "uv",
      "args": [
        "--directory",
        "/absolute/path/to/experiment-mcp",
        "run",
        "experiment-mcp",
        "serve"
      ]
    }
  }
}

Claude Code

claude mcp add experiment-mcp -- uv --directory /absolute/path/to/experiment-mcp run experiment-mcp serve

Streamable HTTP and auth

HTTP is an OAuth 2.1 resource server. It validates bearer tokens. It does not sign users in. A token must be an RS256 JWT whose iss, aud, and exp match the server, and whose scope string contains the scopes for the tool you call.

experiments:read covers the compute and list tools. experiments:write is required to create or close an experiment, and it also allows read tools. Protected-resource metadata advertises both. The SDK's required_scopes list is applied to every HTTP request, so this server leaves that list empty and enforces the split inside each tool. Unauthenticated calls get 401 with a resource_metadata pointer.

Local token minter (loopback only, not a production identity provider, no end-user login):

uv run experiment-mcp dev-auth

It prints a dev secret and the JWKS URL. In another shell:

export EXPERIMENT_MCP_ISSUER_URL=http://127.0.0.1:9000
export EXPERIMENT_MCP_RESOURCE_URL=http://127.0.0.1:8000/mcp
export EXPERIMENT_MCP_JWKS_URL=http://127.0.0.1:9000/.well-known/jwks.json
uv run experiment-mcp serve --transport http --host 127.0.0.1 --port 8000

Mint a token. The dev secret is the Authorization value on this request. It is not the access token.

curl -s -X POST http://127.0.0.1:9000/token \
  -H "Authorization: Bearer $EXPERIMENT_MCP_DEV_AUTH_SECRET" \
  -H "Content-Type: application/json" \
  -d '{"resource":"http://127.0.0.1:8000/mcp","scope":"experiments:read experiments:write","sub":"local-dev"}'

aud / resource must be exactly EXPERIMENT_MCP_RESOURCE_URL. Issuer and resource URLs must be https unless the host is loopback.

Offline alternative, no dev server: experiment-mcp mint-token writes a private key (mode 0600) and a JWKS file, prints one JWT, and you set EXPERIMENT_MCP_JWKS_PATH instead of EXPERIMENT_MCP_JWKS_URL.

EXPERIMENT_MCP_ALLOW_INSECURE_NO_AUTH=1 starts HTTP with no token check. The process logs a warning. Do not expose that process. It cannot be combined with issuer or JWKS settings.

DNS rebinding protection is on. Extra Host and Origin values go in EXPERIMENT_MCP_ALLOWED_HOSTS and EXPERIMENT_MCP_ALLOWED_ORIGINS (comma-separated). Binding 0.0.0.0 is allowed for a container; add the public host to the allow list.

MCP Inspector

uv run --with "mcp[cli]" mcp dev src/experiment_mcp/app.py:mcp

Or point the Inspector at the stdio command uv run experiment-mcp serve. For HTTP, connect to http://127.0.0.1:8000/mcp and paste a bearer token from the minter.

Docker

The image listens on port 8000 and exits unless the auth environment variables above are set (or insecure mode is set explicitly). The database path inside the image is /data/experiments.db.

docker build -t experiment-mcp .
docker run --rm -p 8000:8000 \
  -e EXPERIMENT_MCP_ISSUER_URL \
  -e EXPERIMENT_MCP_RESOURCE_URL \
  -e EXPERIMENT_MCP_JWKS_URL \
  -e EXPERIMENT_MCP_ALLOWED_HOSTS \
  experiment-mcp

Example conversation

The numbers below are one seeded synthetic draw (seed 20261102, offer WEEKEND_15). They are rounded. They are not a real incrementality result. The generator's planted redemption probabilities are 0.08 and 0.12, plus a 0.01 nudge when pre-period spend is above the median, so an estimate near a 4 point gap is what that draw should look like.

User: How many people per arm do I need to see a 2 point redemption lift from a 10% baseline?

Assistant calls power_calc with metric=proportion, baseline=0.1, mde=0.02. The normal approximation returns 3841 per arm at alpha 0.05 and 80% power, equal allocation, two-sided. That formula does not account for peeking or extra metrics.

User: What happened on the synthetic weekend offer?

Assistant calls analyze_lift with offer_id=WEEKEND_15 and outcome=redeemed. On this file the control redemption rate is about 8.83% (n=2016) and treatment is about 13.16% (n=1984). Absolute lift is about +4.33 percentage points (95% CI about +2.39 to +6.26). Relative lift is about +49%. CUPED on pre-period spend barely moves that interval here, because the covariate is only weakly related to the binary outcome in this draw.

The same call with outcome=post_spend gives control mean spend about 21.09 and treatment about 23.45, absolute lift about +2.36 (95% CI about +2.01 to +2.71). With CUPED, theta is about 0.51 and the interval tightens to about +2.32 to +2.69.

User: Write that up and close the experiment.

Assistant calls create_experiment, reads experiment://{id}/results, then draft_readout. If the client supports sampling, the client's model writes the prose from those facts. If it does not, the tool returns a template that repeats the facts and adds none. close_experiment then asks the user to confirm. Declining leaves the record running.

Tools, briefly

  • power_calc solves for per-arm sample size or absolute MDE. Proportions use an unpooled two-sample normal formula. Means use a known common sigma. Equal allocation, two-sided, no continuity correction.

  • assign_cells maps SHA-256(salt + 0x1f + unit_id) into named weights. Arm order is alphabetical. The same salt and id always get the same arm.

  • analyze_lift reads the synthetic offer or raw arrays. The interval is a normal approximation from each arm's sample variance, or a percentile bootstrap (100 to 10,000 replicates) with progress reports. CUPED uses one pooled theta.

  • check_srm is a chi-square goodness of fit. The default alpha is 0.001. A mismatch is evidence against the intended weights, not a diagnosis.

  • create_experiment, list_experiments, close_experiment are the SQLite record. Closing is destructive and idempotent once closed.

Evals

evals/tasks.json is a small set of natural-language tasks with the tool call they should produce. evals/recorded.json is the recorded call for each task. Offline mode does not call a model:

uv run python -m experiment_mcp.evals

It checks that every recorded call matches the expected tool and arguments exactly, then runs those calls in-process. CI runs this. It does not call a model.

Live mode sends each task to OpenRouter and scores the tool call the model returns. The tool list is tools/list from this server, converted to the OpenAI tool format. Model calls are not executed. The default model is openai/gpt-4o-mini. --model overrides EVAL_MODEL. --repeats runs each task more than once. Results go to evals/results/<model>-<date>.json and a Markdown summary beside it. tasks.json has eight tasks, so the score is indicative, not a benchmark.

OPENROUTER_API_KEY=... uv run python -m experiment_mcp.evals --live --model openai/gpt-4o-mini

In GitHub Actions, add the repository secret OPENROUTER_API_KEY, then open Actions, choose Evals (live), and run the workflow. The workflow is workflow_dispatch only. It writes the summary on the job and uploads evals/results/ as an artifact. It does not run on push or pull request.

Development

uv sync
uv run ruff check .
uv run ruff format --check .
uv run mypy
uv run pytest
uv run python -m experiment_mcp.evals

GitHub Actions runs the same steps.

Regenerate the synthetic files with uv run python -m experiment_mcp.synthetic. Review the diff. Do not treat a regenerated file as new evidence.

Design decisions and limitations

  • The HTTP server validates tokens and publishes protected-resource metadata. The dev minter is a separate localhost process with a shared secret. It is not an authorization-code server and must not be deployed as one.

  • confirm=true on close_experiment is the fallback when elicitation is unavailable. A client can set it without showing a person a form. Treat that as an explicit confirmation from whoever is driving the client, not as a second factor.

  • Sampling and MCP logging are deprecated as of the 2026-07-28 spec (SEP-2577). draft_readout still uses sampling when the client offers it, and otherwise returns the template. Logs always go to the Python logger. Protocol log notifications are still sent for clients that opt in, and they may warn.

  • Lift is a difference in means. There is no sequential testing, no multiple-comparison adjustment, and no CUPED beyond the linear pooled adjustment. Bootstrap intervals are percentiles, not BCa. Relative intervals use the delta method, or bootstrap percentiles when that method is selected.

  • Two arms are analyzed at a time. Assignment can name more than two arms.

  • SQLite is a single local file. There is no multi-replica subscription bus.

  • stdio does not authenticate. Do not point it at a dataset you would not hand to the desktop app.

  • The example figures above are from the committed synthetic generator. They will change if the seed or the generator changes. They are not a benchmark against a public study.

Available Tools

8 tools
analyze_liftAnalyze liftA
Read-only

Estimate absolute and relative lift with a confidence interval.

    Pass offer_id to read the bundled synthetic exposures, or pass control
    and treatment arrays. Bootstrap intervals report progress and stop
    between batches if the client cancels. CUPED uses a pre-period
    covariate when one is available. Reported arm means stay on the
    original scale; the lift uses adjusted outcomes when CUPED is on.
    
NameRequiredDescriptionDefault
seedNo
alphaNo
controlNoControl outcomes.
outcomeNoredeemed or post_spend for an offer. Any label for raw arrays.redeemed
offer_idNoOffer id in the bundled dataset.
ci_methodNonormal
treatmentNoTreatment outcomes.
use_cupedNoAdjust both arms with a pooled CUPED theta.
control_armNocontrol
treatment_armNotreatment
control_covariateNoControl CUPED covariate.
treatment_covariateNoTreatment CUPED covariate.
bootstrap_iterationsNo

Output Schema

NameRequiredDescription
alphaYes
controlYes
outcomeYes
cancelledNo
ci_methodYes
treatmentYes
use_cupedYes
cuped_thetaNo
absolute_liftYes
relative_liftNo
absolute_ci_lowYes
relative_ci_lowNo
absolute_ci_highYes
relative_ci_highNo
bootstrap_iterationsNo

TDQS

A3.8/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnlyHint=true and openWorldHint=false, so safety is covered and the description adds genuine behavioral detail beyond them: bootstrap reports progress and halts between batches on client cancellation, and with CUPED the arm means stay on the original scale while lift uses adjusted outcomes. That scale-semantics note is non-obvious and prevents misreading results. It does not mention error behavior when neither offer_id nor arrays are supplied.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with the purpose, then input modes, then behavioral caveats — a sensible order with no filler sentences. It is dense but every sentence carries information; a short lead-in labeling the CUPED sentences would improve scannability slightly.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

An output schema exists, so return values need not be explained, and the primary call pattern is covered. However, for a 13-parameter statistical tool with only 54% schema coverage, the description leaves the no-argument default path and the arm-label parameters unexplained, which an agent would need to select correctly.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 54% across 13 parameters, and the description compensates for some: offer_id vs control/treatment arrays, the CUPED covariate, and bootstrap ci_method. It says nothing about seed, bootstrap_iterations, alpha, outcome labels, or the control_arm/treatment_arm selectors, leaving several parameters meaningful only via their schema titles.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The opening sentence names a specific verb and output ('Estimate absolute and relative lift with a confidence interval'), which is unambiguous against siblings like power_calc or check_srm. It does not explicitly contrast itself with those siblings, so an agent must infer the boundary rather than being routed.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description clearly states the two invocation modes: 'Pass offer_id to read the bundled synthetic exposures, or pass control and treatment arrays.' That is a real either/or routing instruction. It stops short of saying when to prefer this tool over power_calc or draft_readout, and gives no exclusion conditions.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

assign_cellsAssign experiment cellsA
Read-only

Assign units to arms with a salted SHA-256 bucket.

Arms are ordered by name. The same salt and unit id always map to the same arm. This does not write an experiment record.

NameRequiredDescriptionDefault
saltYesExperiment salt. Changing it reassigns units.
splitsYesArm name to traffic weight. Weights must sum to 1.
unit_idsYesStable unit ids, usually customer ids.

Output Schema

NameRequiredDescription
saltYes
countsYes
arm_orderYes
assignmentsYes

TDQS

A4.2/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnlyHint=true and openWorldHint=false, and the description adds genuinely new behavioral facts: determinism ('the same salt and unit id always map to the same arm'), ordering ('arms are ordered by name'), and the explicit non-persistence guarantee. It stops short of covering output shape, but an output schema exists, so the remaining gap is minor.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Three short sentences, front-loaded with the core action and mechanism, and no filler. The SHA-256 detail is arguably implementation-level, but it is what justifies the determinism claim, so it earns its place.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a read-only, three-parameter computation tool with full schema coverage and an output schema, the description covers purpose, determinism, ordering, and the non-write guarantee. Nothing an agent needs in order to invoke it correctly is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the baseline would be 3, but the description adds real meaning: it explains the stability contract between salt and unit id and clarifies that splits are matched against name-ordered arms. That ties the three parameters together in a way the schema alone does not.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource ('Assign units to arms') plus the mechanism ('salted SHA-256 bucket'), which immediately separates it from siblings like check_srm, analyze_lift, or create_experiment. An agent can tell this is a deterministic assignment computation, not an experiment mutation.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The negative guidance 'This does not write an experiment record' usefully implies this is a compute/preview step rather than a persistence step, but it never names an alternative or states the condition under which an agent should call this versus create_experiment or check_srm. Usage is only inferable, not spelled out.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

check_srmCheck sample ratioA
Read-only

Chi-square test of observed arm counts against intended weights.

A mismatch means the counts are unlikely given the weights. It does not identify the cause.

NameRequiredDescriptionDefault
alphaNoMismatch threshold. 0.001 is the usual SRM bar.
countsYesObserved assignments per arm.
expected_weightsYesIntended traffic weights. Must sum to 1 and match the count keys.

Output Schema

NameRequiredDescription
alphaYes
countsYes
p_valueYes
mismatchYes
chi_squareYes
expected_countsYes
expected_weightsYes
degrees_of_freedomYes

TDQS

A3.5/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnlyHint=true and openWorldHint=false, so safety is covered. The description adds genuine value with the caveat that a mismatch does not identify a cause, but says nothing about minimum sample size requirements or how borderline results near alpha should be interpreted.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two short sentences with zero waste; the operation is front-loaded and the interpretation caveat follows. Every sentence earns its place.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

An output schema and full annotation coverage reduce the burden, but the description still lacks the when-to-use context and sample-size expectations an agent needs to decide whether to call this versus power_calc. Adequate, not complete.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, including the alpha default and its 'usual SRM bar' note, so the schema carries the parameter burden and baseline 3 applies. The description adds no meaning about counts or expected_weights beyond the schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource: a chi-square test of observed arm counts against intended weights. An agent can tell this apart from power_calc or analyze_lift. Minor gap: the tool name's abbreviation (SRM) is never expanded, so the domain term is only implied by 'A mismatch means...'.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Usage is implied by the statistical operation but there is no explicit when-to-use, no prerequisites, and no routing to siblings such as power_calc or assign_cells. The only guidance given is interpretive (a mismatch does not identify the cause), not procedural.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

close_experimentClose experimentA
DestructiveIdempotent

Mark an experiment closed after the user confirms.

The confirmation form is elicited when the client supports it. If the client does not, the experiment stays open until this tool is called again with confirm=true. Declining or cancelling the form does not close it.

NameRequiredDescriptionDefault
confirmNoLeave false to ask the user through elicitation. Set true only after a person has agreed, which is the fallback when the client cannot show a form.
experiment_idYes

Output Schema

NameRequiredDescription
closedYes
detailYes
reasonYes
statusNo
experiment_idYes

TDQS

A4.1/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare destructiveHint=true, idempotentHint=true, and readOnlyHint=false, so the safety profile is covered. The description adds genuinely non-obvious behavior beyond that: the elicitation form is client-dependent, and a failed/declined confirmation leaves the experiment open, meaning the call may be a no-op rather than a partial mutation.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The confirmation contract is front-loaded in the first sentence and the fallback path follows logically. The passive phrasing ("The confirmation form is elicited when the client supports it") is slightly indirect, but nothing is redundant or padded.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With an output schema present, return values need no explanation, and the description fully covers the two-phase confirmation protocol an agent must understand to avoid a silent no-op. It omits whether closing is reversible and what permissions are needed, which are minor given destructiveHint is already set.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 50%: the confirm parameter is documented in the schema, and the description reinforces its meaning (only true after a person has agreed) as the elicitation fallback. experiment_id carries no description anywhere, though its meaning is self-evident from the name, so this lands at the baseline.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The first sentence gives a specific verb and resource ("Mark an experiment closed") and adds the gating condition ("after the user confirms"). It is trivially separable from siblings such as create_experiment, list_experiments, and draft_readout without opening any schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description clearly states when to call it (after user confirmation) and what to do when elicitation is unavailable (call again with confirm=true), plus a negative case (declining/cancelling does not close). It stops short of naming sibling alternatives, but for a terminal-state mutation the when/when-not guidance is explicit.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

create_experimentCreate experimentC

Store a running experiment. This does not assign traffic by itself.

NameRequiredDescriptionDefault
nameYes
saltNo
notesNo
offer_idNo
hypothesisYes
traffic_splitNo
primary_metricYes

Output Schema

NameRequiredDescription
idYes
nameYes
saltYes
notesYes
statusYes
offer_idYes
closed_atYes
created_atYes
hypothesisYes
traffic_splitYes
primary_metricYes

TDQS

C2.7/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

The annotations already declare this as a non-readonly, non-idempotent, non-destructive write. The description adds useful scope by clarifying that traffic assignment does not happen here, preventing an incorrect assumption, but it does not explain permissions, validation behavior, or what the traffic_split/salt fields do.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The two sentences are tight and front-loaded, with no filler. It is appropriately brief, though that brevity comes at the cost of leaving important parameter and workflow context unexplained.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness2/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Although an output schema and annotations reduce the need to explain return values and safety, the description is too thin for a seven-parameter write tool with 0% schema description coverage. It gives no help on required fields, optional configuration such as traffic_split or salt, or how this tool relates to the broader experiment workflow.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters1/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0% across seven parameters, and the description adds no meaning for name, hypothesis, primary_metric, salt, traffic_split, offer_id, or notes. This is the same failure mode as the low-calibration example where three parameters at 0% coverage scored a 1; here the gap is larger.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description names a specific verb ('Store') and resource ('experiment'), and its caveat 'does not assign traffic by itself' helps separate it from assign_cells. It is clear enough to identify the tool, though 'running experiment' is slightly vague about whether it is creating a new record or persisting an existing one.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The only usage-related information is the negative constraint that it does not assign traffic, which hints that assignment is a separate step. There is no explicit guidance on when to use this versus list_experiments, close_experiment, or assign_cells, nor any prerequisite or sequencing guidance beyond that single caveat.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

draft_readoutDraft a readoutA
Read-only

Ask the client model to summarize an experiment, with a template fallback.

Sampling is deprecated in the 2026-07-28 spec and many clients do not offer it. Those clients get a deterministic draft built from the same facts.

NameRequiredDescriptionDefault
experiment_idYes

Output Schema

NameRequiredDescription
textYes
factsYes
sourceYes
experiment_idYes

TDQS

A3.6/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already cover the safety profile (readOnlyHint, openWorldHint=false), and the description adds genuinely useful behavioral context that structured fields cannot convey: output may come from client-side sampling or a deterministic template fallback, so results vary in nature across clients. It still does not say whether the LLM path is validated, whether failures fall back silently, or what quality/consistency differences to expect.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two short sentences, front-loaded with the primary action and followed by the fallback caveat. No filler, though the line breaks and stray indentation in the source make it slightly less tidy than it could be.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

The tool is simple, one parameter, and an output schema exists so return values need not be explained. However, the nondeterministic-vs-template duality is the tool's defining behavioral trait and the description leaves it ambiguous whether the caller can detect which path was taken or how to force the deterministic one.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

A single required experiment_id with 0% schema description coverage; the description does not explain the identifier's form, where to obtain it, or whether it must reference an existing experiment. With only one, self-descriptively named parameter the gap is small, but the low coverage means the description should have compensated and does not.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource: 'summarize an experiment' producing a readout draft. None of the siblings (power_calc, analyze_lift, check_srm, list_experiments, create_experiment, close_experiment) overlap with summarization, so the purpose is distinguishable without opening schemas, though the description never explicitly contrasts itself with the analysis siblings an agent might otherwise reach for.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Usage context is only implied: you call it to get a readout draft for an experiment. There is no statement of when to prefer this over analyze_lift or list_experiments, no preconditions (does the experiment need results first?), and no note on whether an experiment_id that doesn't exist is an error.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

list_experimentsList experimentsC
Read-only

List experiment records stored in this server's SQLite file.

NameRequiredDescriptionDefault
statusNo

Output Schema

NameRequiredDescription
experimentsYes

TDQS

C2.8/5.0
Behavior2/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnlyHint=true and openWorldHint=false, and the mention of a local SQLite file merely restates that the data is local. The description doesn't disclose the default behavior when status is omitted, the ordering, whether results are paginated, or what an empty result means — behavioral traits the annotations don't cover.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

One short, front-loaded sentence with no filler or redundancy. It is efficient, though the brevity comes at the cost of the missing parameter and usage detail noted above.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

An output schema exists, so return-value explanation isn't required, and the tool is simple with one optional parameter. Still, for a listing tool the description should say what the default listing contains and how the status filter behaves, which it omits.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters2/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0%, so the description must carry the burden for the single status parameter, but it never mentions status at all. It doesn't explain the running/closed enum values or that the null default returns all experiments, leaving the parameter's meaning undocumented in both places.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description gives a specific verb and resource: "List experiment records." The clause about the SQLite file adds scope, though it doesn't differentiate this tool from any sibling or note that the other siblings are mutation/analysis tools rather than other listers.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

There is no when-to-use guidance at all: nothing says to call this before close_experiment or draft_readout, and nothing indicates whether an unfiltered call returns everything. The agent must infer usage entirely from the name.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

power_calcSample size or MDEA
Read-only

Estimate equal-allocation sample size or MDE for a two-sided test.

    Provide exactly one of mde or n_per_arm. The normal approximation does
    not include peeking, multiple metrics, or a finite-population correction.
    
NameRequiredDescriptionDefault
mdeNoAbsolute difference to detect. Omit when solving for MDE.
alphaNoTwo-sided type I error.
powerNoDesired power, 1 - beta.
sigmaNoOutcome standard deviation. Required for mean designs.
metricYes
baselineNoControl rate. Required for proportion designs.
n_per_armNoPer-arm sample size. Omit when solving for sample size.

Output Schema

NameRequiredDescription
mdeNoAbsolute difference (treatment minus control) the design can detect.
alphaYes
powerYes
sigmaNo
metricYes
formulaYes
baselineNo
n_per_armNo
allocationNo
solved_forYes
alternativeNo

TDQS

A4.1/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnlyHint=true and openWorldHint=false, so safety is covered. The description adds substantive behavior beyond that: equal allocation only, two-sided test, and an explicit list of what the normal approximation ignores (peeking, multiple metrics, finite-population correction). It does not describe output shape, but an output schema exists to carry that.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Three short sentences, nothing redundant. The core purpose is front-loaded and the constraint and caveat follow in priority order.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a 7-parameter statistical tool with an output schema, the description covers the solve-mode constraint and the modeling limitations that an agent must know to use results correctly. Return values need not be explained given the output schema, and the remaining parameter detail is carried by 86% schema coverage.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 86%, so the per-parameter meanings are largely handled structurally and a baseline 3 applies. The description earns an extra point by stating a cross-parameter constraint the schema cannot express — exactly one of mde or n_per_arm must be supplied — which is the single most likely invocation error for this tool.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description opens with a specific verb and resource: 'Estimate equal-allocation sample size or MDE for a two-sided test.' That tells an agent both what is computed and the two solve modes, which no sibling tool covers. It does not name or differentiate against siblings, but the purpose itself is unambiguous.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

'Provide exactly one of mde or n_per_arm' is an explicit operational rule that selects the solve direction, and the caveat sentence tells the agent when the result is not valid (peeking, multiple metrics, finite-population correction). It stops short of naming an alternative approach for those excluded cases, so there is no full when-to-use-instead routing.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

  1. 8 tool updatesv0.1.0
    • First observedanalyze_lift
    • First observedassign_cells
    • First observedcheck_srm
    • First observedclose_experiment
    • First observedcreate_experiment
    • First observeddraft_readout
    • First observedlist_experiments
    • First observedpower_calc

TDQS

A3.6/5.0

Scored across 8 tools

Disambiguation5/5

Each tool targets a distinct phase of the experiment workflow: power planning, assignment, lift analysis, SRM checking, record lifecycle, and readout drafting. The descriptions explicitly clarify boundary cases, such as assign_cells not writing a record and create_experiment not assigning traffic, which further reduces misselection.

Naming Consistency4/5

All tool names use consistent snake_case, and most follow a verb_noun pattern such as assign_cells, analyze_lift, and create_experiment. The main minor deviation is power_calc, which uses a noun abbreviation rather than a clear verb form like calculate_power.

Tool Count5/5

Eight tools are well-scoped for an experiment management and analysis server. Each tool covers a meaningful part of the workflow without obvious redundancy or inflated surface area.

Completeness4/5

The surface covers experimental design, assignment, analysis, SRM checks, record creation/listing/closure, and readout drafting. Minor gaps remain, such as no get_experiment or update_experiment operation, but the core lifecycle is well represented.

Maintenance

ActivityMaintained
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers

  • A
    license
    A
    quality
    A
    maintenance
    Provides MCP access to plan and execute reproducible computational experiments with immutable execution plans, artifact provenance, verification checks, and support for direct, Slurm, and PBS backends.
    17
    Apache 2.0
  • A
    license
    Not graded
    quality
    B
    maintenance
    Enables statistical A/B testing by computing Welch's two-sample t-test, Satterthwaite degrees of freedom, and two-tailed p-values, alongside time-series forecasting, anomaly detection, regression, and PCA through MCP tools.
    7
    MIT
  • A
    license
    Not graded
    quality
    B
    maintenance
    Connects MCP clients to a hosted experimentation platform so agents can start, pause, stop, and roll back live website A/B tests, inspect Bayesian results, and capture hypotheses.
    226 npm
    MIT