Skip to content

Output Schema

Normative language (MUST/SHOULD/MAY) follows the conventions defined in Conformance Language.

Output schemas make tool responses predictable. AI clients can know in advance what shape the data will have, enabling structured reasoning without parsing guesswork. This document defines the output declaration format, supported types, the response envelope, handler interaction, and validation rules.


Without output schemas, an AI client calling a FlowMCP tool receives an opaque blob of JSON. The client MUST infer the structure from context, previous calls, or the tool description — all unreliable strategies.

Output schemas solve this by declaring the expected response shape at the route level:

  • AI clients can pre-allocate structured reasoning about the response fields
  • Schema validators can verify that handler output matches the declaration
  • Documentation generators can produce accurate response tables automatically
  • Type-aware consumers can generate TypeScript interfaces or Zod schemas from the output definition

Route Definition

output.schema

AI Client: knows fields in advance

Validator: checks handler output

Docs: auto-generated tables

The diagram shows how a single output schema declaration serves three consumers: AI clients, validators, and documentation tools.


Each route can optionally define an output field alongside its method, path, description, and parameters:

routes: {
getTokenPrice: {
method: 'GET',
path: '/simple/price',
description: 'Get current token price',
parameters: [ /* ... */ ],
output: {
mimeType: 'application/json',
schema: {
type: 'object',
properties: {
id: { type: 'string', description: 'Token identifier' },
symbol: { type: 'string', description: 'Token symbol' },
price: { type: 'number', description: 'Current price in USD' },
marketCap: { type: 'number', description: 'Market capitalization', nullable: true },
volume24h: { type: 'number', description: 'Trading volume (24h)' }
}
}
}
}
}

The output field lives in the main block and is therefore part of the hashable, JSON-serializable schema surface. It MUST NOT contain functions or dynamic expressions.


FieldTypeRequiredDescription
mimeTypestringYesResponse content type
schemaobjectYesSimplified JSON Schema describing the data field

Both fields are required when output is present. If a route does not declare output, the entire field is omitted (not set to null or {}).


MIME-TypeDescriptionSchema type
application/jsonJSON response (default)object or array
image/pngPNG image, base64-encodedstring with format: 'base64'
text/plainPlain text responsestring

The mimeType constrains which schema.type values are valid:

  • application/json requires type: 'object' or type: 'array'
  • image/png requires type: 'string' with format: 'base64'
  • text/plain requires type: 'string'

A mismatch between mimeType and schema.type is a validation error.


Every FlowMCP tool response is wrapped in a standard envelope. This envelope is the same for all routes and does not need per-route definition.

{
status: true,
messages: [],
data: { /* described by output.schema */ }
}
{
status: false,
messages: [ 'E001 getTokenPrice: API returned 404' ],
data: null
}
FieldTypeDescription
statusbooleantrue on success, false on error
messagesarrayEmpty on success, error descriptions on failure
dataobject or nullResponse payload on success, null on error

The output.schema describes only the data field when status: true. Schema authors do not declare the envelope — it is implicit and standardized across all tools.


FlowMCP uses a deliberately constrained subset of JSON Schema. This avoids the complexity of full JSON Schema while covering the needs of API response descriptions.

KeywordDescriptionExample
typeValue type'string', 'number', 'boolean', 'object', 'array'
propertiesObject properties{ name: { type: 'string' } }
itemsArray item schema{ type: 'object', properties: {...} }
descriptionHuman-readable description'Current price in USD'
nullableCan be nulltrue
enumAllowed values['active', 'inactive']
formatSpecial format hint'base64', 'date-time', 'uri'

The following JSON Schema keywords are intentionally excluded:

  • $ref — no schema references; output schemas are self-contained
  • oneOf, anyOf, allOf — no union types; keep schemas simple
  • required — all declared properties are informational, not enforced
  • additionalProperties — APIs MAY return extra fields; the schema describes the guaranteed minimum
  • pattern — no regex validation on output fields
  • minimum, maximum — no range validation on output fields
TypeJavaScript equivalentDescription
stringtypeof x === 'string'Text value
numbertypeof x === 'number'Numeric value (integer or float)
booleantypeof x === 'boolean'True or false
objectPlain objectNested structure with properties
arrayArrayCollection with items schema

The most common response type. The schema declares an object with named properties:

output: {
mimeType: 'application/json',
schema: {
type: 'object',
properties: {
id: { type: 'string', description: 'Token identifier' },
symbol: { type: 'string', description: 'Token symbol' },
price: { type: 'number', description: 'Current price in USD' },
marketCap: { type: 'number', description: 'Market capitalization', nullable: true },
volume24h: { type: 'number', description: 'Trading volume (24h)' }
}
}
}

Properties can themselves be objects, up to 4 levels deep:

output: {
mimeType: 'application/json',
schema: {
type: 'object',
properties: {
token: {
type: 'object',
description: 'Token metadata',
properties: {
name: { type: 'string', description: 'Token name' },
contract: {
type: 'object',
description: 'Contract details',
properties: {
address: { type: 'string', description: 'Contract address' },
verified: { type: 'boolean', description: 'Verification status' }
}
}
}
}
}
}
}

For routes that return lists, the schema uses type: 'array' with an items definition:

output: {
mimeType: 'application/json',
schema: {
type: 'array',
items: {
type: 'object',
properties: {
name: { type: 'string', description: 'Protocol name' },
tvl: { type: 'number', description: 'Total value locked in USD' }
}
}
}
}

