Skip to content

feat(mcpgen,mcpserver): @mcpToolArg for tool-only arguments#35

Merged
zer0stars merged 1 commit into
mainfrom
feat/mcpgen-tool-only-args
Apr 23, 2026
Merged

feat(mcpgen,mcpserver): @mcpToolArg for tool-only arguments#35
zer0stars merged 1 commit into
mainfrom
feat/mcpgen-tool-only-args

Conversation

@zer0stars

Copy link
Copy Markdown
Member

Summary

  • Repeatable `@mcpToolArg(name, type, description)` directive for declaring MCP-tool-only arguments that exist purely to feed `SelectionTemplate` — the underlying GraphQL field stays untouched.
  • `ArgDefinition` gains `ToolOnly bool`; mcpserver strips those args from GraphQL variables before executing.
  • Small type-string parser so the directive's `type:` argument can reference any input/scalar/list from the loaded schema (e.g. `[SignalAggregationRequest!]!`).

Why

Builds on the templated selections from #34 (v0.2.0). That release exposed a new problem: to feed the template, the arg had to live on the GraphQL field, which means every repo adopting this pattern has to modify long-standing query signatures. For telemetry-api's `signals` / `signalsLatest`, that's a non-starter. `@mcpToolArg` keeps the change at the MCP layer where it belongs.

Example

```graphql
signals(tokenId: Int!, interval: String!, from: Time!, to: Time!, filter: SignalFilter): [SignalAggregations!]
@mcptool(
name: "get_signals_time_series"
description: "..."
selection: "timestamp{{range .signalRequests}} {{.name}}(agg: {{.agg}}){{end}}"
)
@mcpToolArg(
name: "signalRequests"
type: "[SignalAggregationRequest!]!"
description: "..."
)
```

The GraphQL field and its arguments are unchanged. The MCP tool gets `signalRequests` as a required input. On each call the template expands it into a per-signal selection set; `signalRequests` is dropped before the GraphQL executor runs.

Changes

  • `pkg/mcpserver/mcpserver.go`: `ArgDefinition.ToolOnly`.
  • `pkg/mcpserver/tools.go`: strip tool-only args from variables before executing.
  • `cmd/mcpgen/condensed.go`: parse `@mcpToolArg` directives and append to the tool's Args with `ToolOnly: true`.
  • `cmd/mcpgen/generate.go`: small GraphQL type-string parser (`parseTypeString`); emit `ToolOnly: true` in the generated Go.
  • Tests: `TestToolOnlyArg`, `TestToolOnlyArgGeneratedOutput`, `TestParseTypeString`, `TestToolOnlyArgStrippedFromVariables`; `testdata/tool_only_arg.graphqls`.

Test plan

  • `make lint` clean
  • `go test ./...` all pass (including the new server-side stripping test)
  • Bump in telemetry-api and convert `signals` / `signalsLatest` to use `@mcpToolArg` — no changes to the GraphQL field signatures — then call the MCP tools end-to-end

Shortcut tools that need an argument-driven selection (e.g. signals
time-series where each {name, agg} pair becomes a sub-field) currently
require that argument to exist on the GraphQL field itself — which
forces a schema change on queries that have been stable for years.

Add a repeatable @mcpToolArg directive that declares arguments belonging
only to the MCP tool. They feed SelectionTemplate via the same args map,
and mcpserver strips them from the variables map before the GraphQL
executor is called, so the underlying field never sees them.

Usage:

    signals(tokenId: Int!, interval: String!, from: Time!, to: Time!, filter: SignalFilter): [SignalAggregations!]
      @mcptool(
        name: "get_signals_time_series"
        description: "..."
        selection: "timestamp{{range .signalRequests}} {{.name}}(agg: {{.agg}}){{end}}"
      )
      @mcpToolArg(
        name: "signalRequests"
        type: "[SignalAggregationRequest!]!"
        description: "..."
      )

The GraphQL field keeps its existing arguments and return type. The MCP
tool gets `signalRequests` as a required input. At call time the
template renders it into the selection, and `signalRequests` is dropped
from the variables map before executing.

ArgDefinition gets a `ToolOnly bool` field; mcpgen emits it alongside
the other ArgDefinition fields and reuses the existing JSON-schema
mapping via a small ad-hoc type-string parser so the `type:` argument
of the directive can name any loaded GraphQL type (e.g.
"[SignalAggregationRequest!]!", "[String!]!", "Int").
@zer0stars
zer0stars merged commit bd43ea0 into main Apr 23, 2026
4 checks passed
@zer0stars
zer0stars deleted the feat/mcpgen-tool-only-args branch April 23, 2026 20:00
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.

1 participant