{"record":{"id":"8e4e51bce34f4c34","repo":"aaif-goose/goose","slug":"resource-fallbackuri-returned-no-contents","errorCode":null,"errorMessage":"Resource '${fallbackUri}' returned no contents","messagePattern":"Resource '(.+?)' returned no contents","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"ui/desktop/src/acp/mcp-apps.ts","lineNumber":51,"sourceCode":"  return isRecord(meta) ? meta : undefined;\n}\n\nfunction decodeBase64Text(blob: string): string {\n  let bytes: Uint8Array;\n  if (typeof globalThis.atob === 'function') {\n    const binary = globalThis.atob(blob);\n    bytes = Uint8Array.from(binary, (char) => char.charCodeAt(0));\n  } else {\n    bytes = Uint8Array.from(Buffer.from(blob, 'base64'));\n  }\n  return new TextDecoder().decode(bytes);\n}\n\nfunction flattenReadResourceResult(result: unknown, fallbackUri: string): McpAppResourceResponse {\n  const contents = isRecord(result) && Array.isArray(result.contents) ? result.contents : [];\n  const first = contents.find(isRecord);\n  if (!first) {\n    throw new Error(`Resource '${fallbackUri}' returned no contents`);\n  }\n\n  const uri = stringField(first, 'uri') ?? fallbackUri;\n  const mimeType = stringField(first, 'mimeType') ?? stringField(first, 'mime_type') ?? null;\n  const text = stringField(first, 'text') ?? decodeBase64Text(stringField(first, 'blob') ?? '');\n\n  return {\n    uri,\n    mimeType,\n    text,\n    _meta: metaField(first),\n  };\n}\n\nfunction acpApp(value: unknown): GooseApp | null {\n  if (!isRecord(value)) return null;\n  return value as GooseApp;\n}","sourceCodeStart":33,"sourceCodeEnd":69,"githubUrl":"https://github.com/aaif-goose/goose/blob/3810898a7447ec3299be72e223d3570a7aabf0ab/ui/desktop/src/acp/mcp-apps.ts#L33-L69","documentation":"Thrown by flattenReadResourceResult in mcp-apps.ts when an MCP readResource result has no usable contents: either result.contents is missing/not an array, or no element of it passes isRecord. The function expects the standard MCP ReadResourceResult shape ({contents: [{uri, mimeType?, text? | blob?}]}) and takes the first object entry. An empty array means the server acknowledged the read but returned nothing for the requested URI.","triggerScenarios":"Calling the MCP app resource fetch (readResource with a fallbackUri) where the app/server does not expose that resource; server bug returning {contents: []}; a response shaped differently (e.g. {result: {contents: [...]}}) so the outer contents check fails; empty resource file behind the server.","commonSituations":"Deep links or app-install flows requesting a manifest/resource URI the server version no longer serves; MCP server returning 200 with empty contents for unknown URIs instead of an error; schema drift between the server and this client's expectations.","solutions":["Call the server's resources/list and confirm the requested URI exists before reading it.","Inspect the raw readResource response in devtools/network to see the actual shape — if it is nested, adjust the flattening.","Fix the MCP server to return an explicit error (or valid contents) instead of an empty contents array.","If the resource is legitimately empty, handle this error in the caller and show 'resource unavailable' rather than crashing the app flow."],"exampleFix":"// before\nconst contents = isRecord(result) && Array.isArray(result.contents) ? result.contents : [];\nconst first = contents.find(isRecord);\nif (!first) {\n  throw new Error(`Resource '${fallbackUri}' returned no contents`);\n}\n\n// after (clearer error including what came back)\nconst contents = isRecord(result) && Array.isArray(result.contents) ? result.contents : [];\nconst first = contents.find(isRecord);\nif (!first) {\n  throw new Error(`Resource '${fallbackUri}' returned no contents (got ${JSON.stringify(result)?.slice(0, 200)})`);\n}","handlingStrategy":"validation","validationCode":"// Confirm the resource exists before reading it\nconst listed = await client.readResource ?? null; // placeholder guard for typed clients\nasync function resourceExists(client: McpClient, uri: string): Promise<boolean> {\n  const resources = await client.listResources();\n  return resources.some((r) => r.uri === uri);\n}","typeGuard":"function hasResourceContents(\n  result: unknown\n): result is { contents: Record<string, unknown>[] } {\n  return (\n    typeof result === 'object' && result !== null &&\n    Array.isArray((result as { contents?: unknown }).contents) &&\n    ((result as { contents: unknown[] }).contents.length > 0)\n  );\n}","tryCatchPattern":"try {\n  const response = await readMcpAppResource(uri);\n} catch (error) {\n  if (/returned no contents/.test(String(error))) {\n    return { uri, mimeType: null, text: '', _meta: {} }; // or show 'resource unavailable'\n  }\n  throw error;\n}","preventionTips":["Check resources/list before deep-linking straight to a read.","Fix MCP servers to error explicitly on unknown URIs instead of returning empty contents.","Log the raw result shape when flattening fails to catch schema drift early."],"tags":["mcp","resources","validation","protocol"],"backgroundTag":null,"analyzedSha":"3810898a7447ec3299be72e223d3570a7aabf0ab","analyzedAt":"2026-08-16T10:14:26.282Z","schemaVersion":2},"datasetVersion":"2026-08-16T13:17:31.715Z"}