Skip to content

feat(mcp): serve the host's schema tools through the MCP facade - #449

Merged
TonsOfFun merged 6 commits into
mainfrom
feat/mcp-schema-tools
Sep 12, 2026
Merged

feat(mcp): serve the host's schema tools through the MCP facade#449
TonsOfFun merged 6 commits into
mainfrom
feat/mcp-schema-tools

Conversation

@TonsOfFun

Copy link
Copy Markdown
Contributor

Closes #439. Stacked on #443 (it needs agent_actor); retarget to main once that merges.

#439 asked for a mounted Rack endpoint serving the schema-tool roster over MCP. As the review on #443 noted, Api::MCPController already is that endpoint; what was missing was the roster. This extends the facade rather than adding a second server — one definition, two transports.

Change

  • tools/list offers every tool the dashboard's discovered ActiveAgent::SchemaTools classes generate — find_<records>, count_<records>, get_<record> — beside the run_<slug> agents, each with the tool's own parameter schema as inputSchema, so a client sees which columns it may filter on.
  • tools/call on one of them runs SchemaTools.call(name, actor: agent_actor, **arguments): the key's caller (or whatever ActionAgent.agent_actor_resolver returns), through the host's own scope, exactly as inside an agent run. Arguments that name the caller (actor, current_user) are dropped, for the reason Carry the caller into agent runs, so agents can authorize like controllers #443 gives.
  • A boundary violation (an undeclared filter, an id the caller cannot see) is a tool result with isError, the shape an agent's model would get, so a client can correct its call. A refusal raised by the host's scope — ActiveAgent::NotAuthorized or an error named with denies_with — is a JSON-RPC -32003, as an agent's refusal is.
  • No generation runs, so neither execution_enabled nor the execution quota applies to a direct read, and no execution usage is recorded.
  • ActionAgent.mcp_schema_tools = false keeps the tools reachable only through agents. On by default: the host declared them, and every key holder could already reach them through any agent that has them enabled.
  • autoload :NotAuthorized in lib/active_agent.rb: the class is defined in concerns/authorization.rb, loaded only with Base, and the engine referencing it before any agent class had loaded raised NameError (which run_refused? on Carry the caller into agent runs, so agents can authorize like controllers #443 sidesteps by comparing strings).
  • docs/framework/dashboard.md gains a section on the facade; docs/work/schema-tools-follow-ups/ records this batch of work (branches, issues, milestones, PRs).

Testing

  • actionagent/test/mcp_schema_tools_test.rb: nine cases — listing beside agents with the parameter schema, running as the resolved caller with the client's arguments, a client cannot name the caller, unattributed when nothing resolves, a boundary violation as isError, a refusal as -32003, unknown tool, the switch, no execution switch or quota.
  • bin/test actionagent/test/mcp_*_test.rb test/authorization_test.rb — 30 runs, 83 assertions, 0 failures.
  • bin/rubocop clean on the changed files.

🤖 Generated with Claude Code

https://claude.ai/code/session_01XMSRnSxYS9mRx1hSjytB9Z

tools/list at POST <mount>/mcp offers every tool the dashboard's discovered
ActiveAgent::SchemaTools classes generate — find_<records>, count_<records>,
get_<record> — beside the run_<slug> agents, each with its own parameter
schema, and tools/call runs one as the key's caller through the host's own
scope, exactly as it would inside an agent run. A boundary violation is a
tool result with isError; a refusal from the host's scope is a JSON-RPC
-32003; neither the execution switch nor the execution quota applies,
because nothing generates. ActionAgent.mcp_schema_tools = false keeps the
tools reachable only through agents.

ActiveAgent::NotAuthorized becomes autoloadable: it was defined only when
Base loaded, and the engine referencing it first raised NameError.

Also records this batch of work under docs/work/schema-tools-follow-ups.

Closes #439.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XMSRnSxYS9mRx1hSjytB9Z
@TonsOfFun
TonsOfFun changed the base branch from claude/zealous-turing-4afxvn to main September 12, 2026 19:24
@TonsOfFun
TonsOfFun merged commit 424df2e into main Sep 12, 2026
21 of 24 checks passed
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.

Feature: a model's agent is also an MCP server, so its tools are callable in-process or by an external agent

1 participant