fix: collect all JSON entries from MCP content arrays instead of only the first

Previously json() in createCallResult returned early on the first
parseable JSON entry. When an MCP server returned multiple results
in the content array, only the first item was ever surfaced.

Now all parseable entries are collected. A single item still returns
as a single object (backward compatible); multiple items return as
an array.

Also increased inspect depth in printRaw from 2 to null to prevent
truncation of deeply nested list results.
This commit is contained in:
Blankdlh 2026-03-01 21:47:24 +08:00 committed by Peter Steinberger
parent 98c300b3dc
commit 9746dab9d2
3 changed files with 41 additions and 4 deletions

View File

@ -156,5 +156,5 @@ function printRaw(raw: unknown): void {
console.log(raw.toString());
return;
}
console.log(inspect(raw, { depth: 2, maxStringLength: null, breakLength: 80 }));
console.log(inspect(raw, { depth: null, maxStringLength: null, breakLength: 80 }));
}

View File

@ -169,13 +169,14 @@ export function createCallResult<T = unknown>(raw: T): CallResult<T> {
const content = extractContentArray(raw);
if (content) {
const collected: unknown[] = [];
for (const entry of content) {
if (entry && typeof entry === 'object') {
const typedEntry = entry as Record<string, unknown>;
if (typedEntry.type === 'json') {
const parsed = tryParseJson(entry);
if (parsed !== null) {
return parsed as J;
collected.push(parsed);
}
continue;
}
@ -184,7 +185,7 @@ export function createCallResult<T = unknown>(raw: T): CallResult<T> {
if (typeof text === 'string') {
const parsedText = tryParseJson(text);
if (parsedText !== null) {
return parsedText as J;
collected.push(parsedText);
}
}
continue;
@ -193,10 +194,16 @@ export function createCallResult<T = unknown>(raw: T): CallResult<T> {
if (typeof entry === 'string') {
const parsed = tryParseJson(entry);
if (parsed !== null) {
return parsed as J;
collected.push(parsed);
}
}
}
if (collected.length === 1) {
return collected[0] as J;
}
if (collected.length > 1) {
return collected as J;
}
}
if (typeof raw === 'string') {

View File

@ -143,6 +143,36 @@ describe('createCallResult json extraction', () => {
expect(result.json()).toEqual({ foo: 'bar' });
});
it('returns all items when content array has multiple json entries', () => {
const response = {
content: [
{ type: 'json', json: { id: 1, name: 'first' } },
{ type: 'json', json: { id: 2, name: 'second' } },
{ type: 'json', json: { id: 3, name: 'third' } },
],
};
const result = createCallResult(response);
expect(result.json()).toEqual([
{ id: 1, name: 'first' },
{ id: 2, name: 'second' },
{ id: 3, name: 'third' },
]);
});
it('returns all items when content has mixed json and text-parseable-as-json entries', () => {
const response = {
content: [
{ type: 'json', json: { source: 'json-type' } },
{ type: 'text', text: '{"source":"text-type"}' },
],
};
const result = createCallResult(response);
expect(result.json()).toEqual([
{ source: 'json-type' },
{ source: 'text-type' },
]);
});
it('extracts json from structuredContent nested inside raw wrapper', () => {
const response = {
raw: {