Repository navigation
feat(core): direct MCP tool calls, live registry sources, dynamic settings parity - #595
Merged
Merged
Conversation
…tings parity
## What changed
- **core/tool/mcp**: `Source.CallTool` invokes one tool on a connected server
directly, bypassing the registry that renames, re-scopes and re-authorizes a
call. The result comes back as raw JSON — text content when the server sent
any, structured content when it sent no text mirror, `{}` when it sent
neither, non-JSON text as a JSON string. Unknown server is NotFound, a
server without a finished handshake is NotAvailable, an errored result is an
ordinary error carrying the server's own text.
- **core/tool**: `RegistryFactory` attaches every source implementing
`RegistryAttacher` after construction, so a registry built from
`tool.registry` settings stays live like an assembly mount point instead of
missing publications that arrive after the fact.
- **core/deploy**: `Result.Resolver()` and `Result.Secrets()` expose the
effective settings reference resolver and secret resolver the build
assembled.
- **core/runtime**: `RegisterAgent` and `Reload` pass those resolvers into
`deploy.BindAgent`; a dynamic or reloaded agent previously bound with
`nil, nil`, so a setting using a custom scheme or `${secret:...}` failed
when the same definition was registered at runtime.
- **tests**: `TestDynamicAgentExpandsLikeDeployment` (register + reload paths,
via an engine factory that captures the settings it was constructed with)
and the `CallTool` suite (payload shapes, unknown server, unconnected
server, error propagation).
## Verification
- `make ci` (vet + test across the workspace) passes.
- `make lint` — 0 issues; `gofmt -s -l core` is empty.
- `make release-check` validates the changeset and the pending plan.
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.
Why
A host that wires MCP servers into its runtime can reach their tools only through the tool registry, which renames a tool (
server__tool), scopes it to an agent and authorizes the call against that agent's grant set. That is the right contract for tools a model may pick from, and the wrong one for a graph node that already knows which tool it means — the shape the plugin-provided graph nodes consume.Two smaller gaps come with it:
tool.registrysettings stayed frozen at construction, so a source that publishes after the fact (a refresh, a background MCP connect) reached an assembly mount point but never the mounted registry.Runtime.RegisterAgent) or re-bound byRuntime.Reloadexpanded its settings withnilresolvers: a card or engine setting using a custom scheme or${secret:...}worked in a deployment and failed the moment the same definition arrived at runtime.What changed
Source.CallTool(ctx, server, tool, args)calls one tool on a connected server directly, bypassing the registry. The result is raw JSON: the text content when the server sent any, the structured content when it sent no text mirror (SEP-2106 lets a server answer with data and no text, and reading "no text" as "no result" collapsed a real answer into{}without an error),{}when the result carries neither, and non-JSON text as a JSON string so the caller always holds valid JSON. An unknown server is NotFound, a server whose handshake has not finished is NotAvailable instead of a nil-session panic, and an errored result is an ordinary error carrying the server's own text or its structured content.RegistryFactoryattaches every source that implementsRegistryAttacherright after construction, so a registry built from settings stays live exactly like an assembly mount point.Result.Resolver()andResult.Secrets()expose the effective settings reference resolver and secret resolver the build assembled (nilwhen the deployment declared none).RegisterAgentandReloadpass those resolvers intodeploy.BindAgent, so a dynamic or reloaded agent expands custom schemes and${secret:...}exactly like a deployment-time one.TestDynamicAgentExpandsLikeDeployment(an engine factory captures the settings it was constructed with, on both the register and the reload re-bind path) and theCallToolsuite (payload shapes, unknown server, unconnected server, error propagation).Verification
make ci(vet +go test ./...across the workspace) passes.make lint— 0 issues;gofmt -s -l coreis empty;git diff --checkclean.make release-check— changesets valid;make release-planreportscore: 0.4.9 -> 0.4.10 (patch), tag core/v0.4.10.Release notes
.release/core-v0.4.10.jsoncovers the accumulated delta sincecore/v0.4.9: this PR plus the memory provenance change (Turn.MessageMetadata,SourceRef.ID) that shipped in feat(backends/memory): add the memory backend and its eval harness #583.coreis the only release-managed module and nothing here changes a contract adriver/*module compiles against, somake release-preflightis not needed.