different-ai/openwork · error
MCP tool result was incomplete.
Error message
MCP tool result was incomplete.
What it means
After a successful (ok) run-tool response, useRunMcpConnectionTool validates that the payload is a record containing string referenceId, numeric durationMs, and a 'result' key. If any of these is missing/mistyped it throws 'MCP tool result was incomplete.' Notably, a missing/unparseable inspection is tolerated, but the core result envelope is mandatory — without referenceId the run cannot be traced or audited.
Source
Thrown at ee/apps/den-web/app/(den)/dashboard/_components/mcp-connections-data.tsx:385
typeof payload.message === "string" ? payload.message : "This tool is disabled by organization policy.",
typeof payload.disabledBy === "string" ? payload.disabledBy : null,
typeof payload.disabledAt === "string" ? payload.disabledAt : null,
);
}
const requestError = getRequestError(payload, response, `Failed to run MCP tool (${response.status}).`);
throw new ExternalMcpToolRunError(
requestError.message,
isRecord(payload) ? parseToolCallInspection(payload.inspection) : null,
isRecord(payload) ? parseExternalMcpDiagnostic(payload.diagnostic) : null,
);
}
if (
!isRecord(payload)
|| typeof payload.referenceId !== "string"
|| typeof payload.durationMs !== "number"
|| !("result" in payload)
) {
throw new Error("MCP tool result was incomplete.");
}
return {
referenceId: payload.referenceId,
durationMs: payload.durationMs,
result: payload.result,
// A missing or unparseable inspection must not fail a tool run that
// succeeded (for example across a den-api/den-web deploy skew); the
// runner simply renders without the inspector panel.
inspection: parseToolCallInspection(payload.inspection),
};
},
});
}
function isRecord(value: unknown): value is Record<string, unknown> {
return typeof value === "object" && value !== null;
}
View on GitHub (pinned to 2b7df46e8a)
Solutions
- Log the raw response body and compare against the expected { referenceId: string, durationMs: number, result: unknown } envelope
- Fix the server route to always return referenceId, durationMs, and result on success
- If durationMs is sent as a string, normalize it server-side (or coerce client-side)
- Ensure no proxy/middleware truncates or reshapes large JSON responses
Example fix
// before
if (!isRecord(payload) || typeof payload.referenceId !== "string" || typeof payload.durationMs !== "number" || !("result" in payload)) {
throw new Error("MCP tool result was incomplete.");
}
// after
const durationMs = typeof payload?.durationMs === "string" ? Number(payload.durationMs) : payload?.durationMs;
if (!isRecord(payload) || typeof payload.referenceId !== "string" || typeof durationMs !== "number" || Number.isNaN(durationMs) || !("result" in payload)) {
throw new Error("MCP tool result was incomplete.");
} Defensive patterns
Strategy: validation
Validate before calling
function isCompleteToolResult(payload: unknown): payload is { referenceId: string; durationMs: number; result: unknown } {
return isRecord(payload) && typeof payload.referenceId === "string" && typeof payload.durationMs === "number" && "result" in payload;
} Type guard
function isToolResultEnvelope(v: unknown): v is { referenceId: string; durationMs: number; result: unknown } {
return isRecord(v) && typeof v.referenceId === "string" && typeof v.durationMs === "number" && "result" in v;
} Try / catch
try {
const run = await runTool({ tool, input });
} catch (err) {
if (err.message === "MCP tool result was incomplete.") {
// keep referenceId-less runs out of history; offer re-run
}
} Prevention
- Contract-test the run-tool success envelope {referenceId,durationMs,result}
- Always send durationMs as a number from the server
- Treat inspection as optional but the envelope as mandatory in server code
When it happens
Trigger: Run returns 200 with an empty or differently-shaped body (e.g. {output:...} instead of {referenceId,durationMs,result}); durationMs serialized as a string; server bug omitting result on partial failure while still returning ok.
Common situations: den-api version predating the result-envelope contract; gateway rewriting the JSON; tool returning a huge result that got truncated downstream; server returning {ok:true} acknowledgement instead of the full result for async tools.
Understand the failure class
Background: "invalid response format", "malformed payload", "missing data field": when an API returns 200 but the response shape is wrong — this error's family across 23 libraries.
Related errors
- MCP tool catalog response was incomplete.
- MCP tool policy response was incomplete.
- Create MCP connection response was incomplete.
- invalid_mcp_token_payload
- OpenWork Connect MCP metadata is invalid.
AI-assisted analysis of different-ai/openwork@2b7df46e8a (2026-09-01).
Data as JSON: /api/errors/4fe310b738b7ccac.
Report an issue: GitHub.