Skip to content

MCP output schemas and schema/runtime conformance tests (Bounty #946)#1180

Open
yanyishuai wants to merge 1 commit into
ramimbo:mainfrom
yanyishuai:fix/issue-946-mcp-output-schemas
Open

MCP output schemas and schema/runtime conformance tests (Bounty #946)#1180
yanyishuai wants to merge 1 commit into
ramimbo:mainfrom
yanyishuai:fix/issue-946-mcp-output-schemas

Conversation

@yanyishuai

@yanyishuai yanyishuai commented Jun 29, 2026

Copy link
Copy Markdown

Summary

Adds focused MCP structured-output and schema/runtime conformance improvements for #946 (also aligns with #934).

  • Advertise outputSchema for get_balance, get_wallet, get_ledger_entry, and get_proof
  • Reuse shared MCP_WALLET_OUTPUT_SCHEMA for register/get wallet tools
  • Enforce additionalProperties: false at runtime via safe unknown argument errors (no caller echo)
  • Add tests/test_mcp_schema_conformance.py comparing tools/list schemas with tools/call structuredContent
  • Update agent guide with output-schema and conformance-test notes

Verification

python -m pytest tests/test_mcp_schema_conformance.py tests/test_api_mcp.py::test_mcp_tools_list_and_call -q
python -m ruff check app/mcp.py app/mcp_tools.py tests/test_mcp_schema_conformance.py
python -m ruff format --check app/mcp.py app/mcp_tools.py tests/test_mcp_schema_conformance.py docs/agent-guide.md

Wallet

Do4v7foHJvRJLpRRoGaVPWX6DDEjX3yTK7J91gpwUQpE

Bounty #946
Refs #934

Summary by CodeRabbit

  • New Features
    • Improved MCP tool outputs with consistent, documented output schemas for balance, wallet, ledger entry, and proof actions.
  • Bug Fixes
    • More reliable rejection of invalid tool arguments, including treating an explicitly provided format: None as invalid.
    • Standardized “unknown argument” errors without echoing unexpected parameter names.
  • Documentation
    • Updated the agent guide to reflect the exact advertised tool tools/list input/output schema relationships.
  • Tests
    • Added schema conformance and safe error-handling coverage for MCP tool responses.

@coderabbitai

coderabbitai Bot commented Jun 29, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@yanyishuai, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 48 minutes and 22 seconds. Learn how PR review limits work.

Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file).

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based credits.

🚦 How do rate limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 188811de-2f26-4200-93d5-4c9ef4d09abd

📥 Commits

Reviewing files that changed from the base of the PR and between b379e6a and f2a3257.

📒 Files selected for processing (5)
  • app/mcp.py
  • app/mcp_tools.py
  • docs/agent-guide.md
  • tests/test_api_mcp.py
  • tests/test_mcp_schema_conformance.py
📝 Walkthrough

Walkthrough

Centralizes MCP tool output schemas, tightens unknown-argument handling, and adds schema/runtime conformance tests and documentation for the affected tools.

Changes

MCP Output Schema and Argument Validation

Layer / File(s) Summary
Shared outputSchema constants and MCP_TOOLS wiring
app/mcp.py
Defines shared JSON-schema constants for balance, wallet, ledger entry, and proof responses, then replaces inline outputSchema objects for five MCP tools with those constants.
Unknown-argument validation and classification
app/mcp_tools.py, app/mcp.py
call_mcp_tool now treats format: None as invalid, require_known_fields raises ValueError("unknown argument"), several tool branches add explicit field whitelists, and _KNOWN_FIELDLESS_MESSAGES recognizes that phrase for structured error handling.
Schema conformance and rejection tests
tests/test_mcp_schema_conformance.py, tests/test_api_mcp.py
Adds tests for declared outputSchema shape, structuredContent required fields, and safe unknown-argument rejection; extends existing API tests with outputSchema checks and a null-format invalid case.
Agent guide updates
docs/agent-guide.md
Updates MCP tool documentation to reference the advertised schema fields, structuredContent usage, and the new conformance test coverage.

Possibly related issues

Possibly related PRs

🚥 Pre-merge checks | ✅ 6
✅ Passed checks (6 passed)
Check name Status Explanation
Title check ✅ Passed The title is concrete and accurately names the main MCP output-schema and conformance-test changes.
Description check ✅ Passed The description matches the PR intent and includes summary, verification commands, wallet, and issue refs, though the Evidence checklist is not filled in.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Mergework Public Artifact Hygiene ✅ Passed PASS: The PR’s public-facing edits only add MCP schema/conformance wording; no investment, price, cash-out, or secret-security claims were introduced.
Bounty Pr Focus ✅ Passed Changes stay on MCP wallet/ledger/proof surfaces and matching tests/docs for bounty #946/#934; no unrelated scope surfaced in the touched files.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2


ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 498579dd-d155-4ddb-8941-bd11b9a9cd4b

📥 Commits

Reviewing files that changed from the base of the PR and between 3bc87d2 and 07c238d.

📒 Files selected for processing (5)
  • app/mcp.py
  • app/mcp_tools.py
  • docs/agent-guide.md
  • tests/test_api_mcp.py
  • tests/test_mcp_schema_conformance.py

Comment thread tests/test_mcp_schema_conformance.py Outdated
Comment thread tests/test_mcp_schema_conformance.py

@qingfeng312 qingfeng312 left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CI is failing in the MCP schema/runtime conformance test.

tests/test_api_mcp.py::test_mcp_tools_list_and_call now reaches the balance tool assertions and raises KeyError: 'outputSchema', which means at least one exposed MCP tool is missing the advertised output schema expected by the API tests.

Please add the missing output schema for the balance tool, or adjust the schema generation consistently so all MCP tools expose the contract expected by the tests.

Evidence: GitHub Actions run 28346068593, job 83969516521, 1 failed, 905 passed.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
tests/test_api_mcp.py (1)

2443-2471: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Assert the classified format error payload for the new null case.

The added {"format": null} case only proves the legacy envelope. It would still pass if the classifier stopped returning the field-level data payload for format, which is the behavior this change is meant to cover. As per coding guidelines, "Add or update tests for changed behavior"; as per path instructions, "Focus on whether tests prove the changed behavior and include negative, replay, boundary, or regression cases where relevant."

Suggested fix
 def test_mcp_submit_work_proof_rejects_invalid_bounty_selectors(
     sqlite_url: str, arguments: dict[str, object], request_id: int
 ) -> None:
@@
-    _assert_invalid_tool_arguments_envelope(response.json(), request_id=request_id)
+    expected_data = None
+    if arguments == {"format": None}:
+        expected_data = {
+            "code": "invalid_argument",
+            "tool": "submit_work_proof",
+            "field": "format",
+            "message": "must be a string",
+        }
+    _assert_invalid_tool_arguments_envelope(
+        response.json(), request_id=request_id, expected_data=expected_data
+    )

Sources: Coding guidelines, Path instructions


ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 389b931a-66d9-4b15-81a0-8909069e01e5

📥 Commits

Reviewing files that changed from the base of the PR and between 07c238d and b379e6a.

📒 Files selected for processing (5)
  • app/mcp.py
  • app/mcp_tools.py
  • docs/agent-guide.md
  • tests/test_api_mcp.py
  • tests/test_mcp_schema_conformance.py

Comment thread app/mcp_tools.py
Comment on lines 105 to +108
def output_format_arg() -> str:
value = args.get("format", "text")
if value is None:
if "format" not in args:
return "text"
value = args["format"]

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Reject explicit null consistently across optional typed arguments.

This fixes {"format": null}, but the shared optional helpers still accept explicit null as “argument omitted” for other fields. For example, repo, q, limit, include_awards, and include_expired still pass with null even though their schemas only allow string/integer/boolean, so tools/list and tools/call remain out of sync for those parameters.

Suggested direction
 def optional_clean_str_arg(field: str) -> str | None:
-    value = args.get(field)
-    if value is None:
+    if field not in args:
         return None
+    value = args[field]
+    if value is None:
+        raise ValueError(f"{field} must be a string")
     if not isinstance(value, str):
         raise ValueError(f"{field} must be a string")
@@
 def list_limit_arg(default: int = 25) -> int:
-    if "limit" not in args or args.get("limit") is None:
+    if "limit" not in args:
         return default
     value = positive_int_arg("limit")
@@
 def optional_bool_arg(field: str, default: bool = False) -> bool:
-    value = args.get(field, default)
-    if value is None:
+    if field not in args:
         return default
+    value = args[field]
     if not isinstance(value, bool):
         raise ValueError(f"{field} must be a boolean")

Comment thread app/mcp.py
Comment on lines +134 to +160
MCP_WALLET_OUTPUT_SCHEMA: dict[str, Any] = {
"type": "object",
"description": "Registered MRWK wallet payload returned in structuredContent.",
"properties": {
"address": {
"type": "string",
"pattern": "^[mM][rR][wW][kK]1[0-9a-fA-F]{40}$",
},
"public_key_hex": {"type": "string", "pattern": "^[0-9a-fA-F]{64}$"},
"label": {"type": ["string", "null"]},
"github_login": {"type": ["string", "null"]},
"balance_mrwk": {"type": "string"},
"nonce": {"type": "integer", "minimum": 0},
"next_nonce": {"type": "integer", "minimum": 1},
"created_at": {"type": "string"},
},
"required": [
"address",
"public_key_hex",
"label",
"github_login",
"balance_mrwk",
"nonce",
"next_nonce",
"created_at",
],
"additionalProperties": True,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Close the wallet output schema.

register_wallet/get_wallet advertise a fixed field set here, and tests/test_api_mcp.py:2941-2960 already asserts those schema keys exactly match the serialized wallet payload. Leaving additionalProperties open means undeclared wallet fields can slip into structuredContent without breaking the published contract.

Suggested fix
 MCP_WALLET_OUTPUT_SCHEMA: dict[str, Any] = {
@@
-    "additionalProperties": True,
+    "additionalProperties": False,
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
MCP_WALLET_OUTPUT_SCHEMA: dict[str, Any] = {
"type": "object",
"description": "Registered MRWK wallet payload returned in structuredContent.",
"properties": {
"address": {
"type": "string",
"pattern": "^[mM][rR][wW][kK]1[0-9a-fA-F]{40}$",
},
"public_key_hex": {"type": "string", "pattern": "^[0-9a-fA-F]{64}$"},
"label": {"type": ["string", "null"]},
"github_login": {"type": ["string", "null"]},
"balance_mrwk": {"type": "string"},
"nonce": {"type": "integer", "minimum": 0},
"next_nonce": {"type": "integer", "minimum": 1},
"created_at": {"type": "string"},
},
"required": [
"address",
"public_key_hex",
"label",
"github_login",
"balance_mrwk",
"nonce",
"next_nonce",
"created_at",
],
"additionalProperties": True,
MCP_WALLET_OUTPUT_SCHEMA: dict[str, Any] = {
"type": "object",
"description": "Registered MRWK wallet payload returned in structuredContent.",
"properties": {
"address": {
"type": "string",
"pattern": "^[mM][rR][wW][kK]1[0-9a-fA-F]{40}$",
},
"public_key_hex": {"type": "string", "pattern": "^[0-9a-fA-F]{64}$"},
"label": {"type": ["string", "null"]},
"github_login": {"type": ["string", "null"]},
"balance_mrwk": {"type": "string"},
"nonce": {"type": "integer", "minimum": 0},
"next_nonce": {"type": "integer", "minimum": 1},
"created_at": {"type": "string"},
},
"required": [
"address",
"public_key_hex",
"label",
"github_login",
"balance_mrwk",
"nonce",
"next_nonce",
"created_at",
],
"additionalProperties": False,
}

Comment on lines +48 to +62
def _assert_structured_matches_required(
payload: dict[str, Any] | list[Any], schema: dict[str, Any]
) -> None:
if schema.get("type") == "array":
assert isinstance(payload, list)
item_schema = schema["items"]
for item in payload:
assert isinstance(item, dict)
for key in item_schema.get("required", []):
assert key in item
return
assert isinstance(payload, dict)
for key in schema.get("required", []):
assert key in payload

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Validate the full advertised schema, not just required keys.

_assert_structured_matches_required() only checks that required keys exist. It never checks declared types, patterns, minima, or additionalProperties, so this suite would still pass if balance_microunits became a string or a closed schema started emitting undeclared fields. As per coding guidelines, "Add or update tests for changed behavior"; as per path instructions, "Focus on whether tests prove the changed behavior and include negative, replay, boundary, or regression cases where relevant."

Sources: Coding guidelines, Path instructions

@yanyishuai yanyishuai force-pushed the fix/issue-946-mcp-output-schemas branch from b379e6a to f2a3257 Compare June 29, 2026 04:52
@yanyishuai

Copy link
Copy Markdown
Author

@qingfeng312 Thanks for the review — CI is green on the latest head.

Changes since the failing run:

  • Added outputSchema for get_balance, get_wallet, get_ledger_entry, and get_proof
  • Added tests/test_mcp_schema_conformance.py with schema/runtime conformance coverage
  • Fixed treasury balance assertion after bounty payment in the schema test
  • ruff format applied on the conformance test module

Latest head: f2a32572ac62. Quality check: pass. Ready for another look.

@yanyishuai

Copy link
Copy Markdown
Author

@qingfeng312 CI is green on the latest head (f2a32572ac62) for #946. Could you take another look when you have a moment?

1 similar comment
@yanyishuai

Copy link
Copy Markdown
Author

@qingfeng312 CI is green on the latest head (f2a32572ac62) for #946. Could you take another look when you have a moment?

@qingfeng312 qingfeng312 left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Follow-up on current head f2a32572ac62d04e98fd6528de72379122adb9a3.

The earlier MCP output-schema test blocker has been resolved. I rechecked app/mcp.py, app/mcp_tools.py, the agent-guide update, and the new schema/runtime conformance tests.

The affected tools now advertise the expected outputSchema entries, the structuredContent tests cover the runtime shape, and the unknown-argument behavior remains bounded without echoing unexpected caller input.

Validation checked: GitHub CI Quality, readiness, docs, and image checks passed on run 28349428755; CodeRabbit status is success on this head. I did not find a remaining blocker for the scoped #946/#934 change.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants