Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/calm-tools-show.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@adcp/sdk': patch
---

Mirror final MCP `structuredContent` into a serialized text block so text-only clients retain complete AdCP results.
12 changes: 12 additions & 0 deletions src/lib/server/create-adcp-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5201,6 +5201,18 @@ export function createAdcpServer<TAccount = unknown>(config: AdcpServerConfig<TA

const applyResponseEnhancer = (response: McpToolResponse): McpToolResponse => {
responseEnhancer?.(response);
const structuredContent = response.structuredContent;
if (structuredContent !== undefined) {
const serialized = JSON.stringify(structuredContent);
const hasSerializedFallback = response.content.some(block => block.type === 'text' && block.text === serialized);
if (!hasSerializedFallback) {
// MCP recommends mirroring structuredContent into a TextContent block
// for clients that do not expose the structured channel to the model.
// Preserve any adopter-authored summary as the first block and append
// the exact final wire object after every framework/enhancer rewrite.
response.content.push({ type: 'text', text: serialized });
}
}
return response;
};

Expand Down
34 changes: 34 additions & 0 deletions test/server-create-adcp-server.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -397,6 +397,40 @@ describe('createAdcpServer', () => {
assert.deepStrictEqual(sawParams, { brief: 'premium' });
assert.ok(result.content, 'CallToolResult should carry content');
assert.ok(result.structuredContent, 'CallToolResult should carry structuredContent');
assert.ok(
result.content.some(block => block.type === 'text' && block.text === JSON.stringify(result.structuredContent)),
'CallToolResult should serialize structuredContent for text-only MCP clients'
);
});

it('preserves the human summary and appends one final structured-content fallback', async () => {
const server = createAdcpServer({
name: 'Test',
version: '1.0.0',
responseEnhancer: response => {
response.structuredContent.enhanced = true;
},
mediaBuy: {
getProducts: async () => ({
products: [
{
product_id: 'p1',
name: 'CTV package',
pricing_options: [{ pricing_model: 'cpm', rate: 12, currency: 'USD' }],
},
],
cache_scope: 'public',
}),
},
});

const result = await callToolRaw(server, 'get_products', {});
const serialized = JSON.stringify(result.structuredContent);

assert.strictEqual(result.content[0].text, 'Found 1 products');
assert.strictEqual(result.content.filter(block => block.text === serialized).length, 1);
assert.strictEqual(JSON.parse(result.content.at(-1).text).products[0].name, 'CTV package');
assert.strictEqual(JSON.parse(result.content.at(-1).text).enhanced, true);
});

it('dispatchTestRequest throws for unknown tools and methods', async () => {
Expand Down
Loading