Skip to content

Toolcraft: support MCP-only structured result mapping #527

Description

@kamilio

Problem

A Toolcraft command is shared by CLI, SDK, and MCP. MCP outputSchema requires an object-root structuredContent, but real APIs commonly return a root array, scalar success message, or empty 204/null response.

Today result: S.Object(...) validates the raw handler result. An application can wrap arrays/null into an object only by changing the shared handler, which silently changes CLI and SDK return values and leaves their inferred result type inaccurate. Omitting result avoids the break but leaves the MCP tool without an output schema.

Found while adding output schemas to Skylight Calendar: reward points return a root array, chore deletion may return a string, and multiple deletes may return an empty body.

Proposed API

Keep this optional on the existing command config:

defineCommand({
  name: "list-points",
  params: S.Object({}),
  result: S.Object({ data: S.Array(Point) }),
  mcpResult: (result) => ({ data: result }),
  handler: async () => api.listPoints() // SDK/CLI still receive the raw array
})

mcpResult is invoked only by the MCP adapter after the handler succeeds and before result validation/casing. It is not invoked by CLI or SDK.

Acceptance criteria

  • Optional mcpResult?: (result: TResult) => unknown is preserved across command materialization.
  • Defining mcpResult without result fails early with a clear error.
  • MCP validates/maps the projected value and returns matching structuredContent plus JSON text fallback.
  • CLI and SDK continue returning the original raw handler value.
  • Mapper exceptions follow normal MCP error reporting.
  • Commands that omit the mapper are unchanged.
  • HTTP and stdio MCP behavior remains identical.

This is transport-specific representation, not business logic, and avoids forcing every user of a multi-surface Toolcraft command into MCP object-envelope semantics.

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