Skip to content

Add @mcpToolArg directive for MCP argument descriptions #2435

@shamashel

Description

@shamashel

Component(s)

router

Is your feature request related to a problem? Please describe.

Currently, MCP tool arguments only get descriptions if the underlying GraphQL type (e.g., SearchInput) has a description in the federated schema. There's no way to document individual operation variables at the operation level.

This is a problem because:

  1. LLM context - AI agents benefit from descriptive argument names and descriptions to understand how to use tools
  2. Operation-specific semantics - A $id variable might mean "employee ID" in one operation and "product ID" in another; the type-level description can't capture this
  3. Self-documenting operations - Operations should be self-contained documentation units without requiring schema modifications

Related to #2308 and #2309 which added @mcpTool(name:) and operation-level descriptions.

Describe the solution you'd like

Add an @mcpToolArg directive that can be applied to variable definitions to specify argument descriptions:

"""
Retrieve an employee by their unique identifier.
"""
query GetEmployee(
  $id: Int! @mcpToolArg(description: "The unique employee identifier")
  $includeDetails: Boolean @mcpToolArg(description: "Include extended profile details")
) @mcpTool(name: "get_employee") {
  employee(id: $id) {
    id
    name
    details @include(if: $includeDetails) { email department }
  }
}

This would produce an MCP tool with:

{
  "name": "get_employee",
  "description": "Retrieve an employee by their unique identifier.",
  "inputSchema": {
    "type": "object",
    "properties": {
      "id": { "type": "integer", "description": "The unique employee identifier" },
      "includeDetails": { "type": "boolean", "description": "Include extended profile details" }
    },
    "required": ["id"]
  }
}

Implementation approach (Cosmo-only, no upstream changes):

  1. Add VariableDescriptions map[string]string field to Operation struct in router/pkg/schemaloader/loader.go
  2. Implement extractMCPToolArgDescriptions() to parse @mcpToolArg directives from variable definitions
  3. Implement stripMCPToolArgDirectives() (similar to existing stripMCPDirective)
  4. In schema_builder.go, after jsonschema.BuildJsonSchema() returns, merge descriptions into the JSON schema properties
  5. Add tests

Notes:

  • required continues to be inferred from GraphQL nullability (!)
  • Directive location is VARIABLE_DEFINITION (spec-compliant since GraphQL Sep 2025)
  • Directive must be stripped before validation to avoid "unknown directive" errors

Describe alternatives you've considered

Alternative 1: Upstream to graphql-go-tools

Extend BuildJsonSchema in graphql-go-tools/v2/pkg/engine/jsonschema to accept variable descriptions as an option:

type BuildOptions struct {
    VariableDescriptions map[string]string
}
func BuildJsonSchema(opDoc, schemaDoc *ast.Document, opts ...BuildOptions) (*JSONSchema, error)

Pros: Cleaner separation, reusable. Cons: Requires upstream PR and release cycle.

Alternative 2: Use Operation Description

This is generally the approach we'd currently use as a workaround. Basically, we add a docstring description to the operation and include a description of arguments and their purposes.

Pros: it works. Cons: Non-standard, so it shows up poorly in upstream MCP registries

Alternative 3: External metadata file

Separate YAML/JSON file mapping operation variables to descriptions. Cons: Disconnected from operations, harder to maintain.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions