+
+
+
+
+
+## Start Using Prowler MCP
+
+- *"Show me all critical findings from my AWS accounts"*
+- *"What does the S3 bucket public access check do?"*
+- *"Onboard this new AWS account in my Prowler organization"*
+
+
+
+
+
+---
+
+# Claude Code in the Desktop App (Code Tab)
+
+The **Code** tab in the Claude desktop app runs the same Claude Code as the CLI, and reads the same `~/.claude.json`. There is no separate Prowler setup for it — you configure it **from a terminal** and the Code tab picks it up.
+
+
+
+
+Add the following, replacing the `command` path with the absolute path to the installed binary and the placeholder with your API key:
+
+```json
+{
+ "mcpServers": {
+ "prowler": {
+ "command": "/absolute/path/to/.local/share/prowler-mcp-bridge/node_modules/.bin/mcp-remote",
+ "args": [
+ "https://mcp.prowler.com/mcp",
+ "--header",
+ "Authorization: Bearer ${PROWLER_API_KEY}"
+ ],
+ "env": {
+ "PROWLER_API_KEY": "pk_your_api_key_here"
+ }
+ }
+ }
+}
+```
+
+
+
+
+## Troubleshooting
+
+### Server Does Not Appear After Editing the Config
+
+- Quit and reopen the app entirely — closing the window is not enough on macOS.
+- Confirm `claude_desktop_config.json` is valid JSON.
+- Confirm the `command` path points at a real executable. A wrong path surfaces as the server failing to start rather than as an auth error.
+
+### Tools Appear in Claude Code but Not in Chat
+
+Expected. The Chat tab does not read `~/.claude.json`, so servers added with `claude mcp add` never appear here. The Chat tab needs an entry in `claude_desktop_config.json`, which is what this guide sets up.
+
+### Authentication Fails With 401
+
+- Confirm the header value includes the `Bearer ` prefix.
+- Confirm the key has not been revoked in Prowler Cloud.
+
+### Checking the Logs
+
+- **macOS:** `~/Library/Logs/Claude/mcp*.log`
+- **Windows:** `%APPDATA%\Claude\logs\mcp*.log`
+
+```bash
+tail -f ~/Library/Logs/Claude/mcp*.log
+```
+
+## Next Steps
+
+
+
+
+
+
+
+From the CLI you can also inspect the stored entry directly:
+
+```bash
+codex mcp list # one row per server, with status and auth
+codex mcp get prowler # full entry, header values masked
+```
+
+
+
+
+## Troubleshooting
+
+### Startup Fails With HTTP 403 Forbidden
+
+Codex reports a handshake failure on startup, with an HTML error page rather than a JSON response:
+
+```
+⚠ MCP client for `prowler` failed to start: MCP startup failed: handshaking with MCP server
+ failed: ... unexpected server response: HTTP 403:
+
+
+
+
+
+
+
+## Step 4: Start Using Prowler MCP
+
+Open the chat panel and ask questions that use the Prowler tools:
+
+- *"Show me all critical findings from my AWS accounts"*
+- *"What does the S3 bucket public access check do?"*
+- *"Which of my providers failed the most CIS checks in the last scan?"*
+
+Cursor asks for approval before running an MCP tool the first time.
+
+
+
+
+
+You can toggle individual tools on or off from the tools list at the top of the chat panel, which is useful for keeping the active tool count down.
+
+## Troubleshooting
+
+### Server Does Not Connect
+
+- Check that `mcp.json` is valid JSON. A trailing comma or missing brace prevents the whole file from loading.
+- Open **MCP Logs** in the Output panel for the specific error.
+- Confirm the URL is exactly `https://mcp.prowler.com/mcp`.
+
+### Authentication Fails With 401
+
+- Verify the header value includes the `Bearer ` prefix: `"Bearer pk_..."`, not just the key.
+- Confirm the key has not been revoked in Prowler Cloud.
+- If using `${env:PROWLER_API_KEY}`, check the variable is set in the environment Cursor inherits. Restart Cursor after editing your shell profile — a value exported only in an already-open terminal will not reach the app.
+
+### The Entire `mcp.json` Is Ignored
+
+Remove any `"type": "streamable-http"` field. One such entry causes the Cursor CLI to drop every server in the file silently.
+
+### Some Prowler Tools Are Missing
+
+Cursor limits how many tools it exposes to the agent at once. With several MCP servers enabled you may exceed it, and some tools become unavailable. Disable servers you are not using, or turn off individual tools from the chat panel's tools list.
+
+## Next Steps
+
+
+
+
+
+
+
+
+Select the server to start, stop, or restart it, and to view its output log if the connection fails.
+
+## Step 4: Start Using Prowler MCP
+
+Open the Chat view and switch the mode selector to **Agent**. Click the tools icon to confirm the Prowler tools are available, then ask:
+
+- *"Show me all critical findings from my AWS accounts"*
+- *"What does the S3 bucket public access check do?"*
+- *"Summarize my CIS compliance status by provider"*
+
+
+
+
+
+Copilot asks for confirmation before running an MCP tool for the first time.
+
+## Troubleshooting
+
+### Server Does Not Appear
+
+- Confirm the root key is `servers`, not `mcpServers`.
+- Confirm each server entry has `"type": "http"`.
+- Check that `mcp.json` is valid JSON.
+- Verify your VS Code version is 1.102 or later.
+
+### Authentication Fails With 401
+
+- Verify the header value includes the `Bearer ` prefix.
+- Confirm the key has not been revoked in Prowler Cloud.
+- If you mistyped the key at the prompt, run **MCP: List Servers**, select `prowler`, and restart it to be prompted again.
+
+### Tools Do Not Appear in Chat
+
+- Make sure the Chat view is in **Agent** mode. MCP tools are not available in Ask mode.
+- Open the tools picker and confirm the Prowler tools are enabled.
+
+## Next Steps
+
++
Enter a valid Organizational Unit or Root ID above to enable deployment.
diff --git a/ui/components/providers/scan-config/manage-scan-config-modal.tsx b/ui/components/providers/scan-config/manage-scan-config-modal.tsx index d1d4d6471c..34da9cb056 100644 --- a/ui/components/providers/scan-config/manage-scan-config-modal.tsx +++ b/ui/components/providers/scan-config/manage-scan-config-modal.tsx @@ -3,8 +3,7 @@ import { useState } from "react"; import { setScanConfigurationProviders } from "@/actions/scan-configurations"; -import { Button } from "@/components/shadcn"; -import { useToast } from "@/components/shadcn"; +import { Button, useToast } from "@/components/shadcn"; import { CustomLink } from "@/components/shadcn/custom/custom-link"; import { Modal } from "@/components/shadcn/modal"; import { diff --git a/ui/components/providers/table/column-providers.tsx b/ui/components/providers/table/column-providers.tsx index 2590b5669e..49ea8c265f 100644 --- a/ui/components/providers/table/column-providers.tsx +++ b/ui/components/providers/table/column-providers.tsx @@ -31,6 +31,7 @@ import type { } from "@/types/schedules"; import { LinkToScans } from "../link-to-scans"; + import { DataTableRowActions } from "./data-table-row-actions"; interface GroupNameChipsProps { diff --git a/ui/components/providers/table/data-table-row-actions.test.tsx b/ui/components/providers/table/data-table-row-actions.test.tsx index 0a0a774b50..3166cca65d 100644 --- a/ui/components/providers/table/data-table-row-actions.test.tsx +++ b/ui/components/providers/table/data-table-row-actions.test.tsx @@ -393,7 +393,7 @@ describe("DataTableRowActions", () => { it("opens Edit Scan Schedule for Prowler Cloud subscribed provider rows", async () => { // Given - vi.stubEnv("NEXT_PUBLIC_IS_CLOUD_ENV", "true"); + vi.stubEnv("UI_CLOUD_ENABLED", "true"); const user = userEvent.setup(); render( @@ -420,7 +420,7 @@ describe("DataTableRowActions", () => { it("hides Edit Scan Schedule for manual-only Cloud provider rows", async () => { // Given - vi.stubEnv("NEXT_PUBLIC_IS_CLOUD_ENV", "true"); + vi.stubEnv("UI_CLOUD_ENABLED", "true"); const user = userEvent.setup(); render( @@ -445,7 +445,7 @@ describe("DataTableRowActions", () => { it("hides Edit Scan Schedule for blocked Cloud provider rows", async () => { // Given - vi.stubEnv("NEXT_PUBLIC_IS_CLOUD_ENV", "true"); + vi.stubEnv("UI_CLOUD_ENABLED", "true"); const user = userEvent.setup(); render( @@ -470,7 +470,7 @@ describe("DataTableRowActions", () => { it("opens scan config management with the precomputed current config id", async () => { // Given - vi.stubEnv("NEXT_PUBLIC_IS_CLOUD_ENV", "true"); + vi.stubEnv("UI_CLOUD_ENABLED", "true"); const user = userEvent.setup(); render( @@ -500,7 +500,7 @@ describe("DataTableRowActions", () => { it("shows scan config management as unavailable when scan configs failed to load", async () => { // Given - vi.stubEnv("NEXT_PUBLIC_IS_CLOUD_ENV", "true"); + vi.stubEnv("UI_CLOUD_ENABLED", "true"); const user = userEvent.setup(); render( @@ -528,7 +528,7 @@ describe("DataTableRowActions", () => { it("hides Edit Scan Configuration for dynamic providers in Prowler Cloud", async () => { // Given a dynamic provider in a Cloud tenant with scan configs available. - vi.stubEnv("NEXT_PUBLIC_IS_CLOUD_ENV", "true"); + vi.stubEnv("UI_CLOUD_ENABLED", "true"); const user = userEvent.setup(); render( diff --git a/ui/components/providers/wizard/hooks/use-provider-wizard-controller.test.tsx b/ui/components/providers/wizard/hooks/use-provider-wizard-controller.test.tsx index 0d0ce0b0a2..b12a541697 100644 --- a/ui/components/providers/wizard/hooks/use-provider-wizard-controller.test.tsx +++ b/ui/components/providers/wizard/hooks/use-provider-wizard-controller.test.tsx @@ -10,6 +10,7 @@ import { } from "@/types/provider-wizard"; import type { ProviderWizardInitialData } from "../types"; + import { useProviderWizardController } from "./use-provider-wizard-controller"; const { refreshMock, requestOpenOnWizardCloseMock } = vi.hoisted(() => ({ @@ -46,7 +47,7 @@ describe("useProviderWizardController", () => { sessionStorage.clear(); localStorage.clear(); // Checkpoint is Cloud-only. - vi.stubEnv("NEXT_PUBLIC_IS_CLOUD_ENV", "true"); + vi.stubEnv("UI_CLOUD_ENABLED", "true"); useProviderWizardStore.getState().reset(); useOrgSetupStore.getState().reset(); }); @@ -117,7 +118,7 @@ describe("useProviderWizardController", () => { }); it("does not request the onboarding checkpoint in self-hosted (OSS) deployments", () => { - vi.stubEnv("NEXT_PUBLIC_IS_CLOUD_ENV", "false"); + vi.stubEnv("UI_CLOUD_ENABLED", "false"); const onOpenChange = vi.fn(); const { result } = renderHook(() => useProviderWizardController({ diff --git a/ui/components/providers/wizard/steps/credentials-step.tsx b/ui/components/providers/wizard/steps/credentials-step.tsx index 48c6418d46..371f356d30 100644 --- a/ui/components/providers/wizard/steps/credentials-step.tsx +++ b/ui/components/providers/wizard/steps/credentials-step.tsx @@ -22,6 +22,7 @@ import { import { SelectViaGitHub } from "../../workflow/forms/select-credentials-type/github"; import { SelectViaM365 } from "../../workflow/forms/select-credentials-type/m365"; import { UpdateViaServiceAccountForm } from "../../workflow/forms/update-via-service-account-key-form"; + import { WIZARD_FOOTER_ACTION_TYPE, WizardFooterConfig, diff --git a/ui/components/providers/wizard/steps/launch-step.test.tsx b/ui/components/providers/wizard/steps/launch-step.test.tsx index dbc31771d9..11bd3bbba6 100644 --- a/ui/components/providers/wizard/steps/launch-step.test.tsx +++ b/ui/components/providers/wizard/steps/launch-step.test.tsx @@ -76,7 +76,7 @@ describe("LaunchStep", () => { describe("Prowler OSS (non-Cloud)", () => { beforeEach(() => { - vi.stubEnv("NEXT_PUBLIC_IS_CLOUD_ENV", "false"); + vi.stubEnv("UI_CLOUD_ENABLED", "false"); scanOnDemandMock.mockResolvedValue({ data: { id: "scan-1" } }); }); @@ -174,7 +174,7 @@ describe("LaunchStep", () => { describe("Prowler Cloud subscribed", () => { beforeEach(() => { - vi.stubEnv("NEXT_PUBLIC_IS_CLOUD_ENV", "true"); + vi.stubEnv("UI_CLOUD_ENABLED", "true"); updateScheduleMock.mockResolvedValue({ data: { id: "provider-1" } }); }); @@ -370,7 +370,7 @@ describe("LaunchStep", () => { describe("Prowler Cloud trial/onboarding (manual scan only)", () => { beforeEach(() => { - vi.stubEnv("NEXT_PUBLIC_IS_CLOUD_ENV", "true"); + vi.stubEnv("UI_CLOUD_ENABLED", "true"); scanOnDemandMock.mockResolvedValue({ data: { id: "scan-1" } }); }); diff --git a/ui/components/providers/wizard/steps/launch-step.tsx b/ui/components/providers/wizard/steps/launch-step.tsx index 1c24f2b893..0474821a71 100644 --- a/ui/components/providers/wizard/steps/launch-step.tsx +++ b/ui/components/providers/wizard/steps/launch-step.tsx @@ -11,8 +11,7 @@ import { saveScheduleWithInitialScan, } from "@/components/scans/schedule/save-schedule"; import { ScanScheduleFields } from "@/components/scans/schedule/scan-schedule-fields"; -import { Field, FieldLabel } from "@/components/shadcn"; -import { ToastAction, useToast } from "@/components/shadcn"; +import { Field, FieldLabel, ToastAction, useToast } from "@/components/shadcn"; import { Badge } from "@/components/shadcn/badge/badge"; import { EntityInfo } from "@/components/shadcn/entities"; import { diff --git a/ui/components/providers/wizard/steps/test-connection-step.tsx b/ui/components/providers/wizard/steps/test-connection-step.tsx index 0032771848..cc211c4f47 100644 --- a/ui/components/providers/wizard/steps/test-connection-step.tsx +++ b/ui/components/providers/wizard/steps/test-connection-step.tsx @@ -11,6 +11,7 @@ import { TestConnectionForm, TestConnectionProviderData, } from "../../workflow/forms/test-connection-form"; + import { WIZARD_FOOTER_ACTION_TYPE, WizardFooterConfig, diff --git a/ui/components/providers/workflow/forms/base-credentials-form.tsx b/ui/components/providers/workflow/forms/base-credentials-form.tsx index 8bedaa2514..b90c9095c4 100644 --- a/ui/components/providers/workflow/forms/base-credentials-form.tsx +++ b/ui/components/providers/workflow/forms/base-credentials-form.tsx @@ -37,6 +37,7 @@ import { } from "@/types"; import { ProviderTitleDocs } from "../provider-title-docs"; + import { AlibabaCloudRoleCredentialsForm, AlibabaCloudStaticCredentialsForm, diff --git a/ui/components/providers/workflow/forms/connect-account-form.tsx b/ui/components/providers/workflow/forms/connect-account-form.tsx index 303a5c918a..d8dd37edde 100644 --- a/ui/components/providers/workflow/forms/connect-account-form.tsx +++ b/ui/components/providers/workflow/forms/connect-account-form.tsx @@ -11,8 +11,7 @@ import { addProvider } from "@/actions/providers/providers"; import { AwsMethodSelector } from "@/components/providers/organizations/aws-method-selector"; import { WizardInputField } from "@/components/providers/workflow/forms/fields"; import { ProviderTitleDocs } from "@/components/providers/workflow/provider-title-docs"; -import { Button } from "@/components/shadcn"; -import { useToast } from "@/components/shadcn"; +import { Button, useToast } from "@/components/shadcn"; import { Form } from "@/components/shadcn/form"; import { addProviderFormSchema, diff --git a/ui/components/providers/workflow/forms/select-credentials-type/aws/credentials-type/aws-role-credentials-form.tsx b/ui/components/providers/workflow/forms/select-credentials-type/aws/credentials-type/aws-role-credentials-form.tsx index 44bfde7de4..e344b7e92b 100644 --- a/ui/components/providers/workflow/forms/select-credentials-type/aws/credentials-type/aws-role-credentials-form.tsx +++ b/ui/components/providers/workflow/forms/select-credentials-type/aws/credentials-type/aws-role-credentials-form.tsx @@ -14,6 +14,7 @@ import { } from "@/components/shadcn/select/select"; import { Separator } from "@/components/shadcn/separator/separator"; import { ProviderCredentialFields } from "@/lib/provider-credentials/provider-credential-fields"; +import { isCloud } from "@/lib/shared/env"; import { AWSCredentialsRole } from "@/types"; import { IntegrationType } from "@/types/integrations"; @@ -36,7 +37,7 @@ export const AWSRoleCredentialsForm = ({ type?: "providers" | "integrations"; integrationType?: IntegrationType; }) => { - const isCloudEnv = process.env.NEXT_PUBLIC_IS_CLOUD_ENV === "true"; + const isCloudEnv = isCloud(); const defaultCredentialsType = isCloudEnv ? "aws-sdk-default" : "access-secret-key"; diff --git a/ui/components/providers/workflow/forms/test-connection-form.tsx b/ui/components/providers/workflow/forms/test-connection-form.tsx index 503c74e1b0..687b036cce 100644 --- a/ui/components/providers/workflow/forms/test-connection-form.tsx +++ b/ui/components/providers/workflow/forms/test-connection-form.tsx @@ -14,8 +14,7 @@ import { CheckIcon } from "@/components/icons"; import { Button } from "@/components/shadcn"; import { Form } from "@/components/shadcn/form"; import { testProviderConnection } from "@/lib/provider-helpers"; -import { ProviderType } from "@/types"; -import { testConnectionFormSchema } from "@/types"; +import { ProviderType, testConnectionFormSchema } from "@/types"; import { ProviderConnectionInfo } from "./provider-connection-info"; diff --git a/ui/components/providers/workflow/forms/via-credentials/k8s-credentials-form.tsx b/ui/components/providers/workflow/forms/via-credentials/k8s-credentials-form.tsx index a0f687e037..c442b3fbab 100644 --- a/ui/components/providers/workflow/forms/via-credentials/k8s-credentials-form.tsx +++ b/ui/components/providers/workflow/forms/via-credentials/k8s-credentials-form.tsx @@ -1,7 +1,6 @@ "use client"; -import { Control } from "react-hook-form"; -import { useWatch } from "react-hook-form"; +import { Control, useWatch } from "react-hook-form"; import { WizardTextareaField } from "@/components/providers/workflow/forms/fields"; import { KubernetesCredentials } from "@/types"; diff --git a/ui/components/providers/workflow/provider-title-docs.tsx b/ui/components/providers/workflow/provider-title-docs.tsx index 3f06dd5f88..3556d96bc8 100644 --- a/ui/components/providers/workflow/provider-title-docs.tsx +++ b/ui/components/providers/workflow/provider-title-docs.tsx @@ -1,7 +1,9 @@ "use client"; -import { getProviderName } from "@/components/shadcn/entities/get-provider-logo"; -import { getProviderLogo } from "@/components/shadcn/entities/get-provider-logo"; +import { + getProviderName, + getProviderLogo, +} from "@/components/shadcn/entities/get-provider-logo"; import { ProviderType } from "@/types"; export const ProviderTitleDocs = ({ diff --git a/ui/components/resources/table/resource-detail-content.tsx b/ui/components/resources/table/resource-detail-content.tsx index fba3949bc8..3bb7566d11 100644 --- a/ui/components/resources/table/resource-detail-content.tsx +++ b/ui/components/resources/table/resource-detail-content.tsx @@ -18,8 +18,6 @@ import { Tooltip, TooltipContent, TooltipTrigger, -} from "@/components/shadcn"; -import { BreadcrumbNavigation, CustomBreadcrumbItem, } from "@/components/shadcn"; diff --git a/ui/components/roles/workflow/forms/add-role-form.test.tsx b/ui/components/roles/workflow/forms/add-role-form.test.tsx index e5e791fe07..e39683e7dc 100644 --- a/ui/components/roles/workflow/forms/add-role-form.test.tsx +++ b/ui/components/roles/workflow/forms/add-role-form.test.tsx @@ -96,7 +96,7 @@ describe("AddRoleForm", () => { it("shows Manage Alerts in Prowler Cloud", () => { // Given - vi.stubEnv("NEXT_PUBLIC_IS_CLOUD_ENV", "true"); + vi.stubEnv("UI_CLOUD_ENABLED", "true"); // When render(