import { adcpError } from '@adcp/sdk';
server.registerTool("get_products", { inputSchema: schema }, async ({ query }) => {
if (!products.length) {
return adcpError('PRODUCT_NOT_FOUND', {
message: 'No products match query',
field: 'query',
suggestion: 'Try a broader search term',
});
}
return { content: [...], structuredContent: { products } };
});
Build an L3-compliant MCP tool error response with all three transport layers:
structuredContent.adcp_error— programmatic extraction (L3)content[0].text— JSON text fallback (L2)isError: true— MCP error signalRecovery is auto-populated from the standard error code table when not provided.
Before returning, any field NOT allowlisted for the given code in ADCP_ERROR_FIELD_ALLOWLIST is dropped — sellers get the builder's ergonomics for every code AND the strict wire shape for codes that have a registered allowlist.
IDEMPOTENCY_CONFLICTis the canonical case:recovery,field,suggestion, anddetailsall silently drop so the envelope can't become a stolen-key read oracle. Codes without a registered allowlist pass through unchanged.Two-layer wire shape. This builder emits the envelope layer (
structuredContent.adcp_error) only. For tools whose response schema declares a typed Error arm (errors[]required at the top level), the framework dispatcher synthesises the payload-layererrors[]from the same data at finalize time, so the wire carries both the envelope marker and the typed Error arm together — no adopter code change required. The list of affected tools is derived at server build from the bundled schema cache. RFC:docs/proposals/adcperror-two-layer-emission.md.