Repository navigation
fix(mcp): describe each tool's task, inputs and boundary in tools/list (#800) - #1114
Open
pengfei-threemoonslab wants to merge 2 commits into
Open
pengfei-threemoonslab wants to merge 2 commits into
pengfei-threemoonslab wants to merge 2 commits into
Conversation
#800) The five MCP tools were registered on wrappers with no description and no docstring, so tools/list advertised shipgate.check, shipgate.preflight, shipgate.explain, shipgate.capabilities and shipgate.handoff with empty descriptions: the defect SHIP-DOC-MISSING-DESCRIPTION reports in other projects. Each tool now passes description= through MCPServer.tool, which exists at the extra's floor (mcp 2.0.0) and newest (2.3.0). Each description opens with the task, names every input the tool accepts, says what it returns, and states its boundary as implemented: check takes a caller-provided detached diff and obtains no base or head ref; capabilities builds a lock from a config or diffs two existing locks; preflight is projection only; explain takes a check ID, or a fingerprint with a report path; handoff reads existing verify artifacts and does not check they still match the working tree. None runs git, the Git-backed host comparison (agents-shipgate diff --base), scan or verify. Schema IDs are read from the engine's constants. Names, input and output schemas and annotations are unchanged. Tests hold the descriptions over the SDK's list_tools() and over a real stdio tools/list, plus one tool call; the CI stdio smoke now fails on an empty or duplicated description.
"runs no findings, scan or verify" read as if findings were something a tool runs. The description now says the tool writes no lock, produces no findings and runs no scan or verify, matching the explain tool's wording.
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Closes #800.
create_server()registered all five MCP tools with@server.tool(name=..., annotations=read_only)on inner wrappers that had nodescription=and no docstring. The docstrings were on the outershipgate_*functions, which are not the registered objects. Sotools/listlistedshipgate.check,shipgate.preflight,shipgate.explain,shipgate.capabilitiesandshipgate.handoffwithdescription: "". That is the defectSHIP-DOC-MISSING-DESCRIPTIONreports in other projects. I reproduced it onorigin/main(4809636) at the extra's floor (mcp==2.0.0) and newest (mcp==2.3.0), in-process and over stdio.src/agents_shipgate/mcp_server/server.py: each tool now passesdescription=throughMCPServer.tool, which acceptsdescriptionat both 2.0.0 and 2.3.0. I wrote each description from its handler anddocs/mcp-server.md. Each one opens with the task, names every input the tool accepts, says what it returns and states what it cannot do:shipgate.checktakes caller-provided, detached diff text. It runs no git, obtains no base or head ref, and is not the CLI's Git-backedagents-shipgate diff --base <ref>. Every result stops athuman_review_required, authorizes no verify command and grants no permission, even whendecisionisallow.shipgate.preflighttakes aplanor the direct inputs, never both, and is projection only. It is not a release verdict, everycontrolpermission is false and itscontrol.stateis nevercomplete. It may name a CLIverifycommand but runs nothing.shipgate.explaintakescheck_id, orfingerprintwithreport_path(an existingreport.json).fingerprintrequiresreport_pathand takes precedence overcheck_id. It produces no findings.shipgate.capabilitiestakesconfig, or bothbase_lockandhead_lock(existing lock files). One lock alone is rejected. It runs no git, checks out no base ref and is not the Git-backed host comparison.shipgate.handoffreads an existingverifier.json, plus thereport.jsonandverify-run.jsonbeside it when present. It runs no verify, does not check that those artifacts still match the working tree, and grants nothing the verifier did not.shipgate.agent_boundary_result/v3, preflight0.6,shipgate.agent_handoff/v9) are read from the engine's constants, not copied. Wrapper names and signatures are unchanged, so input schemas, output schemas and annotations are byte-identical toorigin/main's.scripts/mcp_extra_requirement.py --stdio-smoke(CI'smcp extrajobs) now also fails when the installedagents-shipgate mcp-servelists an empty or duplicated description. It prints the five descriptions to the job log.docs/mcp-server.md: one paragraph on the descriptions and the Git-backed-comparison boundary. The handoff output row and paragraph saidshipgate.agent_handoff/v8; the server returnsv9, so they now sayv9. The handoff paragraph also gains the currentness limit.docs/distribution-surfaces.md: themcp_serverrow said the server "has nothing of its own to drift". It now says the descriptions are its own words, carry no claim, and are held bytests/test_mcp_server.pyand the stdio smoke. Claims are unchanged (none), sotests/test_distribution_surface_parity.pyneeds no change, and it passes.CHANGELOG.md: one line under### Changes, after theenv_varsentry.Type
Authorship
Acceptance criteria
tools/listresponses on the supported installed MCP extra.tests/test_mcp_server.py::test_every_tool_tells_an_agent_its_task_inputs_and_boundaryreadsbuild_server().list_tools(). For each tool it asserts a description that is non-empty, at most 1024 characters and opens with that tool's task. It must contain that tool's boundary statements and name every input in the tool's input schema in backticks. All five descriptions must be distinct.test_stdio_tools_list_carries_the_descriptions_and_a_tool_still_answersstartspython -m agents_shipgate mcp-serveas a real stdio server in a scratch cwd. It runsinitializeandtools/listover JSON-RPC and asserts each served description, input schema and annotation equals the registered one. Then it callsshipgate.explain.mcp extra (floor),mcp extra (newest)) now fails on an empty or duplicate description.server.pyreverted toorigin/main, mcp 2.0.0): both tests fail. The smoke exits 1 withshipgate.check is listed with an empty description.checkandcapabilitiesmust say they are notagents-shipgate diff --base <ref>.check,preflight,capabilitiesandhandoffcarry their authority limits as required statements (see the description text below).subprocess.Popenhooked): none forcheck,preflight(diff and empty plan),capabilities(export and diff) orexplain. I also checked the stated behaviours against the handlers: empty-diffcheckgivesdecision=allow,control.state=human_review_required,allowed_next_commands=[]and all permissions false. Empty-planpreflightgivesplanning_only. The error cases (one lock, fingerprint without report, missing verifier) raise the errors the descriptions name.test_descriptions_left_every_tool_name_and_input_unchangedpins each input's JSON types and default, with nothing required, toorigin/main's registration. It leaves out the SDK's cosmetictitlekeys, so an SDK release that retitles them does not fail CI. It also pins the read-only and open-world annotations. Perturbation check: changing onecapabilitiesdefault fails it.origin/mainand this branch over stdio, at mcp 2.0.0 and 2.3.0, are identical inname,inputSchema,outputSchemaandannotations. Onlydescriptiondiffers (0 characters → 701–989). The branch catalog is identical at both SDK versions.Installed-build evidence
I built a wheel from this branch (
uv build --wheel,agents_shipgate-1.2.0-py3-none-any.whl: the source version, not a published release). I installed it withmcp<3into a fresh venv and ran it outside the checkout:agents_shipgateimported from…/installed/lib/python3.12/site-packages/agents_shipgate/, version1.2.0;mcp 2.3.0.scripts/mcp_extra_requirement.py --stdio-smokeagainst the installedagents-shipgate mcp-serve: exit 0,mcp-serve answered over stdio with 5 read-only tools.shipgate.explain {"check_id": "SHIP-DOC-MISSING-DESCRIPTION", "no_plugins": true}returnedis_error=Falsewithstructured_content.id == "SHIP-DOC-MISSING-DESCRIPTION".''at both 2.0.0 and 2.3.0.The five descriptions as stdio
tools/listreturns them (installed wheel, mcp 2.3.0)shipgate.capabilities(845 chars): Export or compare static capability locks: the tools a Shipgate manifest declares for an agent, with their typed actions. Passconfig(manifest path, default shipgate.yaml) to build a lock from the manifest's declared tool sources, or bothbase_lockandhead_lock(existing capability lock JSON files, asverifyorcapability exportwrite them) to diff them; one lock alone is rejected, and a lock pair ignoresconfig.no_pluginskeeps third-party adapters out even when AGENTS_SHIPGATE_ENABLE_PLUGINS is set. Returns capability lock JSON or capability lock diff JSON; neither is a release gate. It writes no lock and runs no findings, scan or verify. The diff reads only the two files given: this tool runs no git and checks out no base ref, so it is not the CLI's Git-backed host comparison (agents-shipgate diff --base <ref>).shipgate.check(989 chars): Check a coding agent's change against the agent-boundary policy for host configuration and other boundary files (for example.codex/config.toml,.claude/settings.json,.mcp.json), from a unified diff you already have. Pass the change asdiff_text; optionalagent(codex, claude-code or cursor; default codex),workspace(holding the tree before or after the diff),configandpolicy. The diff is detached: this tool runs no git and does not obtain a base or head ref, so it is not the CLI's Git-backed host comparison (agents-shipgate diff --base <ref>). Returnsshipgate.agent_boundary_result/v3: decision, affected hosts, violations and advisory rows for only the host files the diff changes. Because the diff is bound to no checkout, every result stops for human routing (control.statehuman_review_required), authorizes no verify command and grants no permission, even whendecisionisallow. It runs no scan or verify and is not a release or merge verdict.shipgate.explain(701 chars): Explain an Agents Shipgate check, or one finding in an existing report. Passcheck_id(for exampleSHIP-POLICY-APPROVAL-MISSING) for that check's catalog entry: what it checks, when it fires, its default severity and recommendation. Or passfingerprintwithreport_path(areport.jsonthatscanorverifyalready wrote) for that finding's fields plus a deterministic explanation;fingerprintrequiresreport_pathand takes precedence overcheck_id.no_pluginskeeps third-party check plugins out even when AGENTS_SHIPGATE_ENABLE_PLUGINS is set. An unknown check ID or fingerprint is an error. Lookup only: it produces no findings and runs no scan, verify, git or host comparison.shipgate.handoff(765 chars): Read the compact agent handoff of averifyrun that already happened. Passverifier_path(default agents-shipgate-reports/verifier.json, which must exist);report_pathandverify_run_pathdefault toreport.jsonandverify-run.jsonbeside it and are read when present. Returnsshipgate.agent_handoff/v9, the projectionverifywrites: release gate, control, blockers, remediation plan and reproducibility identities. It is not a new verdict and grants nothing the verifier did not; artifacts that disagree with each other are refused. It runs no scan, verify, git or host comparison, and does not check that the artifacts still match the working tree: they describe only the tree that run verified, so re-runagents-shipgate verifyafter any change.shipgate.preflight(978 chars): Before editing, route a planned change: does it touch a protected release surface, need a human, need capability evidence, or need verify? Pass eitherplan(preferred: aPreflightPlanV1object withchanged_files,diff_text,capability_requestsandhost_permission_requests) or the direct inputschanged_files,diff_text,capability_requestandbase_preflight(an earlier preflight result to compare policy and trust-root hashes against), never both: a mixed request is rejected. Optionalworkspaceandconfig. Returns a preflight result (preflight_schema_version0.6): protected-surface touches, signals, required evidence,requires_human_review,requires_verifyand a routingcontrol. Projection only: it is not a release verdict, every permission in itscontrolis false and itscontrol.stateis nevercomplete; when verification is owed it names a CLIverifycommand but runs nothing. It runs no git, host comparison, scan or verify.Verification
The local
.venvholds mcp 1.27.2, where the SDK tests skip. So I rantests/test_mcp_server.pyin two scratch venvs built like CI'smcp-extrajob:constraints/dev.txtandconstraints/build-backend.txtwith--require-hashes, an editable install of this worktree, then the SDK at the floor or the newest admitted release.python -m pytest tests/test_mcp_server.py -rs(mcp 2.0.0)python -m pytest tests/test_mcp_server.py -rs(mcp 2.3.0)server.pyreverted toorigin/main(mcp 2.0.0)python scripts/mcp_extra_requirement.py --stdio-smoke(mcp 2.0.0 and 2.3.0, editable)server.pyreverted toorigin/mainshipgate.check is listed with an empty description)PYTHONPATH=src .venv/bin/python -m pytest tests/test_mcp_server.py tests/test_distribution_surface_parity.py tests/test_docs_links.py tests/test_public_surface_contract.py tests/test_release_pipeline.py tests/test_local_contract.py tests/test_agent_instructions_renderers.py -n 4python -m ruff check .Release-readiness notes
docs/checks.md(n/a: no check changes)STABILITY.md(n/a: no report or schema change. Only the MCPtools/listdescriptionfield changes, from empty to text.)Out of scope
AGENTS.md:889andllms-full.txt:914name the five tools with no descriptions; another v1.3.0 PR edits those files.docs/agents/protocol.mdstill says the handoff returnsshipgate.agent_handoff/v7.docs/integrations.md:367and:390anddocs/INDEX.md:26list four tools and omitshipgate.handoff.SHIP-DOC-MISSING-DESCRIPTIONon this server is not possible yet. The repository's static MCP reader cannot followMCPServerto a server because_load_mcp_sdk()resolves it at runtime. A scan ofserver.pyreportsserver_binding_not_provenfor each registration andSHIP-INVENTORY-NOT-ENUMERABLE, before and after this change.diff_textrequired or makingagentan enum: the issue defers it. Schemas are unchanged here.🤖 Generated with Claude Code