{"record":{"id":"8ab1a0ad1a5cd8af","repo":"different-ai/openwork","slug":"mcp-tool-policy-response-was-incomplete","errorCode":null,"errorMessage":"MCP tool policy response was incomplete.","messagePattern":"MCP tool policy response was incomplete\\.","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"ee/apps/den-web/app/(den)/dashboard/_components/mcp-connections-data.tsx","lineNumber":339,"sourceCode":"export function useUpdateMcpConnectionToolPolicy(connectionId: string) {\n  const queryClient = useQueryClient();\n  const { orgId } = useOrgDashboard();\n  return useMutation({\n    mutationFn: async (input: Pick<ExternalMcpToolPolicyView, \"allDisabled\" | \"disabledTools\">): Promise<ExternalMcpToolPolicyView> => {\n      const { response, payload } = await requestJson(\n        `/v1/mcp-connections/${encodeURIComponent(connectionId)}/tool-policy`,\n        {\n          method: \"PUT\",\n          headers: getOrgScopeHeaders(requireOrgId(orgId)),\n          body: JSON.stringify(input),\n        },\n        30000,\n      );\n      if (!response.ok) {\n        throw getRequestError(payload, response, `Failed to update MCP tool policy (${response.status}).`);\n      }\n      const policy = isRecord(payload) ? parseExternalMcpToolPolicy(payload.policy) : null;\n      if (!policy) throw new Error(\"MCP tool policy response was incomplete.\");\n      return policy;\n    },\n    onSuccess: () => queryClient.invalidateQueries({ queryKey: mcpConnectionQueryKeys.tools(orgId, connectionId) }),\n  });\n}\n\n// The den-api tool run is bounded by its 150s MCP tool lifecycle deadline;\n// give the request a little headroom so the server's structured failure\n// arrives instead of a client-side timeout.\nconst RUN_TOOL_REQUEST_TIMEOUT_MS = 160000;\n\nexport function useRunMcpConnectionTool(connectionId: string) {\n  const { orgId } = useOrgDashboard();\n  return useMutation({\n    mutationFn: async (input: { toolName: string; arguments: Record<string, unknown> }): Promise<ExternalMcpToolRun> => {\n      const { response, payload } = await requestJson(\n        `/v1/mcp-connections/${encodeURIComponent(connectionId)}/tools/call`,\n        {","sourceCodeStart":321,"sourceCodeEnd":357,"githubUrl":"https://github.com/different-ai/openwork/blob/2b7df46e8ae1517d64c896c7793d2d52ec845669/ee/apps/den-web/app/(den)/dashboard/_components/mcp-connections-data.tsx#L321-L357","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Inspect the actual update response body and confirm it contains a parseable 'policy' object","Fix the server route to echo the saved policy under payload.policy","Extend parseExternalMcpToolPolicy to accept the current server policy shape","Invalidate and refetch the tools query to read policy from the GET endpoint if the echo cannot be fixed"],"exampleFix":"// before\nconst policy = isRecord(payload) ? parseExternalMcpToolPolicy(payload.policy) : null;\nif (!policy) throw new Error(\"MCP tool policy response was incomplete.\");\n// after\nconst rawPolicy = isRecord(payload) && isRecord(payload.policy) ? payload.policy : isRecord(payload) ? payload : null;\nconst policy = rawPolicy ? parseExternalMcpToolPolicy(rawPolicy) : null;\nif (!policy) throw new Error(\"MCP tool policy response was incomplete.\");","handlingStrategy":"type-guard","validationCode":"if (!isRecord(payload) || parseExternalMcpToolPolicy(payload.policy) === null) {\n  // fall back to refetching policy via the tools query instead of failing\n}","typeGuard":"function hasPolicy(v: unknown): v is { policy: Record<string, unknown> } {\n  return isRecord(v) && isRecord(v.policy);\n}","tryCatchPattern":"try {\n  await updatePolicy(connectionId, policy);\n} catch (err) {\n  if (err.message === \"MCP tool policy response was incomplete.\") {\n    await queryClient.invalidateQueries(mcpConnectionQueryKeys.tools(orgId, connectionId)); // confirm via GET\n  }\n}","preventionTips":["Server should echo the saved policy in the update response","Test the policy PUT contract whenever the policy schema changes","Fall back to refetching state rather than treating an unverifiable update as failed"],"tags":["api","response-shape","mcp","policy"],"backgroundTag":"unexpected-api-response-shape","analyzedSha":"2b7df46e8ae1517d64c896c7793d2d52ec845669","analyzedAt":"2026-09-01T07:59:23.713Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}