You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[MCP/OpenAPI] Return envelope for search and list verbs #1107
Spun out of #1095 (MCP/OpenAPI shared descriptions). That issue ships outputSchema for the record-shaped verbs (get/create/update/patch/delete) where the return shape is trivially derivable from the table attributes. The search_* verb is intentionally deferred because its return shape is a wrapped envelope around the records, and the wrapper shape is an open design question.
This issue picks the wire-protocol envelope for search_* (and any future list/page verb) so that:
MCP clients get a real outputSchema they can validate against
OpenAPI emits a non-empty responses[200] schema
Both consumers share the same shape, same source
Why this is non-trivial
outputSchema/response.schema are compatibility commitments. Once shipped, MCP clients validate against the declared shape; renaming cursor → nextCursor is a breaking change. The envelope decision shouldn't be made inside a description-quality issue — it deserves its own design discussion with explicit review.
Today Harper's search_* returns an iterable that the MCP layer wraps before sending. The exact wire shape isn't widely documented; this issue should both decide the shape and write down what shipped.
The decision
Pick one envelope (or design a new one). Candidate shapes:
Option A — {records, cursor} (concise)
{
"records": [/* Product records */],
"cursor": "opaque-pagination-token-or-null"
}
Pros: minimal, matches existing internal conventions if records/cursor is what we already use.
Cons: no place for total counts, page metadata, or warnings.
Option B — {data, nextCursor} (REST API convention)
Matches common REST API patterns (GitHub, Stripe-ish).
{
"data": [/* records */],
"nextCursor": "opaque-token-or-null"
}
Pros: familiar shape to API consumers; data is the most common modern convention.
Cons: less concise; deviates from Harper's internal records vocabulary if that's what's there today.
Pros: extensible; supports per-query metadata; clear separation of data vs. meta.
Cons: more complex; over-engineered for v1.
Option D — Match existing internal shape verbatim
Check what Harper's search_* actually returns today (the wire format that REST clients already consume), commit to that shape unchanged. Zero migration risk.
Decisions needed
Envelope shape (A / B / C / D / something else)
Cursor naming (cursor / nextCursor / pageToken)
Cursor nullability (null when no more pages, or omitted)
Inclusion of hasMore / total / estimatedTotal metadata
Error semantics — does the envelope carry errors, or do they go through MCP's isError mechanism on the tool result?
Whether the same shape applies to list_*-style operations (currently list_users, list_roles, etc. in operations profile)
Investigation needed
What does Harper's current search_* actually return on the wire? (Check resources/Resource.tssearch implementation + the REST middleware that serializes it.)
Are there existing customer/integration assumptions about the response shape? (Anything in harper/documentation REST reference?)
Context
Spun out of #1095 (MCP/OpenAPI shared descriptions). That issue ships
outputSchemafor the record-shaped verbs (get/create/update/patch/delete) where the return shape is trivially derivable from the table attributes. Thesearch_*verb is intentionally deferred because its return shape is a wrapped envelope around the records, and the wrapper shape is an open design question.This issue picks the wire-protocol envelope for
search_*(and any future list/page verb) so that:outputSchemathey can validate againstresponses[200]schemaWhy this is non-trivial
outputSchema/response.schemaare compatibility commitments. Once shipped, MCP clients validate against the declared shape; renamingcursor→nextCursoris a breaking change. The envelope decision shouldn't be made inside a description-quality issue — it deserves its own design discussion with explicit review.Today Harper's
search_*returns an iterable that the MCP layer wraps before sending. The exact wire shape isn't widely documented; this issue should both decide the shape and write down what shipped.The decision
Pick one envelope (or design a new one). Candidate shapes:
Option A —
{records, cursor}(concise){ "records": [/* Product records */], "cursor": "opaque-pagination-token-or-null" }JSON Schema:
{ "type": "object", "properties": { "records": { "type": "array", "items": <ProductSchema> }, "cursor": { "type": ["string", "null"], "description": "Opaque pagination cursor; null when no more pages." } }, "required": ["records", "cursor"], "additionalProperties": false }Pros: minimal, matches existing internal conventions if
records/cursoris what we already use.Cons: no place for total counts, page metadata, or warnings.
Option B —
{data, nextCursor}(REST API convention)Matches common REST API patterns (GitHub, Stripe-ish).
{ "data": [/* records */], "nextCursor": "opaque-token-or-null" }Pros: familiar shape to API consumers;
datais the most common modern convention.Cons: less concise; deviates from Harper's internal
recordsvocabulary if that's what's there today.Option C — Richer envelope with optional metadata
{ "records": [/* records */], "pagination": { "cursor": "opaque-token-or-null", "hasMore": true, "estimatedTotal": null }, "warnings": [] }Pros: extensible; supports per-query metadata; clear separation of data vs. meta.
Cons: more complex; over-engineered for v1.
Option D — Match existing internal shape verbatim
Check what Harper's
search_*actually returns today (the wire format that REST clients already consume), commit to that shape unchanged. Zero migration risk.Decisions needed
cursor/nextCursor/pageToken)hasMore/total/estimatedTotalmetadataisErrormechanism on the tool result?list_*-style operations (currentlylist_users,list_roles, etc. in operations profile)Investigation needed
search_*actually return on the wire? (Checkresources/Resource.tssearchimplementation + the REST middleware that serializes it.)harper/documentationREST reference?)paginationwork today — query params, headers, response body field? Whatever already exists should drive the response envelope.Scope
In:
deriveSearchOutputSchema(attributes)incomponents/mcp/tools/schemas/derive.tssearch_*addToolcall inapplication.tssearch_*paths via the same shapestatic outputSchemas.searchoverride path for programmatic Resources (parallel to the other verbs from [MCP] Quality descriptions and richer tool-payload metadata for LLM tool selection #1095)Out:
deriveSearchSchemalist_users,list_roles) — consider separately or fold in once envelope shape is pickedoutputSchemaAcceptance criteria (sketch — refine after design)
deriveSearchOutputSchema(attributes)implemented inderive.tsapplication.tssearch_*addToolcall passesoutputSchemaresponses[200]for search paths emits the same shapesearch_*call's return matches the declaredoutputSchema(validated via the official MCP SDK)harper/documentationif the envelope differs from any pre-[MCP] Quality descriptions and richer tool-payload metadata for LLM tool selection #1095 wire formatDependencies
Reference
search_*outputSchema deliberately deferred there.outputSchemais optional JSON Schema on tool descriptors.