Skip to content

feat(mcp-server): expose polymorphic relations in describeCollection#1551

Open
Scra3 wants to merge 11 commits intomainfrom
feat/mcp-polymorphic-relations
Open

feat(mcp-server): expose polymorphic relations in describeCollection#1551
Scra3 wants to merge 11 commits intomainfrom
feat/mcp-polymorphic-relations

Conversation

@Scra3
Copy link
Copy Markdown
Member

@Scra3 Scra3 commented Apr 14, 2026

Summary

  • Detect polymorphic-referenced-models from forest-rails schema in describeCollection
  • Expose isPolymorphic: true and polymorphicTargets: ['Post', 'Video'] in the relation output
  • Set targetCollection: null for polymorphic relations (since the reference name isn't a real collection)
  • Add LLM guidance in the tool description about polymorphic _id and _type fields

Why

When a Ruby agent has polymorphic relations (e.g. commentable on comments), the MCP server didn't expose this info. LLMs couldn't know they need to pass both commentable_id AND commentable_type for create/update, or that listRelated won't work on polymorphic BelongsTo relations.

Example output

{
  "relations": [
    {
      "name": "commentable",
      "type": "many-to-one",
      "targetCollection": null,
      "isPolymorphic": true,
      "polymorphicTargets": ["Post", "Video"]
    }
  ]
}

Test plan

  • Polymorphic BelongsTo relation detected and exposed with targets
  • Non-polymorphic relations unchanged (no extra fields)
  • Build passes, lint clean

🤖 Generated with Claude Code

Note

Expose polymorphic relations in describeCollection MCP tool output

  • Relation entries in describeCollection now include isPolymorphic: true and polymorphicTargets when polymorphicReferencedModels is present on a field; targetCollection is set to null for these relations.
  • Actions without a configured endpoint are excluded from the actions array and surfaced in _meta.skippedActions with reason 'no endpoint configured'; getActionEndpoints also skips endpoint-less actions.
  • Fixes a runtime error in FieldFormStates when a change-hook response omits layout, by treating missing layout as an empty array.
  • Documentation strings in the list tool are updated to explain the : separator for relation fields and limitations for polymorphic relations.

Macroscope summarized 9c21c01.

@qltysh
Copy link
Copy Markdown

qltysh bot commented Apr 14, 2026

4 new issues

Tool Category Rule Count
qlty Structure Function with high complexity (count = 16): declareDescribeCollectionTool 3
qlty Structure Deeply nested control flow (level = 4) 1

Detect polymorphic_referenced_models from forest-rails schema and
expose isPolymorphic + polymorphicTargets in the describeCollection
response. This allows LLMs to know they need to pass both _id and
_type fields when creating/updating records with polymorphic relations.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@Scra3 Scra3 force-pushed the feat/mcp-polymorphic-relations branch from dcd311f to 062f4b8 Compare April 14, 2026 11:33
@qltysh
Copy link
Copy Markdown

qltysh bot commented Apr 14, 2026

Qlty

Coverage Impact

This PR will not change total coverage.

Modified Files with Diff Coverage (3)

RatingFile% DiffUncovered Line #s
Coverage rating: A Coverage rating: A
packages/agent-client/src/action-fields/field-form-states.ts100.0%
Coverage rating: C Coverage rating: C
packages/mcp-server/src/utils/schema-fetcher.ts100.0%
Coverage rating: A Coverage rating: A
packages/mcp-server/src/tools/describe-collection.ts100.0%
Total100.0%
🚦 See full report on Qlty Cloud »

🛟 Help
  • Diff Coverage: Coverage for added or modified lines of code (excludes deleted files). Learn more.

  • Total Coverage: Coverage for the whole repository, calculated as the sum of all File Coverage. Learn more.

  • File Coverage: Covered Lines divided by Covered Lines plus Missed Lines. (Excludes non-executable lines including blank lines and comments.)

    • Indirect Changes: Changes to File Coverage for files that were not modified in this PR. Learn more.

…improve type safety

- Filter actions without valid endpoints from describeCollection and getActionEndpoints
  to prevent 404 errors when the LLM tries to execute them
- Fix "layout is not iterable" crash in agent-client when backend returns no layout
  in hook change responses
- Add polymorphic-referenced-models to ForestSchemaField type, removing unsafe double cast
- Make ResponseBody.layout optional to match runtime behavior
- Add debug logging for schema loading and warn for actions without endpoints at startup

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
export type ResponseBody = {
fields: PlainField[];
layout: ForestServerActionFormLayoutElement[];
layout?: ForestServerActionFormLayoutElement[];
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

in v1 it can be undefined

alban bertolini and others added 9 commits April 14, 2026 15:11
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…getActionEndpoints

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…warning

Actions without endpoints are now reported in describeCollection's
_meta.skippedActions with name and reason, so the LLM can inform users.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…e-dangle)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…odels

The Forest Admin server returns polymorphicReferencedModels in camelCase
after JSON:API deserialization, not kebab-case. The previous key
'polymorphic-referenced-models' was always undefined in production,
silently disabling polymorphic relation detection.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…rom @@@ to :

The filters description incorrectly told the LLM to use @@@ for relation
fields, but the backend expects : (e.g. "relation:field"). The @@@ separator
is only used internally by QuerySerializer for field projections.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…cription

Warn the LLM not to filter via relation:field on polymorphic relations
(returns 500) and explain the two-step approach using _type and _id.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace "returns 500" with "is not supported and will fail" since the
error code varies depending on the backend agent.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@nbouliol
Copy link
Copy Markdown
Member

LGTM

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