feat: MCP get_er_diagram tool + strict selection validation (fix silent field swallowing) - #138
Merged
Merged
Conversation
The single-app MCP server (create_single_app_mcp_server) exposed only
get_schema + graphql_query, so an AI agent had to read the full SDL to
learn the data map. Add a get_er_diagram tool that returns the
entity-relationship diagram as Mermaid text, built from the same
SQLModel metadata that drives the GraphQL schema.
Discovery order for agents is now: get_er_diagram (data map) ->
get_schema (operations) -> graphql_query (execution). The tool docstring
documents this walkthrough and what happens inside a query; the
graphql_query docstring examples now use the real entity-rooted shape
({ Team { by_filter {...} } }) instead of a misleading flat shape, and
get_schema no longer claims to be the starting point.
Also: examples/quickstart_mcp.py (runnable companion of quickstart.py,
--check mode exercises all three tools through a real MCP client),
README Quick start gains a "For AI agents" section, and
scripts/mcp_agent_view.py dumps the raw payloads an MCP client sees
(explainability regression harness).
Default tool count goes 2 -> 3 (4 with allow_mutation=True) — additive.
Co-Authored-By: Claude <noreply@anthropic.com>
…pping them
A misspelled field in a selection ({ Team { by_filter { nmae } } })
passed through unvalidated and was silently dropped at serialization,
returning success: true with empty-object rows — zero signal for an AI
agent to self-correct (found by an MCP blind test; unknown group/method
names already errored, but the entity-field level was never checked).
Add pre-execution selection validation in QueryExecutor: a field is
valid when it is an entity column (model_fields, FK columns included —
they are queryable even though the SDL omits them) or a relationship,
resolved through the same sources the serializer uses (loader registry
-> get_relation_entity). Unknown fields append a GraphQL-style error
("Cannot query field 'nmae' on type 'Team'") and the method is skipped,
mirroring the unknown-method handling; sibling methods still execute.
Paginated relationships ({Target}Result packages) are detected with the
shared is_active_paginated_relationship predicate (same source as
SDL/introspection): items is validated against the target entity,
pagination is metadata-filtered, and other keys error against
{Target}Result. __typename is exempt.
Federation by_<key>_in batch roots are exempt via a
_nexusx_federation_batch_root marker: mounters' remote loaders select
DTO-side computed fields the member never serves (dropped by design,
recomputed mounter-side), so strict validation there would break the
federation wire protocol.
This is a behavior change: queries that previously "succeeded" with
empty objects now return errors.
Co-Authored-By: Claude <noreply@anthropic.com>
…entry
The mermaid ER diagram was a lossy map: no Result {items, pagination}
wrapping, no field args, link tables collapsed. Under strict selection
validation it actively misled agents into writing queries that fail.
The SDL is the source of truth and agents parse it natively, so
get_schema becomes the single discovery entry point, with its docstring
now documenting both list-relationship shapes (plain [Entity!]! vs
EntityResult! wrapping).
get_er_diagram was added on this branch (a3ddb17) and never released.
Co-Authored-By: Claude <noreply@anthropic.com>
…OR code
A failed method used to leave an empty-object ``Entity: {}`` in data —
a misleading "empty success" for clients that read data and ignore
errors. Method return types are non-null, so the failure now nulls the
whole group (GraphQL null propagation at group granularity; sibling
results are discarded by design). The error entry also carries
extensions.code RESOLVER_ERROR, matching BARE_GROUP_FIELD's style, for
machine-side classification.
Co-Authored-By: Claude <noreply@anthropic.com>
Phase 3 now locks every UseCaseService method's return structure with a user-confirmed ASCII tree (tree root = the return type annotation) before dtos.py is written, and the core-principles list gains the matching rule. Co-Authored-By: Claude <noreply@anthropic.com>
Research artifact from the ER-diagram exploration: dumps tool payloads to /tmp for offline analysis. It calls the get_er_diagram tool removed in 34a5f6c (fails with ToolError) and nothing references it; the analysis it fed became the SDL-single-entry design instead. Co-Authored-By: Claude <noreply@anthropic.com>
The {items, pagination} unwrap logic existed twice — once for
pagination roots in _validate_method_selection, once for paginated
relationships in _validate_entity_fields — with divergent behavior:
an unknown key on a pagination-root package fell through to entity-
field validation and errored "on type '<Entity>'", pointing at the
wrong type. Both paths now share _validate_paginated_package; the
root path takes the wrapper name and fk key from the method's
_pagination_root meta, so unknown keys name the actual wrapper type
({Entity}{Field}PagePackage / {Target}Result).
Co-Authored-By: Claude <noreply@anthropic.com>
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
Two related changes, surfaced by an MCP "blind test" (an agent reasoning
only from what the MCP server returns):
get_er_diagramon the single-app MCP server — the entity-firstapp had no AI-facing story for its data map. The server now exposes
the entity-relationship diagram as Mermaid text (generated from the
same SQLModel metadata that drives the GraphQL schema), with a
documented discovery order:
get_er_diagram(data map) →get_schema(operations) →graphql_query(execution). Tooldocstrings now teach the real entity-rooted query shape. README Quick
start gains a "For AI agents" section;
examples/quickstart_mcp.pyis the runnable companion (
--checkexercises all three toolsthrough a real MCP client). Additive: default tool count 2 → 3.
Strict selection validation (behavior fix) — a misspelled field
(
{ Team { by_filter { nmae } } }) was silently dropped atserialization and returned
success: truewith empty-object rows:zero signal for an AI agent (or any client) to self-correct. Unknown
group/method names already errored; the entity-field level was never
checked. The executor now validates selections pre-execution against
model_fields(FK columns included — queryable though omitted fromSDL) ∪ relationships (loader registry →
get_relation_entity, samesources as serialization), erroring with
Cannot query field 'nmae' on type 'Team'.is_active_paginated_relationship(same source as SDL/introspection);itemsvalidated against the target,paginationmetadata-filtered.__typenameexempt; federationby_<key>_inbatch roots exempt viaa marker (mounters' wire selections include DTO-side computed fields
the member drops by design).
Behavior change
Queries that previously "succeeded" returning empty objects now return
GraphQL-style errors. The federation wire protocol is unaffected
(exempted + covered by existing federation suites).
Test plan
tests/test_selection_validation.py(8 cases: unknown field,nested unknown naming the target type, FK queryable,
__typename,valid queries, sibling methods,
itemson non-paginated rel,batch-root exemption)
(
tests/mcp/test_simple_mcp.py,tests/test_documented_api.py)examples/quickstart_mcp.py --checkend-to-end; stdio handshake +tools/listsmoke-tested with raw JSON-RPCqueries unaffected
🤖 Generated with Claude Code