different-ai/openwork · error

MCP_PROVIDER_HTTP_403

MCP_PROVIDER_HTTP_403

Error message

MCP_PROVIDER_HTTP_403

What it means

MCP_PROVIDER_HTTP_403 is raised when an MCP tool discovery or tool execution request receives HTTP 403 from the provider. It is classified as PROVIDER_AUTHORIZATION / provider_policy_denied and is non-retryable with the provider admin as owner: authentication passed far enough to reach an authorization check, but the provider's role, ACL, or application permission policy denies this specific operation.

Solutions

  1. Grant the provider role, ACL, or application permission required for this specific discovery/execution operation.
  2. Confirm which identity the gateway presents to the provider and check its permissions in the provider's admin console.
  3. Re-run the operation after the grant is applied; retries without a permission change will keep failing with 403.

Example fix

// before: token scoped to viewer role calling a mutating tool
await client.callTool({ name: 'delete-record', arguments })
// after: provider admin grants editor role / tool permission, then it succeeds
await client.callTool({ name: 'delete-record', arguments })
Defensive patterns

Strategy: try-catch

Validate before calling

// fail fast: check the identity's granted scopes against the tool's requirement before executing
if (!grantedScovers.includes(tool.requiredPermission)) {
  throw new Error(`missing provider permission ${tool.requiredPermission}`)
}

Type guard

function isProviderAuthzDenied(d: { code: string }): boolean {
  return d.code === 'MCP_PROVIDER_HTTP_403'
}

Try / catch

try {
  return await client.callTool(req)
} catch (e) {
  if (isProviderAuthzDenied(e.diagnostic)) {
    // non-retryable: escalate to provider admin for role/ACL grant
    escalateToProviderAdmin({ operation: req.name, diagnostic: e.diagnostic })
  }
  throw e
}

Prevention

When it happens

Trigger: Calling tools/list (MCP_TOOL_DISCOVERY) or a tool invocation (MCP_TOOL_EXECUTION) against a provider that returns 403 — e.g. the provider token authenticates but the account lacks the role/permission for that tool, or the app is not allow-listed for that operation.

Common situations: Provider API key issued for a read-only role used for a write tool; provider-side ACL changes removing a service account's access; a new tool added on the provider that the existing integration app is not permissioned for; expired or rotated grants on the provider tenant.

Related errors


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

Appendix: source

Thrown at ee/apps/den-api/src/capability-sources/external-mcp-diagnostics.ts:965

    // enterprise providers: only a Bearer/insufficient_scope challenge means
    // token validation; an ordinary tools/* 403 is usually an ACL/role denial.
    if (status === 401 || input.bearerChallenge || input.insufficientScope) {
      return {
        phase: "AUTH_RESOURCE_VALIDATION",
        category: input.insufficientScope ? "oauth_insufficient_scope" : "oauth_resource_rejected",
        code: input.insufficientScope ? "MCP_OAUTH_INSUFFICIENT_SCOPE" : `MCP_OAUTH_HTTP_${status}`,
        retryable: status === 401,
        actionOwner: input.insufficientScope ? "organization_admin" : "member",
        operatorAction: input.insufficientScope
          ? "Grant the provider scopes required by this MCP resource and reconnect."
          : "Reconnect the provider account and verify token audience, tenant, and resource binding.",
      }
    }
    if (status === 403 && (phase === "MCP_TOOL_DISCOVERY" || phase === "MCP_TOOL_EXECUTION")) {
      return {
        phase: "PROVIDER_AUTHORIZATION",
        category: "provider_policy_denied",
        code: "MCP_PROVIDER_HTTP_403",
        retryable: false,
        actionOwner: "provider_admin",
        operatorAction: "Grant the provider role, ACL, or application permission required for this operation.",
      }
    }
  }
  if (status >= 400) {
    return {
      phase,
      category: "http_failure",
      code: `MCP_HTTP_${status}`,
      retryable: status === 408,
      actionOwner: "provider_admin",
      operatorAction: "Inspect provider and proxy logs for the failing HTTP request using the diagnostic reference.",
    }
  }
  if (phase.startsWith("MCP_") && input.contentType === "text/html") {
    return {

View on GitHub (pinned to 2b7df46e8a)