Arrays can also contain primitive types:

output: {
mimeType: 'application/json',
schema: {
type: 'array',
items: {
type: 'string',
description: 'Contract address'
}
}
}

Properties within objects can be arrays:

output: {
mimeType: 'application/json',
schema: {
type: 'object',
properties: {
total: { type: 'number', description: 'Total result count' },
results: {
type: 'array',
description: 'List of matching tokens',
items: {
type: 'object',
properties: {
symbol: { type: 'string', description: 'Token symbol' },
price: { type: 'number', description: 'Current price' }
}
}
}
}
}
}

For routes that return images (charts, QR codes, visual data), the schema declares a base64-encoded string:

output: {
mimeType: 'image/png',
schema: {
type: 'string',
format: 'base64',
description: 'Chart image as base64-encoded PNG'
}
}

The runtime base64-encodes the binary response and places it in the data field of the envelope. AI clients that support image rendering can decode and display the image.


For routes that return plain text:

output: {
mimeType: 'text/plain',
schema: {
type: 'string',
description: 'Raw contract source code'
}
}

Fields that MAY be null in a successful response MUST declare nullable: true:

properties: {
marketCap: { type: 'number', description: 'Market capitalization', nullable: true },
website: { type: 'string', description: 'Project website URL', nullable: true }
}

Without nullable: true, a null value in the response triggers a validation warning. This distinction helps AI clients differentiate between “field not available for this entry” (nullable) and “field SHOULD always be present” (not nullable).


Output fields can declare enum to restrict values to a known set:

properties: {
status: {
type: 'string',
description: 'Verification status',
enum: ['verified', 'unverified', 'pending']
}
}

This helps AI clients reason about possible values without inspecting raw data.


The format keyword provides additional semantic information about a string field:

FormatDescriptionExample value
base64Base64-encoded binary data'iVBORw0KGgo...'
date-timeISO 8601 date-time'2026-02-16T12:00:00Z'
uriValid URI'https://etherscan.io/address/0x...'

Format is informational — the runtime does not validate format compliance. It exists to give AI clients and documentation generators better context.


If a route does not define an output field:

  • The response is treated as application/json by default
  • The data field is passed through without schema validation
  • AI clients cannot rely on a specific shape
  • This is valid but discouraged for new schemas

Omitting the output schema is acceptable for:

  • Legacy schemas migrating from v1.x
  • Routes with highly variable response shapes (rare)
  • Exploratory schemas during development

For production schemas, the output schema SHOULD always be declared.


When a route has a postRequest handler, the output schema describes the final response after handler transformation, not the raw API response.

Yes

No

API Response

postRequest Handler

Transformed Data

Matches output.schema?

Return in envelope

Validation warning

The diagram shows the validation point: the output schema is checked against the handler’s return value, not the raw API response.

  • The output.schema must describe the shape after postRequest transforms the data
  • If postRequest flattens nested API responses, the schema describes the flat structure
  • If postRequest renames fields, the schema uses the new names
  • If no postRequest handler exists, the schema describes the raw API response directly
// Raw API response from CoinGecko:
// { "bitcoin": { "usd": 45000, "usd_market_cap": 850000000000 } }
// postRequest handler (inside factory) flattens it:
export const handlers = ( { sharedLists, libraries } ) => ({
getTokenPrice: {
postRequest: async ( { response, struct, payload } ) => {
const [ id ] = Object.keys( response )
const { usd, usd_market_cap } = response[ id ]
return { response: { id, price: usd, marketCap: usd_market_cap } }
}
}
})
// Output schema describes the FLATTENED result:
output: {
mimeType: 'application/json',
schema: {
type: 'object',
properties: {
id: { type: 'string', description: 'Token identifier' },
price: { type: 'number', description: 'Price in USD' },
marketCap: { type: 'number', description: 'Market capitalization in USD' }
}
}
}

The following rules are enforced when validating output schemas:

mimeType must be one of the supported types: application/json, image/png, text/plain. Unknown MIME-types are rejected.

schema.type must be compatible with the declared mimeType:

mimeTypeAllowed schema.type
application/jsonobject, array
image/pngstring (with format: 'base64')
text/plainstring

properties is only valid when type is 'object'. Declaring properties on a string or array type is a validation error.

items is only valid when type is 'array'. Declaring items on a string or object type is a validation error.

Maximum nesting depth is 4 levels. This prevents overly complex schemas that are difficult for AI clients to reason about:

Level 1: output.schema (root)
Level 2: output.schema.properties.token
Level 3: output.schema.properties.token.properties.contract
Level 4: output.schema.properties.token.properties.contract.properties.address

A 5th level is rejected.

nullable: true means the field can be null in a successful response (when status: true). It does not mean the field can be absent — absent fields are not described by the schema at all.

Output schema validation is non-blocking. A mismatch between the actual handler output and the declared schema produces a validation warning, not an error. The response is still delivered to the client.

This design choice reflects the reality that external APIs MAY change their response shapes without notice. A strict error would break the tool even though the data might still be usable. The warning is logged and surfaced to schema maintainers for review.

No

Yes

Yes

No

Handler returns data

Schema declared?

Pass through, no check

Data matches schema?

Return data

Log warning

Return data anyway

The diagram shows the non-blocking validation flow: mismatches produce warnings but do not prevent the response from being delivered.