Apizr as a local MCP server
For prerequisites and package availability, see Install Apizr.
Let your AI assistant explore a Python project with the optional outerspace-apizr-mcp plugin. It can inspect code, check which functions are ready to expose, and prepare a plan. By default, it is read-only and does not execute your project's functions.
Find the plugin on Glama or the official MCP Registry. To make your own functions callable by an assistant, use the MCP generation guide.
Install and activate explicitly
Follow Install Apizr → choose a plugin profile
with mcp. Use its verified exported wheels and locks; users do not need
to build plugins or compute dependency closures. Keep that workspace as $work,
with core/ and the explicit plugins/ store. For the MCP example below, create
the directories below, then save the four files shown in the next section.
export work="$PWD"
mkdir -p project/src project/policies
For a temporary isolated profile, use the verified preparation workflow. It selects compatible wheels and normalizes multi-hash resolver locks without a Python normalization script. Preparation installs nothing; installation and activation remain explicit. Contributors use the same workflow with a local wheelhouse containing core and plugin wheels from the same recorded commit.
Choose one local project
Create project/ with this complete apizr.toml:
schema_version = "apizr.project/v1"
root = "."
readiness_policy = "policies/readiness.json"
exposure_policy = "policies/exposure.json"
[scan]
source_roots = ["src"]
excluded_directories = ["ignored"]
max_file_bytes = 4096
[graph]
max_ast_nodes = 10000
src/calculator.py:
def add(a: int, b: int = 1) -> int:
return a + b
policies/readiness.json:
{"execution": {"modes": ["direct"]}}
policies/exposure.json:
{
"selection": {"include": ["python:calculator:add"]},
"interfaces": ["rest", "mcp"],
"execution": {"allowed": ["direct"]}
}
Paths resolve relative to apizr.toml, not the client's working directory. The
server freezes configuration and policy values at startup. Restart explicitly
to apply operator edits. Policy files must be regular files, at most 64 KiB,
without duplicate JSON keys. The existing project loader bounds TOML input.
The selected root is anchored by filesystem identity; symlink traversal and
root replacement cannot redirect a calculation to another project. Existing
scanner refusals and diagnostics remain visible.
From $work, prepare and review an explicit analysis grant:
core/bin/python - <<'PYTHON'
import json
from pathlib import Path
root = str(Path("project").resolve())
Path("operator.json").write_text(json.dumps({"schema":"apizr.operator-policy/v1","grants":[{"adapter":"repository","operation":"analyze","target":{"kind":"local","root":root},"permissions":["source.analyze"]}]}))
PYTHON
Start with absolute paths:
"$work/core/bin/apizr" mcp serve --project "$work/project/apizr.toml" \
--operator-policy "$work/operator.json" \
--plugins-dir "$work/plugins"
Without --plugins-dir, the existing --user-config selection or default plugin store applies. The resolved selection is captured once at startup. The core
validates the active installation and replaces itself with that environment's
interpreter and the registered apizr_mcp module. It passes an empty environment,
uses isolated Python, and reserves stdout for MCP. Diagnostics use stderr.
This durable connection is not apizr plugins run: that command retains its
one-request apizr.extension/v1 protocol and existing timeout. Internally, the
MCP server uses that runtime for fresh, isolated compiler calculations, not
as an envelope around MCP traffic. Those workers call Python APIs, not the CLI.
Registry installation
This is the canonical installation path linked by server.json. 0.4.5 is
published on PyPI and in the Official MCP Registry. The launcher and its
explicit plugin profile have been verified from fresh public installations.
Use matching 0.4.5 core and plugin versions.
The official MCP Registry
lists Apizr as io.github.Alien6-Studio/outerspace-apizr.
Glama also lists its
three analysis tools and their inputs.
- Install its core and prepare an explicit MCP profile with the commands below. Use an absolute path to your installed CPython 3.11–3.14 interpreter and uv 0.12.x. Run each command in order, stopping on any failure. Keep this temporary directory while using the server.
python=/absolute/path/to/python3.14
setup=$(mktemp -d)
setup=$(cd "$setup" && pwd -P)
uv venv --python "$python" "$setup/core"
uv pip install --python "$setup/core/bin/python" 'outerspace-apizr[preparation]==0.4.5'
apizr="$setup/core/bin/apizr"
"$apizr" plugins prepare outerspace-apizr-mcp --version 0.4.5 \
--python "$python" --platform native \
--index-url https://pypi.org/simple --output-dir "$setup/prepared" --json
plugin_sha=$(awk '$1 == "outerspace-apizr-mcp==0.4.5" {sub("--hash=sha256:", "", $2); print $2}' \
"$setup/prepared/requirements.lock")
"$apizr" plugins install "$setup/prepared/wheelhouse/outerspace_apizr_mcp-0.4.5-py3-none-any.whl" \
--sha256 "$plugin_sha" --python "$python" \
--requirements "$setup/prepared/requirements.lock" \
--wheelhouse "$setup/prepared/wheelhouse" --plugins-dir "$setup/profile"
"$apizr" plugins enable outerspace-apizr-mcp --version 0.4.5 \
--plugins-dir "$setup/profile"
Your managed plugin directory is the absolute $setup/profile path.
Preparation verifies compatible wheel bytes; review the selected dependencies
before installation and activation. Those remain separate explicit steps.
The preparation guide explains
supported platforms, including the reviewed dependency's macOS Intel refusal.
For unpublished contributor candidates, use the qualified local wheelhouse
without --index-url; do not mix public 0.4.4 artifacts with this profile.
2. Create the four project files in Choose one local project,
or select your existing apizr.toml. It selects a source root and analysis
policies; it does not grant access to that root.
3. Create and review the following operator.json. Replace
<ABSOLUTE_PROJECT_ROOT> with the absolute root selected by your project file.
Approving this grant is an operator decision, not an installation side effect.
{
"schema": "apizr.operator-policy/v1",
"grants": [{
"adapter": "repository",
"operation": "analyze",
"target": {"kind": "local", "root": "<ABSOLUTE_PROJECT_ROOT>"},
"permissions": ["source.analyze"]
}]
}
- Configure your MCP client's stdio connection with the following command. Replace each angle-bracket placeholder with an absolute path you selected:
uvx outerspace-apizr-mcp@0.4.5 \
--project "<ABSOLUTE_APIZR_TOML>" \
--operator-policy "<ABSOLUTE_OPERATOR_JSON>" \
--plugins-dir "<ABSOLUTE_MANAGED_PLUGIN_DIRECTORY>"
- Connect. Initialization identifies
apizr, titled OuterSpace Apizr, at the matching version.tools/listshows Analyze Python Project, Assess Repository Readiness, and Plan Capability Exposure. Ask the first tool for a summary. A response containing repository evidence confirms the connection; a readinessexit_code: 1can be valid business evidence. Stdio stdout is MCP traffic, so running the command alone does not print a human-readable welcome.
| Registry argument | Required format | Why no default; where to obtain it |
|---|---|---|
--project |
Absolute filepath | Source selection is your decision. Create/select apizr.toml in step 2. |
--operator-policy |
Absolute filepath | Installation cannot authorize source access. Create and review the JSON grant in step 3. |
--plugins-dir |
Absolute directory path (filepath in the registry schema) |
The governed store must be deterministic. Use the directory where step 1 explicitly installed and activated the matching plugin. |
The launcher delegates to apizr mcp serve; installing it with uvx does not
activate a plugin, authorize a project, or enable delivery. All three registry
arguments intentionally remain required even where the generic CLI offers
fallbacks. Missing authorization and absent/inactive plugins fail closed.
The directory UI intentionally excludes --user-config (an alternative generic
CLI configuration source, redundant with this explicit store), --timeout-ms,
--max-request-bytes and --max-response-bytes (advanced operational tuning), and
--delivery-request (explicit effectful mode). Configure those through the
operator-run CLI when needed; the registry's normal installation remains
analysis-only. No credentials or other environment variables are requested.
For Smithery, the prepared MCPB 0.4 bundle presents the same three required
file/directory inputs and delegates to this same launcher. It targets the
qualified macOS/Linux platforms and needs an MCPB host supporting the uv runtime.
It neither contains nor provisions your policy or governed plugin store.
Its submission and actual Smithery listing remain a post-release operation.
See the distribution qualification matrix for schema checks, supported directory mechanisms and remaining external evidence.
Choose a tool: discover → assess → propose
The server's initialization instructions and tool descriptions explain this journey directly to a connected assistant:
| Need | Choose | Next step |
|---|---|---|
| Find what the project contains and obtain exact capability IDs | apizr_analyze for static source and relationship evidence |
Review a capability and retain its repository digest if subsequent calls should use the same evidence |
| Understand whether a capability is usable and under which conditions | apizr_readiness for limitations, uncertainty and execution/control compatibility under the startup readiness policy |
Inspect the relevant full, summary or exact detail result before choosing proposal constraints |
| Test a selection you already intend to expose | apizr_plan_exposure with an explicit policy or the captured startup exposure policy |
Review the proposed capability/interface/execution combinations or the refusal diagnostics |
This is guidance, not a required sequence. Readiness and planning can be called independently, including as the first call. Each tool assesses current evidence within the startup source scope; earlier reports are not cached or supplied to later calculations. Digest pinning checks consistency between independent calls.
These three tools do not execute project code, generate a server, change project configuration or publish anything. A readiness report assesses evidence; a plan is a proposal. Neither grants operator authority. Repository/root, operator permissions and server bounds are chosen at startup. Tool arguments cannot expand source scope, activate plugins, select infrastructure or enable delivery.
When delivery is explicitly enabled, initialization instead identifies the additional effects: status inspects local evidence, while run/resume may mutate remote state using captured authority. Analysis/readiness/planning remain non-mutating in that mode. See Explicit delivery mode.
Tools and results
| Tool | Arguments | Structured result |
|---|---|---|
apizr_analyze |
Optional view, exact detail filter, pagination and expected_repository_digest |
Default/full: existing canonical catalog, graph, and repository_digest. Summary/detail: explicitly identified evidence view |
apizr_readiness |
Same view arguments | Default/full: existing canonical report, repository_digest, and exit_code. Summary/detail: states and focused evidence |
apizr_plan_exposure |
Optional policy (existing ExposurePolicy), expected_repository_digest, offset and limit |
Existing canonical ExposurePlan; optional pagination applies only to refused-plan diagnostics |
The advertised JSON Schemas contain fixed descriptions and valid request examples,
including documentation under reusable $defs/$ref references. These are
generated from the canonical Pydantic models, independently of project text.
expected_repository_digest is an optional plain string: exactly 64 lowercase
hexadecimal SHA-256 characters, without a prefix or algorithm name. Omission or
null means normal discovery without asserting a previous identity. It does not
select a repository or change the startup source scope. Obtain the actual value
from a preceding response, rather than copying the illustrative digest in a
schema example. With an initialized official SDK client:
analysis = await client.call_tool("apizr_analyze", {})
digest = analysis.structured_content["repository_digest"]["value"]
readiness = await client.call_tool(
"apizr_readiness", {"expected_repository_digest": digest}
)
The response's reusable Digest object is
{"algorithm": "sha256", "value": "<64 lowercase hexadecimal characters>"}.
Its algorithm is fixed to SHA-256; only its value goes in the input pin.
Bounded views provide that same value at identity.repository_digest.value.
Pinning compares evidence discovered by the current call with the expected
identity; it neither caches the old repository nor disables discovery.
For apizr_plan_exposure, an explicit policy supplies the proposal constraints.
Omission or null uses the captured startup exposure policy; if neither exists,
the result is policy_required. For a discovered capability with compatible
startup readiness requirements, an explicit proposal has this shape:
{
"policy": {
"selection": {"include": ["python:calculator:add"]},
"interfaces": ["mcp"],
"execution": {"allowed": ["direct"]}
}
}
Use the exact ID returned by analysis. selection.include_all_ready adds the
report's READY capabilities to explicit include; exclude then removes exact
IDs. Unknown IDs are refused. eligibility.allow_conditional permits proposals
with CONDITIONAL required evidence, subject to all other checks; it does not
turn them into READY capabilities. interfaces requests rest and/or mcp.
execution.allowed is a nonempty acceptable mode set, with no ranking, default
or fallback; execution.require names required controls. Compatibility is also
constrained by the startup readiness policy.
ExposurePolicy is a planning constraint, not operator authorization. It
cannot enlarge the captured startup permissions. A successful ExposurePlan
contains selected capabilities, interfaces, compatible execution modes, static
readiness/relationship evidence and artifact/policy digests. It is a proposal,
not generation, execution, publication or an authority grant.
A missing exposure policy yields policy_required; it never selects everything.
A changed digest yields repository_changed. Each call scans once; separate
calls can see changed source files. Readiness exit_code: 1 is a valid business
report with isError: false, preserving unknown/conditional/refused distinctions.
Errors have isError: true and structured error.code/error.diagnostics:
invalid_arguments, exposure_refused (canonical planning diagnostics),
scope_changed, repository_changed, and redacted runtime/operational codes.
No internal traceback or raw subprocess diagnostic is returned. Strict tool
schemas reject client-supplied roots, policy paths, executables or startup limits.
The three analysis tool descriptions and read-only annotations are fixed; repository text remains
data and cannot create tools or replace server instructions. Annotations alone
are not an authorization boundary.
Recover from a refusal
The same recovery guidance is available through initialization metadata.
Inspect isError and structured evidence, not just display text:
| Result | Existing recovery |
|---|---|
repository_changed |
Analyze current source evidence again, review what changed and its new identity, then decide whether to continue with the new digest. Never replace a pin blindly. |
policy_required |
Supply an explicit valid ExposurePolicy when no startup exposure policy was captured. This does not edit configuration or confer authority. |
exposure_refused |
Inspect error.diagnostics and supported action values; correct the proposal or evidence before another attempt. A refusal is not a successful plan. |
| Scope or authorization refusal | Operator/startup action is required. Alternative tool arguments cannot bypass captured scope or grants. |
server_busy |
The call was refused while another calculation was active. Wait for that call to finish before retrying an analysis tool. This is not a successful assessment or an automatic delivery retry. |
response_too_large |
For analysis/readiness, request summary, exact detail or smaller detail pages. For refused planning diagnostics, use their existing offset/limit pagination. Successful plans are not paginated; large individual records can still exceed the bound. No response is automatically truncated. |
arguments_too_large / invalid_arguments |
Correct or reduce the request within its advertised schema and startup bounds. These tools cannot raise server limits. |
| Transport, runtime, cleanup or operation failure | Preserve the failure and seek operator diagnosis where necessary. It is not a successful business assessment. Unconfirmed cleanup terminates the session. |
Tool refusals use isError: true; transport failures can instead prevent a tool
response. In contrast, readiness exit_code: 1 with isError: false is a valid
assessment of limitations or uncertainty. Delivery business outcomes retain
their separate existing semantics.
Start with a small view (0.4.5)
Ask your AI assistant to summarize the repository before inspecting a function:
{"view":"summary"}
Use this with apizr_analyze for source, capability and graph counts, or with
apizr_readiness for READY/CONDITIONAL/AMBIGUOUS/UNSUPPORTED counts, execution
compatibility and principal blockers. principal_blockers reports total, at
most ten shown diagnostics, and has_more; the sample is never presented as
all the evidence. Neither summary embeds a Catalog, Graph or Readiness report.
Then ask “Can I expose python:model:predict?” using apizr_readiness:
{"view":"detail","capability_id":"python:model:predict"}
focus retains the exact identity, source path, local readiness, repository and
selected-scope states, interface eligibility and reasons. principal_blockers
remain visible on every page. records contains the required dependency context,
relationships, imports, source metadata and diagnostics. Private helpers are
execution support, with exposed: false; being a dependency does not select a
public function. Required evidence uses the same selected-scope authority as
exposure planning, including module initialization and incomplete evidence.
For a Data Scientist, this lets an assistant inspect model.predict, explain an
unresolved feature-normalization dependency and see a precise import refusal,
while unrelated unfinished experiments remain in the complete repository audit.
Analysis does not execute models or evaluate annotations. The view supplies
evidence for a later exposure-policy proposal; it does not authorize execution.
To explore one logical module instead, use:
{"view":"detail","module":"model"}
Module detail retains its own capabilities, states, source metadata, outgoing
relationships, imports and diagnostics. Module names and capability IDs match
exactly: no path filter, fuzzy name matching or implicit selection. Detail
requires exactly one of module or capability_id; unknown identities yield
unknown_module or unknown_capability. Filters, offset and limit are valid
only in detail. All incompatible combinations yield invalid_arguments.
Module strings are bounded to 512 characters and capability IDs to 1,024;
pagination accepts strict integers, never strings or booleans.
Keep canonical identity and read every page
Every reduced view declares view: "summary" or view: "detail". Its identity
contains the complete repository, Catalog and Graph digest objects. Readiness
views also contain repository_readiness_digest and readiness_policy_digest.
Filtering and paging never change those identities. A view has no canonical
artifact schema version and must not be submitted as a Catalog, Graph or
RepositoryReadinessReport.
Detail returns page with offset, limit, total, returned, next_offset
and complete. The default limit is 50 records; the maximum is 200 and the
minimum is 1. offset starts at zero and is bounded to 2,147,483,647. Filtering
precedes deterministic ordering, which precedes pagination. Continue with the
returned next_offset, the same filter and the full repository digest value:
{"view":"detail","capability_id":"python:model:predict","offset":50,"limit":50,"expected_repository_digest":"<64 lowercase hexadecimal characters>"}
A nonzero offset requires that digest. If any included source changes between
pages, the next call yields repository_changed; restart at page zero with the
new identity. Changing the filter starts another independent collection, whose
counts and offset describe that filter. There is no persistent analysis cache or
pagination session. Each call discovers once and uses retained static artifacts.
Read until complete: true to obtain all ancillary records; focus, decision
states and principal blockers are never paged away.
Understand a refused exposure plan
apizr_plan_exposure retains isError: true, error.code: "exposure_refused"
and every existing diagnostic field. An additive action names a corrective
step when supported by evidence. A dependency refusal can identify:
{"source_path":"config.py","line":1,"dependency_path":["python:serving:predict","python:features:_normalize","python:config:scale","python-module:config"],"evidence_code":"APIZR-GRAPH-002","reason":"import","action":"fix_import"}
This is a diagnostic excerpt, not a replacement response schema. Stable actions
include fix_import, fix_binding, fix_initialization,
select_known_capability, resolve_interface_contract,
adjust_execution_requirements and inspect_dependency. Unknown evidence has
action: null; actions express a suggested correction and grant no authority.
For large refusals, optionally request limit/offset alongside policy.
error.page and error.diagnostic_count describe the complete deterministic
diagnostic collection. Continuations require expected_repository_digest;
successful canonical plans remain unchanged even when paging was requested.
Request the complete audit when needed
Existing clients calling {} receive the same complete canonical fields.
Explicit {"view":"full"} returns that same result. Full reports remain useful
for audit/export; summary and detail improve normal assistant conversations.
The advertised output schema accepts each strict response variant.
All modes obey the response limit. A full report exceeding it yields
response_too_large; the server never switches silently to a summary. Request a
summary or smaller detail page explicitly. A page can still exceed the byte
limit when an individual record is large. Views contain source metadata and
retained static evidence, never raw source files, host paths or tracebacks.
Explicit delivery mode (0.4.2)
The default launch remains:
apizr mcp serve \
--project /absolute/project/apizr.toml \
--operator-policy /absolute/operator.json
It exposes only analysis, readiness and exposure planning, even when delivery plugins and delivery grants already exist. To enable delivery, the operator selects one existing BatchRequest containing a qualified build and its explicit destinations:
apizr mcp serve \
--project /absolute/project/apizr.toml \
--operator-policy /absolute/operator.json \
--delivery-request /absolute/delivery.json \
--plugins-dir /absolute/plugins
The launcher reads the request once: an absolute regular file, at most 512 KiB,
with no symlink traversal or duplicate JSON keys. Existing strict BatchRequest
validation binds the shared build, proof requirement and ordered unique destinations.
A private inherited descriptor carries the frozen request, policy and plugin-store
selection. Restart to select different values; editing their files does not change
an active session. Delivery authority is not included in analysis worker jobs.
| Additional tool | Exact accepted arguments | Result and effects |
|---|---|---|
apizr_delivery_status |
{} |
Existing BatchResult, local evidence only; no directory creation, plugin invocation or network |
apizr_delivery_run |
Required expected_delivery_manifest_digest |
Existing coordinator starts the captured request; retained evidence prevents a silent restart |
apizr_delivery_resume |
Required expected_delivery_manifest_digest |
Existing coordinator resumes retained progress and verifies remote state; missing evidence is refused |
The confirmation is the existing digest object:
{"expected_delivery_manifest_digest":{"algorithm":"sha256","value":"<64 lowercase hexadecimal characters>"}}
All three schemas forbid extra fields. Run/resume require the confirmation to match the digest returned by status before any coordinator call. The client cannot submit destinations, credentials, keys, plugin paths, evidence roots, recovery selections, requests or authority. Each underlying managed operation still requires its exact existing operator grant, plugin identity and repository. MCP creates no permission or grant; annotations grant no authority.
Status returns the exact retained complete/partial result. Without evidence,
all destination outcomes are not_started; the existing aggregate schema uses
state: failed until a destination completes. This is a successful status call,
with isError: false, and creates no evidence. Run/resume likewise return typed
complete, partial, failed or cancelled business results with isError: false.
They reuse the 0.4.1 deliver_batch coordinator and do not rebuild, regenerate,
resolve dependencies or blindly re-sign completed destinations.
Status is annotated read-only, idempotent and closed-world. Run/resume are
annotated effectful, non-idempotent and open-world; all three use
destructiveHint: false because they do not delete remote state. Run/resume may
write local evidence and remote images, proofs and destination tags. No automatic
retries or rollback are added. Cancellation preserves confirmed progress and
uncertainty for in-flight mutations, stops later destinations and waits for native
cleanup. Unconfirmed cleanup produces cleanup_unconfirmed and ends the session.
Fixed refusals include delivery_not_enabled, delivery_identity_changed,
delivery_evidence_invalid, delivery_operation_failed, cleanup_unconfirmed,
invalid_arguments, arguments_too_large, response_too_large and server_busy.
When delivery is not enabled its tools are absent and receive the normal
unknown-tool refusal. No raw exception, operational path or secret is returned.
Bounds and lifecycle
Startup-only options:
| Option | Default | Accepted range |
|---|---|---|
--timeout-ms |
10,000 per call | 1–600,000 |
--max-request-bytes |
65,536 per stdio line | 4,096–1,048,576 |
--max-response-bytes |
4,194,304 | 2,048–16,777,216 |
The argument allowance reserves 2 KiB within the request bound. Worker stdout
(including its private response envelope) is bounded while reading; an overflow
returns response_too_large, never a truncated canonical report. Result serialization
is also checked (response_too_large). The SDK's protocol envelope and schemas
have a separate 256 KiB allowance. An oversized/invalid byte stream closes the
connection with a redacted stderr diagnostic such as request_too_large.
Protocol writes have a five-second deadline. Scanner limits still apply; these
bounds are not an operating-system memory quota or a sandbox.
One call runs at a time across analysis and delivery tools. Overlapping calls get server_busy; discovery
remains responsive. Timeout affects a calculation, not the connection lifetime.
MCP cancellation, EOF, client disconnection and SIGINT/SIGTERM/SIGHUP cancel active
work through the existing runtime and wait for pipe closure and child recovery.
Unconfirmed cleanup ends the session with an explicit diagnostic. Ordinary
process-group descendants are covered by the runtime; detached descendants
remain outside its documented cleanup guarantees. No plugin or project code is
imported by the core, and project code is not executed by analysis.
There is no automatic .env load, inherited secret environment, HTTP/SSE server,
OAuth, sampling, LLM call, telemetry or Git acquisition. Only explicitly enabled
delivery can perform the selected registry and proof operations. Results
may contain project names, docstrings and diagnostics: the MCP client may send
them to its model. Local stdio alone does not guarantee confidentiality.
Client configuration and validation
The real integration proof uses the locked official Python MCP SDK 2.3.0, its
low-level API,
and a real SDK stdio client. It validates revisions 2026-07-28 (auto) and
2025-11-25 (legacy). No JSON-RPC server implementation is duplicated here.
This VS Code .vscode/mcp.json example follows the official
MCP configuration reference.
Replace all paths; the graphical VS Code connection has not been tested.
No personal client configuration is changed by installation or this proof.
{
"servers": {
"apizr": {
"type": "stdio",
"command": "/absolute/work/core/bin/apizr",
"args": [
"mcp", "serve", "--project", "/absolute/work/project/apizr.toml",
"--operator-policy", "/absolute/work/operator.json",
"--plugins-dir", "/absolute/work/plugins"
]
}
}
}
From the checkout, uv run --locked python scripts/smoke_mcp_server.py --output
/absolute/new-proof-directory builds and installs separate wheels outside the
checkout, checks inactive refusal and explicit activation, compares all tools
against Python/CLI, and exercises actual subprocess cancellation/shutdown with a
synchronized test worker. It checks core files, activations and project contents.
The dedicated CI matrix covers Linux 3.11–3.14 and macOS 3.11/3.14 and preserves
logs and separate plugin coverage. Linux repeats the installed proof with network
access removed by a disposable network namespace. These are development proofs,
not tests of a graphical client. See the release record
for publication status.
scripts/mcp_views_proof.py also measures actual structured response bytes on a
69-file repository, validates every successful view against its advertised
schema through real stdio, reads the complete dependency slice and checks source
changes between pages. Both supported protocols prove that a small response
bound refuses full reports while allowing an explicitly requested summary.
scripts/mcp_schema_proof.py, included in the installed-wheel proof, retains
schemas-auto.json and schemas-legacy.json from actual tools/list responses,
plus the corresponding tool results and schema-proof.json. It checks property
descriptions, referenced definitions and schema examples, obtains a real digest
from analysis, pins readiness to it, and plans an actually discovered capability.
Its fixture includes uncertain experimental code, so a valid readiness result
has exit_code: 1 and isError: false. Project prose cannot replace its discovery
metadata. The proof runs locally without network access after installation and
does not depend on external directory scoring. --capture-only retains prior
discovery/results for before/after comparison without requiring new descriptions.
The compact test baseline records the prior validation-schema and business-result
fingerprints; full discovery JSON stays in qualification artifacts.
The same proof retains discovery-{auto,legacy}.json with initialization
instructions and complete tool metadata, and recovery-{auto,legacy}.json with
SDK responses including error status. Real changed-source, missing-policy and
refused-selection calls accompany the normal journey. guidance_baseline.json
binds the prior installed wheels and fingerprints every unchanged discovery
contract and response, including #268's schema descriptions/examples. Only the
three analysis tool descriptions and initialization instructions are excluded
from discovery comparison. Delivery discovery retains all six tools and verifies
their effect annotations separately in mcp_delivery_local_proof.py.
Local TDQS review
This assessment covers the six Tool Definition Quality Score dimensions without
claiming an external score. verify_guidance checks essential selection/recovery
concepts and a conciseness budget against actual SDK discovery; it does not
attempt to measure agent intelligence or directory ranking.
| Dimension | Assessment and check |
|---|---|
| Purpose Clarity | Analyze discovers identities, readiness assesses usability, planning tests a chosen proposal. Each description names its distinct result. |
| Usage Guidelines | Each tool states when to choose it and a next step; initialization presents discover → assess → propose as optional guidance. Fresh-session readiness/planning tests preserve independent use. |
| Behavioral Transparency | No project execution or authority grant; fresh evidence, readiness business results, structured refusals, recovery and delivery effects are explicit and exercised. |
| Parameter Semantics | #268's complete schemas, referenced definitions and valid examples remain unchanged, verified against installed discovery fingerprints. Descriptions refer to those schemas. |
| Conciseness | Each analysis description is capped at 115 whitespace-delimited words; initialization at 350 in either mode. Shared workflow/recovery guidance lives in initialization rather than being repeated per tool. |
| Contextual Completeness | Capability IDs, actual digest pinning, policy fallback, startup scope, principal recoveries and mode-specific effects are available without external documentation. |
External Glama evaluations, when available, are dated observations about the version indexed there. They are not an offline qualification gate or evidence that an unpublished development commit has been evaluated.
Delivery qualification adds official SDK status/run/resume calls for both protocol
revisions. scripts/smoke_oci_registry.py --artifacts --output /absolute/new-proof
reuses the disposable authenticated HTTPS registry, TSA, Attest, ORAS and Docker
fixture. It exercises complete delivery and A/C complete with B failed followed by
B recovery, checks retained digests, upload/signature counts and zero new builds,
and runs delivered REST/MCP services after original source and bundle removal.
The installed lifecycle proof covers cancellation, EOF, disconnection and signals
with a synchronized native worker and retained batch evidence.
Qualification evidence
The MCP server plugin workflow has two independent jobs, with no dependency
between them:
stdio: the installed analysis plugin and official SDK client, including separate plugin coverage and the Linux proof without network access, on Linux Python 3.11–3.14 and macOS 3.11/3.14;generated-recovery: ten predetermined repetitions per transport of the generated business MCP server recovery test, on Linux/macOS Python 3.11.
An error in either job does not prevent the other from running. Both jobs and the general CI must succeed on the same final commit to complete qualification. A prior green run, skipped job or successful later call is not a replacement for that evidence. Artifacts are retained on failure; repetitions are never retried until green.
Run the recovery series with a fresh output directory:
uv run --locked python scripts/repeat_mcp_transports.py --output /tmp/mcp-recovery
Each JUnit report contains redacted call history and bounded execution_phases:
process launch, worker bootstrap/import completion, request reception/validation,
source binding, calculation, response and cleanup. Every completed exchange must
show a reaped direct child and closed stdin/stdout; the test also checks that the
recorded worker PIDs no longer exist. Records contain interpreter path, version,
architecture, timestamps and process/pipe state, never arguments or business
results. The test-only observer wraps the embedded runtime without changing the
generated artifacts, public protocol or production defaults. Its overhead is
included in timings; local and runner interpreter identities must be compared
explicitly before drawing conclusions.
The functional recovery test uses a fixed 5,000 ms policy (the existing execution
policy default), measured before process creation, including interpreter
startup/imports and waiting for process exit. Infinite loops and blocked calls
still produce real timeouts; crashes, output overflow, artifact tampering and
subsequent successful calls remain asserted. Dedicated tests in
tests/execution/test_deadline.py retain real 100 ms deadlines, prove that a
budget consumed inside Popen is not restarted, and check reaping and closed
pipes before recovery.
The former 1,000 ms functional budget conflated recovery with cold-process
performance. In the initial measured macOS 3.11.9 series, an expected fail
exchange took 974.489 ms although calculation took 0.143 ms: imports took
344.340 ms, the response was available around 521 ms, and cleanup began around
951 ms. Linux 3.11.15's slowest non-timeout exchange took 183.896 ms; local
macOS 3.11.14 measured 225.503 ms. These are different interpreters/environments,
not interchangeable benchmarks. The existing 5-second default gives the
functional checks margin above this measured near-boundary exchange, while the
separate short-deadline tests retain the timing contract. Production defaults
and the start of deadline measurement are unchanged.
Issue #142 retains all historical failures and per-commit qualification links. The old logs did not measure internal phases: their exact startup/shutdown split cannot be recovered. No specific OS scheduling or interpreter defect is claimed from those logs.
Session authority
The launcher checks source admission before server execution, preserving active installation checks and the usage lease. It hands the captured policy and project scope through a bounded private inherited descriptor, not a policy path to reopen. Workers recheck the shared gate, and the server checks before spawning a worker. Device/inode pinning refuses root replacement; descriptor traversal prevents a pathname change redirecting analysis. Only explicitly chosen bounded configuration is read before admission. Policy/project edits require a restart: no dynamic revocation is promised. Clients cannot supply new roots, grants or operator paths. Exposure proposals remain selection policies, never source authority. Default tools remain local stdio analysis/readiness/planning. Explicit delivery uses a separate captured session section; no Git acquisition, generation or build tool is exposed.