MCP servers
Call the tools of MCP servers (Model Context Protocol) as an AI assistant calls them, and check their results, with the tools’ own schemas as the contract. Read their resources and request their prompts.
Given the parcels mcp server with the following properties: | url | http://localhost:8400/mcp |When the track_parcel tool is called on the parcels mcp server with the following arguments: | reference | PX-MCP-9101 |Then the track_parcel tool's result is not an errorAnd the track_parcel tool's result has the following properties: | status | OUT_FOR_DELIVERY |- Over stdio or streamable HTTP: a
commandruns the server for the scenario, which ends it when it ends; aurlreaches it. The session agrees on the protocol version with the server: 2026-07-28, or an earlier one the server speaks. - Run it as its clients do: a server assistants start over stdio is the scenario’s
command, like a command-line tool, and not a service in axx.yaml; a server that runs on its own over HTTP is a service, and theurlreaches it. - A tool’s schemas are the contract: its arguments take the types its input schema gives them (
10115is text for a string), and are checked against it before the call; its structured result is checked against its output schema. - Two kinds of failure: a tool’s result that is an error (the tool ran, and says what went wrong), and a call the server refused (an unknown tool, invalid parameters), with its JSON-RPC error code.
Findings have keys and levels, like the REST pack’s OpenAPI findings:
| Key | Found when |
|---|---|
validation.arguments.schema.<keyword> |
the arguments break the tool’s input schema (required, type, enum…) |
validation.result.schema.<keyword> |
the structured result breaks the tool’s output schema |
validation.result.missing |
a tool with an output schema answered no structured result |
- Levels:
ERROR(orFAIL) fails the step,WARNandINFOlog the finding,IGNOREdrops it. Every finding is anERRORunlesspacks.mcp.levelsin axx.yaml, or the scenario’sthe MCP validation levels are:, says otherwise. - Secrets stay secret:
${env:..}values in the properties are masked in logs and failures, and${token:..}names a token of the scenario.
Add it to a project with axx pack add mcp.
Settings
Section titled “Settings”In axx.yaml, under packs.mcp:
| Setting | Takes | Values |
|---|---|---|
levels |
Validation key -> ERROR (or FAIL), WARN, INFO or IGNORE, for every scenario. | names and values |
mcp.server
Section titled “mcp.server”Given the {word} mcp server with the following properties: | property | value |The table may start with the row naming its columns, as above, or go straight to its rows.
Register an MCP server under a name, and connect to it: over stdio (a command the scenario runs, and ends when it ends) or over streamable HTTP (a url). The session agrees on the protocol version with the server.
- A server its clients start over stdio is the scenario’s
command, as assistants run it, not a service in axx.yaml; a server that runs on its own over HTTP is a service, which theurlreaches.
| Parameter | Takes | For example |
|---|---|---|
{word} |
one word, with no spaces | parcels, PX-4101 |
| Property | Takes | Default |
|---|---|---|
command |
the command that runs the server over stdio, split into words without a shell, like docker compose exec -T app parcels mcp |
|
url |
the server’s streamable HTTP endpoint | |
dir |
the folder the command runs in, relative to the project | a folder of the scenario's own |
env.<NAME> |
an environment variable of the command, such as env.PARCELS_DB_URL |
|
header.<name> |
a header sent with every request to the url, such as header.Authorization |
|
timeout |
how long connecting, or a call, may take, like 10s |
30s |
protocol version |
the MCP version to speak, to check that the server still answers an earlier one, like 2025-06-18 |
the latest the server speaks |
A command or a url is required. Values are expanded (${env:..}, ${sys:..}, ${token:..}); what ${env:..} references expand to is masked everywhere.
Example:
Given the parcels mcp server with the following properties: | url | http://localhost:8400/mcp |Given the parcels mcp server with the following properties: | command | ${sys:parcels.mcp} | | dir | ../infra |Since 0.1.5.
mcp.tool
Section titled “mcp.tool”Then the {word} mcp server has the {word} tool[[ with the following properties:]]Check that the server lists the tool, and that it has the table’s properties: what an AI assistant reads before it calls the tool, like its description and its hints.
| Parameter | Takes | For example |
|---|---|---|
{word} |
one word, with no spaces | parcels, PX-4101 |
A row is a path into the tool as the server lists it (description, annotations.readOnlyHint, inputSchema.required[0]) and its value.
Variants, the parts in [[...]] said or left out:
the {word} mcp server has the {word} toolthe {word} mcp server has the {word} tool with the following properties:
Example:
Then the parcels mcp server has the track_parcel toolThen the parcels mcp server has the track_parcel tool with the following properties: | title | Track a parcel | | annotations.readOnlyHint | true |Since 0.1.5.
mcp.noTool
Section titled “mcp.noTool”Then the {word} mcp server does not have the {word} toolCheck that the server does not list the tool: one it must not offer an AI assistant.
| Parameter | Takes | For example |
|---|---|---|
{word} |
one word, with no spaces | parcels, PX-4101 |
Example:
Then the parcels mcp server does not have the cancel_parcel toolSince 0.1.5.
mcp.call
Section titled “mcp.call”When the {word} tool is called on the {word} mcp server[[ with the following arguments:]]Call the tool, with the table’s arguments when the step has one. The arguments are checked against the tool’s input schema before they are sent, and its structured result against its output schema.
- A result that is an error does not fail the step: check it with
the {word} tool's result is an error. - Nor does a call the server refuses: check it with
the {word} tool's call failed with the error code {int}.
| Parameter | Takes | For example |
|---|---|---|
{word} |
one word, with no spaces | parcels, PX-4101 |
Each row is an argument, or a path into one (address.postcode), and its value: a value takes the type the tool’s input schema gives its argument, double quotes make it text, null is null and undefined leaves it out.
Variants, the parts in [[...]] said or left out:
the {word} tool is called on the {word} mcp serverthe {word} tool is called on the {word} mcp server with the following arguments:
Example:
When the track_parcel tool is called on the parcels mcp server with the following arguments: | reference | PX-MCP-9101 |Since 0.1.5.
mcp.call.file
Section titled “mcp.call.file”When the {word} tool is called on the {word} mcp server with the {filepath} argumentsCall the tool with the arguments of a file of the project: a JSON or YAML object of the arguments’ names and values. They are checked against the tool’s input schema before they are sent.
| Parameter | Takes | For example |
|---|---|---|
{word} |
one word, with no spaces | parcels, PX-4101 |
{filepath} |
a file of the project: a path relative to the feature file’s directory, the resources directories or axx.yaml’s directory (looked for in that order), or an absolute path, quoted when it has a space |
seeds/parcels.yaml, kafka/scan-delivered.json |
Example:
When the hold_parcel tool is called on the parcels mcp server with the mcp/hold-parcel.json argumentsSince 0.1.5.
mcp.notError
Section titled “mcp.notError”Then the {word} tool's result[[ on the {word} mcp server]] is not an errorCheck that the tool answered, with a result that is not an error.
The result is the tool’s latest in the scenario, on the server the step names, or on the only server the scenario called the tool on.
| Parameter | Takes | For example |
|---|---|---|
{word} |
one word, with no spaces | parcels, PX-4101 |
Variants, the parts in [[...]] said or left out:
the {word} tool's result is not an errorthe {word} tool's result on the {word} mcp server is not an error
Example:
Then the track_parcel tool's result is not an errorSince 0.1.5.
mcp.error
Section titled “mcp.error”Then the {word} tool's result[[ on the {word} mcp server]] is an errorCheck that the tool answered with a result that is an error: the tool ran, and says what went wrong, for the assistant to tell the user or try again. Check what it says with contains.
The result is the tool’s latest in the scenario, on the server the step names, or on the only server the scenario called the tool on.
| Parameter | Takes | For example |
|---|---|---|
{word} |
one word, with no spaces | parcels, PX-4101 |
Variants, the parts in [[...]] said or left out:
the {word} tool's result is an errorthe {word} tool's result on the {word} mcp server is an error
Example:
Then the hold_parcel tool's result is an errorSince 0.1.5.
mcp.contains
Section titled “mcp.contains”Then the {word} tool's result[[ on the {word} mcp server]] contains {string}Check that the tool’s result has a text: in its content (text, and embedded resources’ text), or in its structured content’s JSON.
The result is the tool’s latest in the scenario, on the server the step names, or on the only server the scenario called the tool on.
| Parameter | Takes | For example |
|---|---|---|
{word} |
one word, with no spaces | parcels, PX-4101 |
{string} |
text in double or single quotes, which the step leaves out | "Get a quote", 'Express' |
Variants, the parts in [[...]] said or left out:
the {word} tool's result contains {string}the {word} tool's result on the {word} mcp server contains {string}
Example:
Then the hold_parcel tool's result contains 'already out for delivery'Since 0.1.5.
mcp.properties
Section titled “mcp.properties”Then the {word} tool's result[[ on the {word} mcp server]] has the following properties: | property | value |The table may start with the row naming its columns, as above, or go straight to its rows.
Check the tool’s structured result: its structured content, or the JSON of its text for a tool that answers JSON as text.
- Paths are dotted (
lastScan.location) or JSONPath;nullandundefinedwork as in the other property steps. - The result is the tool’s latest in the scenario, on the server the step names, or on the only server the scenario called the tool on.
| Parameter | Takes | For example |
|---|---|---|
{word} |
one word, with no spaces | parcels, PX-4101 |
A row is a path into the structured result, and its value.
Variants, the parts in [[...]] said or left out:
the {word} tool's result has the following properties:the {word} tool's result on the {word} mcp server has the following properties:
Example:
Then the track_parcel tool's result has the following properties: | status | OUT_FOR_DELIVERY | | lastLocation | Leipzig |Since 0.1.5.
mcp.refused
Section titled “mcp.refused”Then the {word} tool's call[[ on the {word} mcp server]] failed with the error code {int}Check that the server refused the call, with a JSON-RPC error of that code: -32602 for a tool it does not have or invalid parameters, -32603 for an internal error.
The result is the tool’s latest in the scenario, on the server the step names, or on the only server the scenario called the tool on.
| Parameter | Takes | For example |
|---|---|---|
{word} |
one word, with no spaces | parcels, PX-4101 |
{int} |
a whole number | 200, 3 |
Variants, the parts in [[...]] said or left out:
the {word} tool's call failed with the error code {int}the {word} tool's call on the {word} mcp server failed with the error code {int}
Example:
Then the cancel_parcel tool's call failed with the error code -32602Since 0.1.5.
mcp.resource.read
Section titled “mcp.resource.read”When the {word} resource is read from the {word} mcp serverRead a resource of the server, by its URI.
| Parameter | Takes | For example |
|---|---|---|
{word} |
one word, with no spaces | parcels, PX-4101 |
Example:
When the parcels://PX-MCP-9103/label resource is read from the parcels mcp serverSince 0.1.5.
mcp.resource.contains
Section titled “mcp.resource.contains”Then the {word} resource contains {string}Check that the text of the resource, as the scenario last read it, has a text.
| Parameter | Takes | For example |
|---|---|---|
{word} |
one word, with no spaces | parcels, PX-4101 |
{string} |
text in double or single quotes, which the step leaves out | "Get a quote", 'Express' |
Example:
Then the parcels://PX-MCP-9103/label resource contains 'PX-MCP-9103'Since 0.1.5.
mcp.resource.properties
Section titled “mcp.resource.properties”Then the {word} resource has the following properties: | property | value |The table may start with the row naming its columns, as above, or go straight to its rows.
Check the resource, as the scenario last read it, when its text is JSON.
| Parameter | Takes | For example |
|---|---|---|
{word} |
one word, with no spaces | parcels, PX-4101 |
A row is a path into the resource’s JSON, and its value.
Example:
Then the parcels://PX-MCP-9103/label resource has the following properties: | reference | PX-MCP-9103 | | service | EXPRESS |Since 0.1.5.
mcp.prompt
Section titled “mcp.prompt”When the {word} prompt is requested from the {word} mcp server[[ with the following arguments:]]Request a prompt of the server, with the table’s arguments when the step has one: the messages it offers an AI assistant.
| Parameter | Takes | For example |
|---|---|---|
{word} |
one word, with no spaces | parcels, PX-4101 |
A row is an argument of the prompt, and its value, as text.
Variants, the parts in [[...]] said or left out:
the {word} prompt is requested from the {word} mcp serverthe {word} prompt is requested from the {word} mcp server with the following arguments:
Example:
When the delivery_update prompt is requested from the parcels mcp server with the following arguments: | reference | PX-MCP-9104 |Since 0.1.5.
mcp.prompt.contains
Section titled “mcp.prompt.contains”Then the {word} prompt contains {string}Check that the text of the prompt’s messages, as the scenario last requested it, has a text.
| Parameter | Takes | For example |
|---|---|---|
{word} |
one word, with no spaces | parcels, PX-4101 |
{string} |
text in double or single quotes, which the step leaves out | "Get a quote", 'Express' |
Example:
Then the delivery_update prompt contains 'PX-MCP-9104'Since 0.1.5.
mcp.levels
Section titled “mcp.levels”Given the MCP validation levels are: | validation key | level |The table may start with the row naming its columns, as above, or go straight to its rows.
Relax, for this scenario, the checks of tools’ arguments and results against their schemas.
A row is a validation key and its level: ERROR (or FAIL), WARN, INFO or IGNORE. The key must be one the pack reports, or a prefix of such keys, like validation.arguments: any other fails the step, which names the closest keys.
Example:
Given the MCP validation levels are: | validation.arguments.schema.required | IGNORE |Since 0.1.5.