Skip to content

Implement workflow-first legislative UX tools#70

Merged
StatPan merged 2 commits into
masterfrom
issue-62-ux-define-public-tool-surface-and-naming-contract
May 31, 2026
Merged

Implement workflow-first legislative UX tools#70
StatPan merged 2 commits into
masterfrom
issue-62-ux-define-public-tool-surface-and-naming-contract

Conversation

@StatPan
Copy link
Copy Markdown
Owner

@StatPan StatPan commented May 31, 2026

Summary

Implements the workflow-first AssemblyMCP UX roadmap across issues #62-#69.

Changes

  • Add get_legislative_research_kit as the first-call public tool surface contract.
  • Add structured failure markers: [NOT_FOUND], [AMBIGUOUS], [VERIFY_FAILED], [API_FAILED], [PARTIAL].
  • Add claim verification for bill, member, committee, and vote claims via verify_legislative_claims.
  • Add workflow tools: issue_brief, bill_timeline, legislative_impact_map, and watch_action_plan.
  • Rewrite Korean and English READMEs around the implemented workflow-first interface.
  • Add focused unit coverage for UX helpers and server workflow tools.

Verification

  • uv run ruff check .
  • uv run pytest -q -> 80 passed
  • Started local MCP HTTP server on 127.0.0.1:8765/mcp
  • uv run python scripts/test_streamable_http.py http://127.0.0.1:8765/mcp -> initialize/tools-list/ping passed, 26 tools listed
  • Called get_legislative_research_kit over MCP HTTP -> isError=false, 6 workflow tools returned, [VERIFY_FAILED] marker present

Closes #62
Closes #63
Closes #64
Closes #65
Closes #66
Closes #67
Closes #68
Closes #69

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces workflow-oriented public tools, such as claim verification, issue briefing, and bill timelines, along with helper utilities, updated documentation, and new unit tests. The review feedback identifies several potential TypeError bugs where service calls could return None instead of lists, and suggests converting numeric values to floats during comparison to prevent false mismatch reports.

Comment thread assemblymcp/server.py
)
data = to_public_data(details)
else:
matches = await service.get_bill_info(age=age, bill_name=value, limit=limit)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

If get_bill_info returns None (e.g., due to an API error or empty response), attempting to iterate over it in the list comprehension on the next line will raise a TypeError: 'NoneType' object is not iterable. Defaulting to an empty list [] prevents this potential crash.

Suggested change
matches = await service.get_bill_info(age=age, bill_name=value, limit=limit)
matches = await service.get_bill_info(age=age, bill_name=value, limit=limit) or []

Comment thread assemblymcp/server.py
details={"claim": claim},
)

matches = await service.get_member_info(value)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

If get_member_info returns None, attempting to iterate over it in the list comprehension on the next line will raise a TypeError: 'NoneType' object is not iterable. Defaulting to an empty list [] prevents this potential crash.

Suggested change
matches = await service.get_member_info(value)
matches = await service.get_member_info(value) or []

Comment thread assemblymcp/server.py
details={"claim": claim},
)

matches = await service.get_committee_list(value)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

If get_committee_list returns None, attempting to iterate over it in the list comprehension on the next line will raise a TypeError: 'NoneType' object is not iterable. Defaulting to an empty list [] prevents this potential crash.

Suggested change
matches = await service.get_committee_list(value)
matches = await service.get_committee_list(value) or []

Comment thread assemblymcp/server.py

warnings: list[dict[str, Any]] = []
try:
meetings = await meeting_svc.get_meeting_records(bill_data.get("BILL_ID") or bill_id)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

If get_meeting_records returns None instead of raising an exception, the meetings variable will be None. This will cause a TypeError: 'NoneType' object is not iterable when iterating over meetings on line 555. Defaulting to an empty list [] prevents this potential crash.

Suggested change
meetings = await meeting_svc.get_meeting_records(bill_data.get("BILL_ID") or bill_id)
meetings = await meeting_svc.get_meeting_records(bill_data.get("BILL_ID") or bill_id) or []

Comment thread assemblymcp/server.py
Comment on lines +139 to +142
if isinstance(expected_value, int | float) or isinstance(actual, int | float):
if actual != expected_value:
mismatches.append({"field": key, "expected": expected_value, "actual": actual})
continue
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

When comparing numeric values, comparing an integer/float directly with a string (e.g., 180 != "180") will evaluate to True in Python, leading to false mismatch reports. Converting both values to floats before comparison ensures robust numeric validation regardless of whether they are represented as strings or numbers in the source data.

Suggested change
if isinstance(expected_value, int | float) or isinstance(actual, int | float):
if actual != expected_value:
mismatches.append({"field": key, "expected": expected_value, "actual": actual})
continue
try:
if float(actual) == float(expected_value):
continue
except (ValueError, TypeError):
pass
if isinstance(expected_value, int | float) or isinstance(actual, int | float):
if actual != expected_value:
mismatches.append({"field": key, "expected": expected_value, "actual": actual})
continue

@StatPan
Copy link
Copy Markdown
Owner Author

StatPan commented May 31, 2026

Check

Deployment readiness check before merge: uv build produced sdist/wheel, stdio MCP integration script passed, HTTP MCP server smoke passed, Docker image build passed, Docker container HTTP MCP smoke passed, local ruff/pytest passed, and GitHub CI is green. Local ASSEMBLY_API_KEY is loaded but direct Assembly API calls return ERROR-990/empty rows, so live data-row verification is blocked by the local key/API authorization state rather than server startup, MCP protocol, packaging, or tool-schema behavior.

Context:

@StatPan StatPan merged commit a77ec79 into master May 31, 2026
2 checks passed
@StatPan StatPan deleted the issue-62-ux-define-public-tool-surface-and-naming-contract branch May 31, 2026 11:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant