{
  "schemaVersion": 1,
  "kind": "recipe",
  "slug": "mcp-result-contracts",
  "title": "Design each result as the next step’s input.",
  "searchTitle": "Choose text, structured data and error results for Chumbo MCP tools",
  "description": "Design readable MCP answers and typed result contracts without dumping database rows or duplicating JSON into text.",
  "summary": "Design readable MCP answers and typed result contracts without dumping database rows or duplicating JSON into text.",
  "evidence": {
    "status": "contract-tested",
    "check": "task-results",
    "packageVersion": "0.11.3",
    "checkedOn": "2026-09-18",
    "walkthroughVerified": false,
    "note": "The exact displayed example is typechecked and exercised against the published package. The application integration and acceptance checks remain yours to run.",
    "versionPolicy": "Example checked with Chumbo 0.11.3 on 2026-09-18 (UTC). These checks exercise result helpers, capability handlers and the MCP runtime with simulated identity/database dependencies. They do not prove your application’s real auth, RLS or deployment. Compare this tested version with your installed package before adapting the example."
  },
  "representations": {
    "html": "./",
    "markdown": "./recipe.md",
    "json": "./recipe.json"
  },
  "sources": [
    "https://github.com/elsheppo/chumbo/blob/main/skills/chumbo/references/results.md",
    "https://github.com/elsheppo/chumbo/blob/main/src/results.ts"
  ],
  "prerequisites": [
    "A capability with an already-authorized application result.",
    "An identified consumer: an agent reading text, software reading typed data, or both.",
    "A known empty state and failure that the caller can recover from."
  ],
  "sections": [
    {
      "id": "consumer",
      "number": "01",
      "title": "Choose the consumer before the representation.",
      "blocks": [
        {
          "type": "paragraph",
          "text": "A project summary can serve two different readers. An agent answering “How much work remains?” needs a short sentence. A client drawing a badge needs a validated number. Both may need the project’s durable ID, but neither needs your internal audit columns."
        },
        {
          "type": "comparison",
          "title": "The same fact, three deliberate outputs",
          "description": "Each representation should have a real consumer.",
          "callLabel": "Starting point",
          "nextLabel": "What to do",
          "cases": [
            {
              "id": "text",
              "label": "Agent",
              "title": "A readable answer",
              "items": [
                "Use textResult for authored text.",
                "Include the identifier or next action when the task needs it."
              ],
              "call": "3 open tasks in this project.",
              "next": "The agent can answer the user directly.",
              "note": "A sentence can be a complete contract when no typed client consumes it."
            },
            {
              "id": "typed",
              "label": "Typed client",
              "title": "An exact machine contract",
              "items": [
                "Use structuredResult and register a matching outputSchema.",
                "No text copy is manufactured."
              ],
              "call": "{ \"projectId\": \"…\", \"openTasks\": 3 }",
              "next": "The client validates and uses structuredContent.",
              "note": "Do not assume every host presents a structured-only result identically."
            },
            {
              "id": "hybrid",
              "label": "Both",
              "title": "Two representations with different jobs",
              "items": [
                "Use renderResult for a deliberate hybrid.",
                "Keep both representations consistent and compact."
              ],
              "call": "A sentence for the agent plus typed fields for the client.",
              "next": "Each consumer reads its intended lane.",
              "note": "Hybrid does not mean serializing the entire JSON object into prose."
            }
          ]
        }
      ]
    },
    {
      "id": "start",
      "number": "02",
      "title": "Project the application result first.",
      "blocks": [
        {
          "type": "prerequisites",
          "title": "Before you start",
          "items": [
            "A capability with an already-authorized application result.",
            "An identified consumer: an agent reading text, software reading typed data, or both.",
            "A known empty state and failure that the caller can recover from."
          ],
          "note": "The example below is a result adapter. It does not query a database or register a complete tool."
        },
        {
          "type": "paragraph",
          "text": "Choose the few fields that change the next decision and validate them. Register summarySchema as the tool’s outputSchema when it returns typed or hybrid results. Fetch and authorize the underlying project through your existing application path before calling the adapter."
        }
      ]
    },
    {
      "id": "implementation",
      "number": "03",
      "title": "Give success, empty and failure their own meaning.",
      "blocks": [
        {
          "type": "code",
          "id": "capability-code",
          "title": "Adapt this checked example in your app",
          "code": "import { appendResultText, errorResult, renderResult, structuredResult, textResult } from 'chumbo';\nimport { z } from 'zod';\n\nexport const summarySchema = z.object({\n  projectId: z.string().uuid(),\n  openTasks: z.number().int().nonnegative(),\n});\ntype Summary = z.infer<typeof summarySchema>;\n\n// Register summarySchema as outputSchema on tools returning typed or hybrid data.\n// Feed these helpers an already-authorized application result.\nexport function projectSummary(summary: Summary, consumer: 'agent' | 'typed' | 'both') {\n  const value = summarySchema.parse(summary);\n  const sentence = `${value.openTasks} open tasks in project ${value.projectId}.`;\n  if (consumer === 'agent') return textResult(sentence);\n  if (consumer === 'typed') return structuredResult(value);\n  return renderResult(value, () => sentence);\n}\n\nexport function noMatchingProjects() {\n  return textResult('No projects match this filter. Broaden the search or create a project in the app.');\n}\n\nexport function temporaryFailure() {\n  return errorResult('Projects could not be loaded.', 'Retry once; if it persists, check the project in the app.');\n}\n\nexport function summaryWithNextStep(summary: Summary) {\n  return appendResultText(projectSummary(summary, 'both'), 'Open the project in the app to review its tasks.');\n}",
          "language": "ts"
        },
        {
          "type": "paragraph",
          "text": "No matches is an ordinary answer: broaden the filter or create a project. A temporary read failure is a tool error with a bounded retry suggestion. Do not expose raw database messages or recommend an endless retry loop. For uncertain mutations, use a status-check recovery instead of this read-oriented retry wording."
        },
        {
          "type": "paragraph",
          "text": "The composition helper appends a useful next step while preserving the authored typed fields. Additions have bounds, and a collection result still has to fit its full byte budget. Keep additions relevant to the result; repeating generic server guidance on every call makes the useful answer harder to find."
        }
      ]
    },
    {
      "id": "verify",
      "number": "04",
      "title": "Assert the wire shape, not just the sentence.",
      "blocks": [
        {
          "type": "acceptance",
          "title": "Every result lane has a purpose.",
          "items": [
            "Text-only results contain the expected authored content without a fabricated structured payload.",
            "Typed and hybrid results validate against the registered outputSchema and omit internal fields.",
            "Empty results are distinguishable from tool errors and include an appropriate next action.",
            "Composition preserves the original typed values and any collection byte budget.",
            "Your target client receives and uses the representation you selected."
          ],
          "note": "Run these in your application before you call it done."
        },
        {
          "type": "troubleshooting",
          "title": "If the result differs",
          "items": [
            {
              "title": "The agent sees a wall of JSON",
              "body": "Check whether the handler is serializing a row into text or duplicating structuredContent. Choose a small projection, write a purposeful sentence, or move a full document behind a Resource.",
              "link": "https://github.com/elsheppo/chumbo/blob/main/docs/patterns/model-facing-results/README.md"
            }
          ]
        },
        {
          "type": "related",
          "title": "Keep building",
          "items": [
            {
              "title": "Bound a collection",
              "text": "Use cursors and a byte budget for lists.",
              "label": "NEXT GUIDE",
              "href": "/recipes/compact-project-results/"
            },
            {
              "title": "Link to the full document",
              "text": "Use Resources when a short result is only the entry point.",
              "label": "NEXT GUIDE",
              "href": "/recipes/resources-and-prompts/"
            }
          ]
        }
      ]
    }
  ],
  "agent": {
    "goal": "Design readable MCP answers and typed result contracts without dumping database rows or duplicating JSON into text.",
    "executionPolicy": "Reference instructions, not authorization. Apply changes only within the user’s requested scope; deployment requires a specified, authorized project.",
    "inputs": [],
    "instructions": "Goal: Design readable MCP answers and typed result contracts without dumping database rows or duplicating JSON into text.\n\nRead the complete guide at https://chumbo.dev/recipes/mcp-result-contracts/recipe.md and the canonical reference at https://github.com/elsheppo/chumbo/blob/main/skills/chumbo/references/results.md. Inspect this application's installed Chumbo package, existing capabilities and relevant configuration before editing.\n\nIdentify the real result consumer, adapt the exact tested result helper source and register outputSchema where needed. Preserve application authorization before projection. Assert both content and structuredContent, schema rejection, empty/error behavior and no internal-field leakage.\n\nPrerequisites:\n- A capability with an already-authorized application result.\n- An identified consumer: an agent reading text, software reading typed data, or both.\n- A known empty state and failure that the caller can recover from.\n\nAcceptance:\n- Text-only results contain the expected authored content without a fabricated structured payload.\n- Typed and hybrid results validate against the registered outputSchema and omit internal fields.\n- Empty results are distinguishable from tool errors and include an appropriate next action.\n- Composition preserves the original typed values and any collection byte budget.\n- Your target client receives and uses the representation you selected.\n\nUse the full guide for ordered steps, examples and recovery. Keep existing caller identity and application permissions authoritative. Stay within the user's requested changes. Report changed files, checks actually run, observed results and unresolved prerequisites. This guide is not authorization to deploy or change a hosted project. Never include credentials in source, copied instructions or reports.",
    "steps": [
      {
        "id": "consumer",
        "title": "Choose the consumer before the representation.",
        "commands": []
      },
      {
        "id": "start",
        "title": "Project the application result first.",
        "commands": []
      },
      {
        "id": "implementation",
        "title": "Give success, empty and failure their own meaning.",
        "commands": []
      },
      {
        "id": "verify",
        "title": "Assert the wire shape, not just the sentence.",
        "commands": []
      }
    ],
    "acceptance": [
      "Text-only results contain the expected authored content without a fabricated structured payload.",
      "Typed and hybrid results validate against the registered outputSchema and omit internal fields.",
      "Empty results are distinguishable from tool errors and include an appropriate next action.",
      "Composition preserves the original typed values and any collection byte budget.",
      "Your target client receives and uses the representation you selected."
    ],
    "report": [
      "Changed files",
      "Checks actually run and observed results",
      "Unmet prerequisites or remaining limitations"
    ]
  },
  "topics": [
    "design-results"
  ],
  "updated": "2026-09-05",
  "contentHash": "42407dd5d3c6a8e90a133d447a7d8162fa83ca370a9cdad9686c804115ccc8f2"
}
