Documentation
¶
Overview ¶
Package conform implements the end-to-end MCP conformance suite. The `mcp-tui conform <url|cmd>` subcommand runs a curated battery of MCP protocol scenarios plus every verify probe, prints a per-scenario PASS/ FAIL summary, and (when `--report-junit <file>` is set) writes a JUnit XML report consumable by CI dashboards.
Scenario authors implement the Scenario interface; the dispatcher in scenarios.go resolves a name to a typed Scenario value and runs it against the supplied Target. JUnit XML marshaling lives in junit.go.
Index ¶
- Variables
- func AllPassed(results []ScenarioResult) bool
- func CountResults(results []ScenarioResult) (passed, failed, skipped int)
- func IsScenarioName(name string) bool
- func WriteJUnitReport(w io.Writer, suite JUnitTestSuite) error
- type JUnitFailure
- type JUnitTestCase
- type JUnitTestSuite
- type Runner
- type ScenarioResult
- type Target
Constants ¶
This section is empty.
Variables ¶
var AllScenarios = []string{
"initialize",
"tools.list",
"tools.call",
"tools.call.isError",
"resources.list",
"resources.read",
"resources.templates.list",
"prompts.list",
"prompts.get",
"sampling.createMessage",
"elicitation.create",
"notifications",
"completion.complete",
"verify.cross-origin",
"verify.dns-rebind",
"verify.content-type",
"verify.origin-header",
"verify.mcp-method-headers",
"verify.seterror-content",
}
AllScenarios is the canonical, ordered list of conformance scenarios. The order doubles as the iteration order — text and JUnit reports both emit results in this sequence so dashboards can diff runs over time.
Sections:
- Protocol scenarios (initialize through completion/complete) — driven against the connected target via mcp.Service.
- Verify probes — six security/behavior probes from internal/cli/verify prefixed with "verify." for namespacing.
Functions ¶
func AllPassed ¶
func AllPassed(results []ScenarioResult) bool
AllPassed returns true when every non-skipped scenario in results passed. An empty slice counts as failure (consistent with verify.AllPassed) so a run that produced zero scenarios exits non-zero. Skipped scenarios do NOT block a green exit — `--scenario foo` against a target without `foo`'s preconditions should be a successful no-op.
func CountResults ¶
func CountResults(results []ScenarioResult) (passed, failed, skipped int)
CountResults tallies (passed, failed, skipped). Passed includes Skipped because Skipped is a sub-state of Pass — but the dedicated counter lets the text report show "5 passed, 1 failed, 2 skipped" instead of glomming them together.
func IsScenarioName ¶
IsScenarioName reports whether name is one of AllScenarios. The CLI uses this to reject typos in --scenario before connecting.
func WriteJUnitReport ¶
func WriteJUnitReport(w io.Writer, suite JUnitTestSuite) error
WriteJUnitReport marshals suite to w as indented JUnit XML with the canonical XML 1.0 prolog. Returns an error if the underlying writer fails or marshaling fails (which only happens when the struct contains an un-marshalable field, never in our case — but we surface it anyway so callers don't have to wrap).
The output is deterministic for a given suite value: encoding/xml preserves struct field order and slice element order.
Types ¶
type JUnitFailure ¶
type JUnitFailure struct {
XMLName xml.Name `xml:"failure"`
Message string `xml:"message,attr"`
Type string `xml:"type,attr"`
Body string `xml:",chardata"`
}
JUnitFailure holds the failure metadata. Message is the short summary shown in CI dashboards; Type categorises the failure ("Failure" vs "Error" — we always emit "Failure" because conformance is binary). The chardata body carries the detail (stack-trace equivalent).
type JUnitTestCase ¶
type JUnitTestCase struct {
XMLName xml.Name `xml:"testcase"`
ClassName string `xml:"classname,attr"`
Name string `xml:"name,attr"`
Time string `xml:"time,attr"`
Failure *JUnitFailure `xml:"failure,omitempty"`
}
JUnitTestCase represents a single <testcase> element. ClassName is the suite name (we use "mcp-tui.conform"), Name is the scenario identifier. Time is elapsed-seconds in fixed-point form. Failure is non-nil when the scenario failed; SystemOut carries any captured diagnostic output (skipped for now to keep payloads compact).
type JUnitTestSuite ¶
type JUnitTestSuite struct {
XMLName xml.Name `xml:"testsuite"`
Name string `xml:"name,attr"`
Tests int `xml:"tests,attr"`
Failures int `xml:"failures,attr"`
Errors int `xml:"errors,attr"`
Time string `xml:"time,attr"` // seconds, fixed-point — Surefire format
Cases []JUnitTestCase `xml:"testcase"`
}
JUnitTestSuite is the top-level <testsuite> element of a JUnit-XML report.
We follow the de-facto Maven Surefire schema (the most widely consumed shape): a testsuite element with attributes for counts and elapsed time, containing testcase elements. Each testcase has classname (the suite), name (the scenario), and time. A failing testcase contains a <failure> element with the failure message and stack/text body.
The struct tags use `xml:"...,attr"` for attributes and `xml:",chardata"` for element bodies. Field order in the struct dictates emission order because encoding/xml marshals fields top-to-bottom.
func BuildJUnitReport ¶
func BuildJUnitReport(suiteName string, results []ScenarioResult) JUnitTestSuite
BuildJUnitReport converts the per-scenario ScenarioResult slice into a JUnit-shaped testsuite. The conversion is pure: same input always produces the same output, scenarios are emitted in the slice's order so the conform-run report mirrors the runner's iteration order.
suiteName populates the testsuite name attribute and is also used as the classname on every testcase so CI dashboards group them under one suite. Pass "mcp-tui.conform" for the canonical run.
type Runner ¶
type Runner struct {
// contains filtered or unexported fields
}
Runner executes scenarios against a Target. It owns a single mcp.Service that is connected lazily on first protocol-level scenario and reused for the remainder of the run, so a full conform pass against an stdio target pays the spawn-and-handshake cost exactly once.
Verify probes never share Runner.svc — they drive their own per-probe connections so a hung verify doesn't stall the whole suite.
Runner is not safe for concurrent use; the conform suite runs scenarios sequentially by design (so failures are easy to read in a CI log).
func NewRunner ¶
NewRunner builds a Runner for the supplied target without connecting. The first protocol-level scenario triggers connection.
type ScenarioResult ¶
type ScenarioResult struct {
Name string `json:"name"`
Pass bool `json:"pass"`
Skipped bool `json:"skipped,omitempty"`
Error string `json:"error,omitempty"`
Detail string `json:"detail,omitempty"`
Elapsed time.Duration `json:"elapsed"`
}
ScenarioResult is the typed outcome of a single conformance scenario.
Name — scenario identifier (matches the --scenario flag value)
Pass — true if the scenario satisfied its acceptance criteria
Skipped — true if the scenario was skipped because the target lacks
the required capability (counts as Pass for exit-code purposes)
Error — short, single-line summary shown in the text report
Detail — multi-line diagnostic body (goes into JUnit <failure>)
Elapsed — wall time spent running the scenario
type Target ¶
type Target struct {
// URL is the streamable-HTTP endpoint for HTTP-class targets.
URL string
// Command + Args are the stdio command for stdio targets.
Command string
Args []string
// SamplingStub, when non-empty, is installed via
// sampling.NewTextStubHandler before connect so the sampling scenario
// (which calls a tool that triggers sampling/createMessage) gets a
// canned reply.
SamplingStub string
// ElicitStub, when non-empty, is installed via
// elicitation.NewJSONStubHandler before connect so the elicitation
// scenario gets a canned reply.
ElicitStub string
// SamplingTriggerTool overrides the default "sampleLLM" tool name used
// by the sampling scenario. The scenario calls this tool to provoke a
// server-initiated sampling/createMessage request.
SamplingTriggerTool string
// ElicitTriggerTool overrides the default "startElicitation" tool name
// used by the elicitation scenario.
ElicitTriggerTool string
// CompletionPromptName is the prompt name (or resource template URI when
// CompletionRefIsResource=true) used to drive completion/complete. When
// empty the scenario falls back to the first prompt with arguments
// returned by prompts/list.
CompletionPromptName string
CompletionRefIsResource bool
CompletionArgumentName string
CompletionArgumentValue string
}
Target describes what the conformance suite drives. URL/Command/Args have the same meaning as verify.Target — when both are set, HTTP-class scenarios use URL and stdio-class scenarios use the command (mirroring `verify`).
SamplingStub and ElicitStub are non-empty when the user wants the suite to auto-reply to server-initiated sampling/elicitation requests; the conform runner installs the appropriate stub handler on the connected service before the scenario fires its trigger tool.