different-ai/openwork · error

MCP tool policy response was incomplete.

Error message

MCP tool policy response was incomplete.

What it means

useUpdateMcpConnectionToolPolicy PUTs a new tool policy for a connection and expects the response body (a record) to carry payload.policy parseable by parseExternalMcpToolPolicy. If the body is not a record or the policy fails to parse, this error is thrown even though the HTTP status was ok — the client cannot confirm what policy is now in effect.

Source

Thrown at ee/apps/den-web/app/(den)/dashboard/_components/mcp-connections-data.tsx:339

export function useUpdateMcpConnectionToolPolicy(connectionId: string) {
  const queryClient = useQueryClient();
  const { orgId } = useOrgDashboard();
  return useMutation({
    mutationFn: async (input: Pick<ExternalMcpToolPolicyView, "allDisabled" | "disabledTools">): Promise<ExternalMcpToolPolicyView> => {
      const { response, payload } = await requestJson(
        `/v1/mcp-connections/${encodeURIComponent(connectionId)}/tool-policy`,
        {
          method: "PUT",
          headers: getOrgScopeHeaders(requireOrgId(orgId)),
          body: JSON.stringify(input),
        },
        30000,
      );
      if (!response.ok) {
        throw getRequestError(payload, response, `Failed to update MCP tool policy (${response.status}).`);
      }
      const policy = isRecord(payload) ? parseExternalMcpToolPolicy(payload.policy) : null;
      if (!policy) throw new Error("MCP tool policy response was incomplete.");
      return policy;
    },
    onSuccess: () => queryClient.invalidateQueries({ queryKey: mcpConnectionQueryKeys.tools(orgId, connectionId) }),
  });
}

// The den-api tool run is bounded by its 150s MCP tool lifecycle deadline;
// give the request a little headroom so the server's structured failure
// arrives instead of a client-side timeout.
const RUN_TOOL_REQUEST_TIMEOUT_MS = 160000;

export function useRunMcpConnectionTool(connectionId: string) {
  const { orgId } = useOrgDashboard();
  return useMutation({
    mutationFn: async (input: { toolName: string; arguments: Record<string, unknown> }): Promise<ExternalMcpToolRun> => {
      const { response, payload } = await requestJson(
        `/v1/mcp-connections/${encodeURIComponent(connectionId)}/tools/call`,
        {

View on GitHub (pinned to 2b7df46e8a)

Solutions

  1. Inspect the actual update response body and confirm it contains a parseable 'policy' object
  2. Fix the server route to echo the saved policy under payload.policy
  3. Extend parseExternalMcpToolPolicy to accept the current server policy shape
  4. Invalidate and refetch the tools query to read policy from the GET endpoint if the echo cannot be fixed

Example fix

// before
const policy = isRecord(payload) ? parseExternalMcpToolPolicy(payload.policy) : null;
if (!policy) throw new Error("MCP tool policy response was incomplete.");
// after
const rawPolicy = isRecord(payload) && isRecord(payload.policy) ? payload.policy : isRecord(payload) ? payload : null;
const policy = rawPolicy ? parseExternalMcpToolPolicy(rawPolicy) : null;
if (!policy) throw new Error("MCP tool policy response was incomplete.");
Defensive patterns

Strategy: type-guard

Validate before calling

if (!isRecord(payload) || parseExternalMcpToolPolicy(payload.policy) === null) {
  // fall back to refetching policy via the tools query instead of failing
}

Type guard

function hasPolicy(v: unknown): v is { policy: Record<string, unknown> } {
  return isRecord(v) && isRecord(v.policy);
}

Try / catch

try {
  await updatePolicy(connectionId, policy);
} catch (err) {
  if (err.message === "MCP tool policy response was incomplete.") {
    await queryClient.invalidateQueries(mcpConnectionQueryKeys.tools(orgId, connectionId)); // confirm via GET
  }
}

Prevention

When it happens

Trigger: Policy update returns 200 with empty body; response nests policy differently (e.g. {data:{policy}}); parseExternalMcpToolPolicy rejects the new policy object because required fields are missing or of wrong type.

Common situations: Server version without the policy echo in the update response; admin UI and API disagree on policy schema after a policy-format migration; proxy stripping response bodies.

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


AI-assisted analysis of different-ai/openwork@2b7df46e8a (2026-09-01). Data as JSON: /api/errors/8ab1a0ad1a5cd8af. Report an issue: GitHub.