#
tokens: 49779/50000 60/111 files (page 1/5)
lines: on (toggle) GitHub
raw markdown copy reset
This is page 1 of 5. Use http://codebase.md/crowdstrike/falcon-mcp?lines=true&page={x} to view the full context.

# Directory Structure

```
├── .dockerignore
├── .env.dev.example
├── .env.example
├── .github
│   ├── dependabot.yml
│   ├── ISSUE_TEMPLATE
│   │   ├── bug.yaml
│   │   ├── config.yml
│   │   ├── feature-request.yaml
│   │   └── question.yaml
│   └── workflows
│       ├── docker-build-push.yml
│       ├── docker-build-test.yml
│       ├── markdown-lint.yml
│       ├── python-lint.yml
│       ├── python-test-e2e.yml
│       ├── python-test.yml
│       └── release.yml
├── .gitignore
├── .markdownlint.json
├── CHANGELOG.md
├── Dockerfile
├── docs
│   ├── CODE_OF_CONDUCT.md
│   ├── CONTRIBUTING.md
│   ├── deployment
│   │   ├── amazon_bedrock_agentcore.md
│   │   └── google_cloud.md
│   ├── e2e_testing.md
│   ├── module_development.md
│   ├── resource_development.md
│   └── SECURITY.md
├── examples
│   ├── adk
│   │   ├── adk_agent_operations.sh
│   │   ├── falcon_agent
│   │   │   ├── __init__.py
│   │   │   ├── agent.py
│   │   │   ├── env.properties
│   │   │   └── requirements.txt
│   │   └── README.md
│   ├── basic_usage.py
│   ├── mcp_config.json
│   ├── sse_usage.py
│   └── streamable_http_usage.py
├── falcon_mcp
│   ├── __init__.py
│   ├── client.py
│   ├── common
│   │   ├── __init__.py
│   │   ├── api_scopes.py
│   │   ├── errors.py
│   │   ├── logging.py
│   │   └── utils.py
│   ├── modules
│   │   ├── __init__.py
│   │   ├── base.py
│   │   ├── cloud.py
│   │   ├── detections.py
│   │   ├── discover.py
│   │   ├── hosts.py
│   │   ├── idp.py
│   │   ├── incidents.py
│   │   ├── intel.py
│   │   ├── sensor_usage.py
│   │   ├── serverless.py
│   │   └── spotlight.py
│   ├── registry.py
│   ├── resources
│   │   ├── __init__.py
│   │   ├── cloud.py
│   │   ├── detections.py
│   │   ├── discover.py
│   │   ├── hosts.py
│   │   ├── incidents.py
│   │   ├── intel.py
│   │   ├── sensor_usage.py
│   │   ├── serverless.py
│   │   └── spotlight.py
│   └── server.py
├── LICENSE
├── pyproject.toml
├── README.md
├── scripts
│   ├── generate_e2e_report.py
│   └── test_results_viewer.html
├── SUPPORT.md
├── tests
│   ├── __init__.py
│   ├── common
│   │   ├── __init__.py
│   │   ├── test_api_scopes.py
│   │   ├── test_errors.py
│   │   ├── test_logging.py
│   │   └── test_utils.py
│   ├── conftest.py
│   ├── e2e
│   │   ├── __init__.py
│   │   ├── modules
│   │   │   ├── __init__.py
│   │   │   ├── test_cloud.py
│   │   │   ├── test_detections.py
│   │   │   ├── test_discover.py
│   │   │   ├── test_hosts.py
│   │   │   ├── test_idp.py
│   │   │   ├── test_incidents.py
│   │   │   ├── test_intel.py
│   │   │   ├── test_sensor_usage.py
│   │   │   ├── test_serverless.py
│   │   │   └── test_spotlight.py
│   │   └── utils
│   │       ├── __init__.py
│   │       └── base_e2e_test.py
│   ├── modules
│   │   ├── __init__.py
│   │   ├── test_base.py
│   │   ├── test_cloud.py
│   │   ├── test_detections.py
│   │   ├── test_discover.py
│   │   ├── test_hosts.py
│   │   ├── test_idp.py
│   │   ├── test_incidents.py
│   │   ├── test_intel.py
│   │   ├── test_sensor_usage.py
│   │   ├── test_serverless.py
│   │   ├── test_spotlight.py
│   │   └── utils
│   │       └── test_modules.py
│   ├── test_client.py
│   ├── test_registry.py
│   ├── test_server.py
│   └── test_streamable_http_transport.py
└── uv.lock
```

# Files

--------------------------------------------------------------------------------
/.markdownlint.json:
--------------------------------------------------------------------------------

```json
1 | {
2 |   "default": true,
3 |   "MD013": false,
4 |   "MD024": false,
5 |   "MD033": false,
6 |   "MD041": false
7 | }
8 | 
```

--------------------------------------------------------------------------------
/.dockerignore:
--------------------------------------------------------------------------------

```
 1 | # Git files
 2 | .git
 3 | .gitignore
 4 | .gitattributes
 5 | 
 6 | # Documentation
 7 | *.md
 8 | docs/
 9 | README.md
10 | 
11 | # Python cache and bytecode
12 | __pycache__/
13 | *.py[cod]
14 | *$py.class
15 | *.so
16 | .Python
17 | build/
18 | develop-eggs/
19 | dist/
20 | downloads/
21 | eggs/
22 | .eggs/
23 | lib/
24 | lib64/
25 | parts/
26 | sdist/
27 | var/
28 | wheels/
29 | *.egg-info/
30 | .installed.cfg
31 | *.egg
32 | 
33 | # Virtual environments
34 | .env
35 | .venv/
36 | venv/
37 | ENV/
38 | env/
39 | 
40 | # IDE and editor files
41 | .vscode/
42 | .idea/
43 | *.swp
44 | *.swo
45 | *~
46 | .DS_Store
47 | 
48 | # Testing
49 | .pytest_cache/
50 | .coverage
51 | .tox/
52 | htmlcov/
53 | .cache
54 | 
55 | # CI/CD
56 | .github/
57 | .gitlab-ci.yml
58 | .travis.yml
59 | azure-pipelines.yml
60 | 
61 | # Docker files (except what's needed)
62 | Dockerfile*
63 | docker-compose*
64 | .dockerignore
65 | 
66 | # Logs
67 | *.log
68 | logs/
69 | 
70 | # Temporary files
71 | *.tmp
72 | *.temp
73 | .tmp/
74 | 
75 | # OS files
76 | Thumbs.db
77 | ehthumbs.db
78 | 
79 | # Security sensitive files
80 | *.pem
81 | *.key
82 | *.p12
83 | *.pfx
84 | .env.local
85 | .env.production
86 | secrets/
```

--------------------------------------------------------------------------------
/.env.example:
--------------------------------------------------------------------------------

```
 1 | # =============================================================================
 2 | # CrowdStrike Falcon API Credentials
 3 | # =============================================================================
 4 | # Required: Get these from your CrowdStrike console (Support > API Clients and Keys)
 5 | FALCON_CLIENT_ID=your-client-id
 6 | FALCON_CLIENT_SECRET=your-client-secret
 7 | 
 8 | # =============================================================================
 9 | # CrowdStrike Falcon API Base URL
10 | # =============================================================================
11 | # Required: Choose the correct region for your CrowdStrike instance
12 | # US-1 (Default):     https://api.crowdstrike.com
13 | # US-2:               https://api.us-2.crowdstrike.com
14 | # EU-1:               https://api.eu-1.crowdstrike.com
15 | # US-GOV:             https://api.laggar.gcw.crowdstrike.com
16 | FALCON_BASE_URL=https://api.us-2.crowdstrike.com
17 | 
18 | # =============================================================================
19 | # Optional: Server Configuration
20 | # =============================================================================
21 | # Modules to enable (comma-separated list)
22 | # Options: detections,incidents,intel,hosts,spotlight,cloud,idp,sensorusage
23 | # Default: all modules enabled if not specified
24 | #FALCON_MCP_MODULES=detections,incidents,intel,hosts,spotlight,cloud,idp,sensorusage,serverless,discover
25 | 
26 | # Transport method to use
27 | # Options: stdio, sse, streamable-http
28 | # Default: stdio
29 | #FALCON_MCP_TRANSPORT=stdio
30 | 
31 | # Enable debug logging
32 | # Options: true, false
33 | # Default: false
34 | #FALCON_MCP_DEBUG=false
35 | 
36 | # Host for HTTP transports (sse, streamable-http)
37 | # Default: 127.0.0.1
38 | #FALCON_MCP_HOST=127.0.0.1
39 | 
40 | # Port for HTTP transports (sse, streamable-http)
41 | # Default: 8000
42 | #FALCON_MCP_PORT=8000
43 | 
44 | # User agent comment to include in API requests
45 | # This will be added to the User-Agent header comment section
46 | # Example: CustomApp/1.0
47 | #FALCON_MCP_USER_AGENT_COMMENT=
48 | 
```

--------------------------------------------------------------------------------
/.env.dev.example:
--------------------------------------------------------------------------------

```
 1 | # =============================================================================
 2 | # CrowdStrike Falcon API Credentials
 3 | # =============================================================================
 4 | # Required: Get these from your CrowdStrike console (Support > API Clients and Keys)
 5 | FALCON_CLIENT_ID=your-client-id
 6 | FALCON_CLIENT_SECRET=your-client-secret
 7 | 
 8 | # =============================================================================
 9 | # CrowdStrike Falcon API Base URL
10 | # =============================================================================
11 | # Required: Choose the correct region for your CrowdStrike instance
12 | # US-1 (Default):     https://api.crowdstrike.com
13 | # US-2:               https://api.us-2.crowdstrike.com
14 | # EU-1:               https://api.eu-1.crowdstrike.com
15 | # US-GOV:             https://api.laggar.gcw.crowdstrike.com
16 | FALCON_BASE_URL=https://api.us-2.crowdstrike.com
17 | 
18 | # =============================================================================
19 | # Optional: Server Configuration
20 | # =============================================================================
21 | # Modules to enable (comma-separated list)
22 | # Options: detections,incidents,intel,hosts,spotlight,cloud,idp,sensorusage
23 | # Default: all modules enabled if not specified
24 | #FALCON_MCP_MODULES=detections,incidents,intel,hosts,spotlight,cloud,idp,sensorusage,serverless,discover
25 | 
26 | # Transport method to use
27 | # Options: stdio, sse, streamable-http
28 | # Default: stdio
29 | #FALCON_MCP_TRANSPORT=stdio
30 | 
31 | # Enable debug logging
32 | # Options: true, false
33 | # Default: false
34 | #FALCON_MCP_DEBUG=false
35 | 
36 | # Host for HTTP transports (sse, streamable-http)
37 | # Default: 127.0.0.1
38 | #FALCON_MCP_HOST=127.0.0.1
39 | 
40 | # Port for HTTP transports (sse, streamable-http)
41 | # Default: 8000
42 | #FALCON_MCP_PORT=8000
43 | 
44 | # User agent comment to include in API requests
45 | # This will be added to the User-Agent header comment section
46 | # Example: CustomApp/1.0
47 | #FALCON_MCP_USER_AGENT_COMMENT=
48 | 
49 | # =============================================================================
50 | # Development & E2E Testing Configuration
51 | # =============================================================================
52 | # Only needed if you plan to run end-to-end tests or contribute to development
53 | 
54 | # OpenAI API key for E2E testing (required for running E2E tests)
55 | #OPENAI_API_KEY=your-openai-api-key
56 | 
57 | # Custom LLM API endpoint for testing (optional)
58 | #OPENAI_BASE_URL=https://your-custom-llm-endpoint.com/v1
59 | 
60 | # Comma-separated list of models to test (optional)
61 | #MODELS_TO_TEST=example-model-1,example-model-2
62 | 
63 | # Number of test runs per test case (optional)
64 | #RUNS_PER_TEST=2
65 | 
66 | # Success threshold for E2E tests (optional)
67 | #SUCCESS_TRESHOLD=0.7
68 | 
69 | MCP_USE_ANONYMIZED_TELEMETRY=false
70 | 
```

--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------

```
  1 | # Byte-compiled / optimized / DLL files
  2 | __pycache__/
  3 | *.py[cod]
  4 | *$py.class
  5 | 
  6 | # C extensions
  7 | *.so
  8 | 
  9 | # Distribution / packaging
 10 | .Python
 11 | build/
 12 | develop-eggs/
 13 | dist/
 14 | downloads/
 15 | eggs/
 16 | .eggs/
 17 | lib/
 18 | lib64/
 19 | parts/
 20 | sdist/
 21 | var/
 22 | wheels/
 23 | share/python-wheels/
 24 | *.egg-info/
 25 | .installed.cfg
 26 | *.egg
 27 | MANIFEST
 28 | 
 29 | # PyInstaller
 30 | #  Usually these files are written by a python script from a template
 31 | #  before PyInstaller builds the exe, so as to inject date/other infos into it.
 32 | *.manifest
 33 | *.spec
 34 | 
 35 | # Installer logs
 36 | pip-log.txt
 37 | pip-delete-this-directory.txt
 38 | 
 39 | # Unit test / coverage reports
 40 | htmlcov/
 41 | .tox/
 42 | .nox/
 43 | .coverage
 44 | .coverage.*
 45 | .cache
 46 | nosetests.xml
 47 | coverage.xml
 48 | *.cover
 49 | *.py,cover
 50 | .hypothesis/
 51 | .pytest_cache/
 52 | cover/
 53 | 
 54 | # Translations
 55 | *.mo
 56 | *.pot
 57 | 
 58 | # Django stuff:
 59 | *.log
 60 | local_settings.py
 61 | db.sqlite3
 62 | db.sqlite3-journal
 63 | 
 64 | # Flask stuff:
 65 | instance/
 66 | .webassets-cache
 67 | 
 68 | # Scrapy stuff:
 69 | .scrapy
 70 | 
 71 | # Sphinx documentation
 72 | docs/_build/
 73 | 
 74 | # PyBuilder
 75 | .pybuilder/
 76 | target/
 77 | 
 78 | # Jupyter Notebook
 79 | .ipynb_checkpoints
 80 | 
 81 | # IPython
 82 | profile_default/
 83 | ipython_config.py
 84 | 
 85 | # pyenv
 86 | #   For a library or package, you might want to ignore these files since the code is
 87 | #   intended to run in multiple environments; otherwise, check them in:
 88 | # .python-version
 89 | 
 90 | # pipenv
 91 | #   According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
 92 | #   However, in case of collaboration, if having platform-specific dependencies or dependencies
 93 | #   having no cross-platform support, pipenv may install dependencies that don't work, or not
 94 | #   install all needed dependencies.
 95 | #Pipfile.lock
 96 | 
 97 | # UV
 98 | #   Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
 99 | #   This is especially recommended for binary packages to ensure reproducibility, and is more
100 | #   commonly ignored for libraries.
101 | #uv.lock
102 | 
103 | # poetry
104 | #   Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
105 | #   This is especially recommended for binary packages to ensure reproducibility, and is more
106 | #   commonly ignored for libraries.
107 | #   https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
108 | #poetry.lock
109 | 
110 | # pdm
111 | #   Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
112 | #pdm.lock
113 | #   pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
114 | #   in version control.
115 | #   https://pdm.fming.dev/latest/usage/project/#working-with-version-control
116 | .pdm.toml
117 | .pdm-python
118 | .pdm-build/
119 | 
120 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
121 | __pypackages__/
122 | 
123 | # Celery stuff
124 | celerybeat-schedule
125 | celerybeat.pid
126 | 
127 | # SageMath parsed files
128 | *.sage.py
129 | 
130 | # Environments
131 | .env
132 | .venv
133 | env/
134 | venv/
135 | ENV/
136 | env.bak/
137 | venv.bak/
138 | 
139 | # Spyder project settings
140 | .spyderproject
141 | .spyproject
142 | 
143 | # Rope project settings
144 | .ropeproject
145 | 
146 | # mkdocs documentation
147 | /site
148 | 
149 | # mypy
150 | .mypy_cache/
151 | .dmypy.json
152 | dmypy.json
153 | 
154 | # Pyre type checker
155 | .pyre/
156 | 
157 | # pytype static type analyzer
158 | .pytype/
159 | 
160 | # Cython debug symbols
161 | cython_debug/
162 | 
163 | # PyCharm
164 | #  JetBrains specific template is maintained in a separate JetBrains.gitignore that can
165 | #  be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
166 | #  and can be added to the global gitignore or merged into this file.  For a more nuclear
167 | #  option (not recommended) you can uncomment the following to ignore the entire idea folder.
168 | #.idea/
169 | 
170 | # Abstra
171 | # Abstra is an AI-powered process automation framework.
172 | # Ignore directories containing user credentials, local state, and settings.
173 | # Learn more at https://abstra.io/docs
174 | .abstra/
175 | 
176 | # Visual Studio Code
177 | #  Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
178 | #  that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
179 | #  and can be added to the global gitignore or merged into this file. However, if you prefer,
180 | #  you could uncomment the following to ignore the enitre vscode folder
181 | .vscode/
182 | 
183 | # Ruff stuff:
184 | .ruff_cache/
185 | 
186 | # PyPI configuration file
187 | .pypirc
188 | 
189 | # AI Assistants
190 | #  Various AI coding assistants create local cache, settings, and conversation history
191 | #  These contain user-specific data and should not be committed to version control
192 | .cursorignore
193 | .cursorindexingignore
194 | .claude/
195 | CLAUDE.md
196 | .anthropic/
197 | .openai/
198 | .codeium/
199 | .tabnine/
200 | .github-copilot/
201 | .roo/
202 | .aider/
203 | .aider*
204 | .clinerules/
205 | memory-bank/
206 | 
207 | # E2E artifacts
208 | static_test_report.html
209 | test_results.json
210 | 
```

--------------------------------------------------------------------------------
/examples/adk/README.md:
--------------------------------------------------------------------------------

```markdown
  1 | # Running/Deploying with a prebuilt agent
  2 | 
  3 | This repository includes a prebuilt [Google ADK](https://google.github.io/adk-docs/) based agent integrated with the `falcon-mcp` server.
  4 | 
  5 | The goal is to provide customers an opinionated and validated set of instructions for running falcon-mcp and deploying it for their teams.
  6 | 
  7 | ## Table of Contents
  8 | 
  9 | 1. [Setting up and running locally (5 minutes)](#setting-up-and-running-locally-5-minutes)
 10 | 2. [Deployment - Why Deploy?](#deployment---why-deploy)
 11 | 3. [Deploying the agent to Cloud Run](#deploying-the-agent-to-cloud-run)
 12 | 4. [Deploying to Vertex AI Agent Engine and registering on Agentspace](#deploying-to-vertex-ai-agent-engine-and-registering-on-agentspace)
 13 | 5. [Securing access, Evaluating, Optimizing performance and costs](#securing-access-evaluating-optimizing-performance-and-costs)
 14 | 
 15 | 
 16 | ### Setting up and running locally (5 minutes)
 17 | You can run the following commands locally on Linux / Mac or in Google Cloud Shell.
 18 | If you plan to deploy the agent, it is recommended to run in Google Cloud Shell.
 19 | 
 20 | ```bash
 21 | 
 22 | git clone https://github.com/CrowdStrike/falcon-mcp.git
 23 | 
 24 | cd falcon-mcp
 25 | 
 26 | cd examples/adk
 27 | 
 28 | # create and activate python environment
 29 | python3 -m venv .venv
 30 | . .venv/bin/activate
 31 | 
 32 | # install depenencies
 33 | pip install -r falcon_agent/requirements.txt
 34 | 
 35 | chmod +x adk_agent_operations.sh
 36 | 
 37 | ./adk_agent_operations.sh
 38 | 
 39 | ```
 40 | 
 41 | 
 42 | 
 43 | The script will create `.env` file in `falcon_agent/` directory and prompt you to update it. At a minimum update the `General Agent Configuration` section.
 44 | 
 45 | 
 46 | <details>
 47 | 
 48 | <summary><b>Sample Output - Very First Run</b></summary>
 49 | 
 50 | ```bash
 51 | ./adk_agent_operations.sh
 52 | INFO: No operation mode provided and './falcon_agent/.env' is not found.
 53 | INFO: Attempting to copy template './falcon_agent/env.properties' to './falcon_agent/.env'.
 54 | SUCCESS: './falcon_agent/env.properties' copied to './falcon_agent/.env'.
 55 | ACTION REQUIRED: Please update the variables in './falcon_agent/.env' before running this script with an operation mode.
 56 | 
 57 | ```
 58 | </details>
 59 | 
 60 | <br>
 61 | 
 62 | > [!NOTE]
 63 | > Make sure you get and update the GOOGLE_API_KEY using these [instructions](https://ai.google.dev/gemini-api/docs/api-key).
 64 | 
 65 | Now run the script with `local_run` parameter.
 66 | 
 67 | 
 68 | ```bash
 69 | # local run
 70 | ./adk_agent_operations.sh local_run
 71 | ```
 72 | 
 73 | 
 74 | 
 75 | Here is the sample output
 76 | 
 77 | 
 78 | <details>
 79 | 
 80 | <summary><b>Sample Output - Local Run</b></summary>
 81 | 
 82 | ```bash
 83 | ./adk_agent_operations.sh local_run
 84 | INFO: Operation mode selected: 'local_run'.
 85 | --- Loading environment variables from './falcon_agent/.env' ---
 86 | --- Environment variables loaded. ---
 87 | --- Validating required environment variables for 'local_run' mode ---
 88 | INFO: Variable 'GOOGLE_GENAI_USE_VERTEXAI' is set and valid.
 89 | INFO: Variable 'GOOGLE_API_KEY' is set and valid.
 90 | INFO: Variable 'GOOGLE_MODEL' is set and valid.
 91 | INFO: Variable 'FALCON_CLIENT_ID' is set and valid.
 92 | INFO: Variable 'FALCON_CLIENT_SECRET' is set and valid.
 93 | INFO: Variable 'FALCON_BASE_URL' is set and valid.
 94 | INFO: Variable 'FALCON_AGENT_PROMPT' is set and valid.
 95 | --- All required environment variables are VALID. ---
 96 | INFO: Running ADK Agent for local development...
 97 | INFO:     Started server process [20071]
 98 | INFO:     Waiting for application startup.
 99 | 
100 | +-----------------------------------------------------------------------------+
101 | | ADK Web Server started                                                      |
102 | |                                                                             |
103 | | For local testing, access at http://localhost:8000.                         |
104 | +-----------------------------------------------------------------------------+
105 | 
106 | INFO:     Application startup complete.
107 | INFO:     Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
108 | 
109 | ```
110 | </details>
111 | 
112 | <br>
113 | 
114 | You can access the agent on http://localhost:8000 🚀
115 | 
116 | > If running in the Google Cloud Shell - please use the web preview with port 8000.
117 | 
118 | You can stop the agent with `ctrl+C`
119 | 
120 | 
121 | ### Deployment - Why Deploy?
122 | You may want to deploy the agent (with the `falcon-mcp` server) for following reasons
123 | 
124 | 1. You do not want to hand out credentials to everyone to run MCP server locally
125 | 2. You want to share the ready to use agent with your team
126 | 3. Use it for demos without any setup
127 | 
128 | You have two distinct paths to deployment:
129 | 1. Deploy on Cloud Run
130 | 2. Deploy on Vertex AI Agent Engine (and access through Agentspace after registration)
131 | 
132 | <br>
133 | 
134 | > [!NOTE]
135 | > For all the following sections - If you are not running in Google Cloud Shell, make sure you have `gcloud` CLI [installed](https://cloud.google.com/sdk/docs/install) and you have authenticated with your username (preferably as owner of the project) on your local computer.
136 | 
137 | ### Deploying the agent to Cloud Run
138 | 
139 | This section covers deployment to cloud run. Make sure you have all the required [APIs enabled](https://cloud.google.com/run/docs/quickstarts/build-and-deploy/deploy-python-service#before-you-begin) on the GCP project.
140 | 
141 | ```bash
142 | cd examples/adk/
143 | ./adk_agent_operations.sh cloudrun_deploy
144 | ```
145 | 
146 | In the sample output below, note the lines marked with ➡️
147 | 
148 | 1. You will have to provide input for `Allow unauthenticated invocations?` (say N)
149 | 2. Once deployment is completed you get a URL to access your agent.
150 | 
151 | 
152 | <details>
153 | 
154 | <summary><b>Sample Output - Cloud Run Deloyment</b></summary>
155 | 
156 | ```bash
157 | INFO: Operation mode selected: 'cloudrun_deploy'.
158 | --- Loading environment variables from './falcon_agent/.env' ---
159 | --- Environment variables loaded. ---
160 | --- Validating required environment variables for 'cloudrun_deploy' mode ---
161 | INFO: Variable 'GOOGLE_GENAI_USE_VERTEXAI' is set and valid.
162 | INFO: Variable 'GOOGLE_MODEL' is set and valid.
163 | INFO: Variable 'FALCON_CLIENT_ID' is set and valid.
164 | INFO: Variable 'FALCON_CLIENT_SECRET' is set and valid.
165 | INFO: Variable 'FALCON_BASE_URL' is set and valid.
166 | INFO: Variable 'FALCON_AGENT_PROMPT' is set and valid.
167 | INFO: Variable 'PROJECT_ID' is set and valid.
168 | INFO: Variable 'REGION' is set and valid.
169 | --- All required environment variables are VALID. ---
170 | INFO: Preparing for Cloud Run deployment...
171 | INFO: Backing up './falcon_agent/.env' to './falcon_agent/.env.bak'.
172 | INFO: Modifying './falcon_agent/.env': Deleting GOOGLE_API_KEY and setting GOOGLE_GENAI_USE_VERTEXAI=True.
173 | INFO: Re-loading modified environment variables.
174 | INFO: Deploying ADK Agent to Cloud Run...
175 | Start generating Cloud Run source files in /tmp/cloud_run_deploy_src/20250801_071151
176 | Copying agent source code...
177 | Copying agent source code complete.
178 | Creating Dockerfile...
179 | Creating Dockerfile complete: /tmp/cloud_run_deploy_src/20250801_071151/Dockerfile
180 | Deploying to Cloud Run...
181 | ➡️ Allow unauthenticated invocations to [falcon-agent-service] (y/N)?  N
182 | 
183 | Building using Dockerfile and deploying container to Cloud Run service [falcon-agent-service] in project [crowdstrikexxxxxxx] region [us-central1]
184 | ⠛ Building and deploying new service... Uploading sources.
185 |   ⠛ Uploading sources...
186 | ✓ Building and deploying new service... Done.
187 |   ✓ Uploading sources...
188 |   ✓ Building Container... Logs are available at [https://console.cloud.google.com/cloud-build/builds;region=us-central1/b1dbfe60-46fe-4cc1-ba6a-xxxx?project=xxxxx].
189 |   ✓ Creating Revision...
190 |   ✓ Routing traffic...
191 |   ✓ Setting IAM Policy...
192 | Done.
193 | Service [falcon-agent-service] revision [falcon-agent-service-00001-abc] has been deployed and is serving 100 percent of traffic.
194 | ➡️ Service URL: https://falcon-agent-service-xxxxx.us-central1.run.app
195 | INFO: Display format: "none"
196 | Cleaning up the temp folder: /tmp/cloud_run_deploy_src/20250801_071151
197 | SUCCESS: Cloud Run deployment completed successfully.
198 | --- Operation 'cloudrun_deploy' complete. ---
199 | INFO: Restoring .env file from backup: './falcon_agent/.env.bak'.
200 | ```
201 | 
202 | </details>
203 | 
204 | <br>
205 | 
206 | > [!NOTE]
207 | > By default the service has IAM authentication enabled for it. Please follow steps below to enable access to yourself and your team.
208 | 
209 | 
210 | 1. Cloud Run - Services - select `falcon-agent-service`, by clicking the checkbox next to it.
211 | 2. At the top click `permissions`, a pane `Permissions for falcon-agent-service` should open on the right hand side.
212 | 3. Click `Add principal`
213 | 4. Add the users you want to provide access to and provide them `Cloud Run Invoker` role.
214 | 5. Wait for some time.
215 | 
216 | **Accessing the service**
217 | 
218 | 1. Ask your users to run the following command (replace project id and region with the project id & region in which you have deployed the service)
219 | 
220 | ```bash
221 | gcloud run services proxy falcon-agent-service --project PROJECT-ID --region YOUR-REGION
222 | ```
223 | 
224 | <details>
225 | 
226 | <summary><b>Sample Output Accessing Cloud Run service through local proxy</b></summary>
227 | 
228 | 
229 | ```bash
230 | # You might be asked to install a component, for the proxy to work locally
231 | This command requires the `cloud-run-proxy` component to be installed. Would
232 |  you like to install the `cloud-run-proxy` component to continue command
233 | execution? (Y/n)?  Y
234 | 
235 | Proxying to Cloud Run service [falcon-agent-service] in project [crowdstrike-xxx-yyy] region [us-central1]
236 | http://127.0.0.1:8080 proxies to https://falcon-agent-service-abc1234-uc.a.run.app
237 | 
238 | ```
239 | </details>
240 | 
241 | 2. Now they can access the Cloud Run Service locally on `http://localhost:8080`
242 | 
243 | ### Deploying to Vertex AI Agent Engine and registering on Agentspace
244 | 
245 | This section covers deployment to Vetex AI Agent Engine. To acces the agent and to consolidate all your agents under one umbrella you can also register the deployed agent to Agentspace.
246 | 
247 | 1. Make sure that you create a bucket for staging the Agent Engine artifacts in the same project as the deployment (env variable - `AGENT_ENGINE_STAGING_BUCKET`).
248 | 
249 | ```bash
250 | cd examples/adk/
251 | ./adk_agent_operations.sh agent_engine_deploy
252 | ```
253 | 
254 | And here is the sample output.
255 | 
256 | Make sure you copy the Agent Engine Number from the output (marked by ➡️ for illustration)
257 | 
258 | <details>
259 | 
260 | <summary><b>Sample Output - Agent Engine Deployment</b></summary>
261 | 
262 | ```bash
263 | INFO: Operation mode selected: 'agent_engine_deploy'.
264 | --- Loading environment variables from './falcon_agent/.env' ---
265 | --- Environment variables loaded. ---
266 | --- Validating required environment variables for 'agent_engine_deploy' mode ---
267 | INFO: Variable 'GOOGLE_GENAI_USE_VERTEXAI' is set and valid.
268 | INFO: Variable 'GOOGLE_MODEL' is set and valid.
269 | INFO: Variable 'FALCON_CLIENT_ID' is set and valid.
270 | INFO: Variable 'FALCON_CLIENT_SECRET' is set and valid.
271 | INFO: Variable 'FALCON_BASE_URL' is set and valid.
272 | INFO: Variable 'FALCON_AGENT_PROMPT' is set and valid.
273 | INFO: Variable 'PROJECT_ID' is set and valid.
274 | INFO: Variable 'REGION' is set and valid.
275 | INFO: Variable 'AGENT_ENGINE_STAGING_BUCKET' is set and valid.
276 | --- All required environment variables are VALID. ---
277 | INFO: Preparing for Agent Engine deployment...
278 | INFO: Backing up './falcon_agent/.env' to './falcon_agent/.env.bak'.
279 | INFO: Modifying './falcon_agent/.env': Deleting GOOGLE_API_KEY and setting GOOGLE_GENAI_USE_VERTEXAI=True.
280 | INFO: Re-loading modified environment variables.
281 | INFO: Deploying ADK Agent to Agent Engine...
282 | Copying agent source code...
283 | Copying agent source code complete.
284 | Initializing Vertex AI...
285 | Resolving files and dependencies...
286 | Reading environment variables from /tmp/agent_engine_deploy_src/20250801_103024/.env
287 | Vertex AI initialized.
288 | Created /tmp/agent_engine_deploy_src/20250801_103024/agent_engine_app.py
289 | Files and dependencies resolved
290 | Deploying to agent engine...
291 | Reading requirements from requirements='/tmp/agent_engine_deploy_src/20250801_103024/requirements.txt'
292 | Read the following lines: ['google-adk[eval]', 'falcon-mcp', 'google-cloud-aiplatform[agent_engines]', 'cloudpickle']
293 | Identified the following requirements: {'google-cloud-aiplatform': '1.105.0', 'cloudpickle': '3.1.1', 'pydantic': '2.11.7'}
294 | The following requirements are missing: {'pydantic'}
295 | The following requirements are appended: {'pydantic==2.11.7'}
296 | The final list of requirements: ['google-adk[eval]', 'falcon-mcp', 'google-cloud-aiplatform[agent_engines]', 'cloudpickle', 'pydantic==2.11.7']
297 | Using bucket agent-engine-xxyyzz
298 | Wrote to gs://agent-engine-xxyyzz/agent_engine/agent_engine.pkl
299 | Writing to gs://agent-engine-xxyyzz/agent_engine/requirements.txt
300 | Creating in-memory tarfile of extra_packages
301 | Writing to gs://agent-engine-xxyyzz/agent_engine/dependencies.tar.gz
302 | Creating AgentEngine
303 | INFO:vertexai.agent_engines:Creating AgentEngine
304 | Create AgentEngine backing LRO: projects/123456789101/locations/us-central1/reasoningEngines/3670952665795123456/operations/5379102769057612345
305 | INFO:vertexai.agent_engines:Create AgentEngine backing LRO: projects/123456789101/locations/us-central1/reasoningEngines/3670952665795123456/operations/5379102769057612345
306 | View progress and logs at https://console.cloud.google.com/logs/query?project=crowdstrike-xxxx-yyyy
307 | INFO:vertexai.agent_engines:View progress and logs at https://console.cloud.google.com/logs/query?project=crowdstrike-xxxx-yyyy
308 | ➡️ AgentEngine created. Resource name: projects/123456789101/locations/us-central1/reasoningEngines/3670952665795123456
309 | INFO:vertexai.agent_engines:AgentEngine created. Resource name: projects/123456789101/locations/us-central1/reasoningEngines/3670952665795123456
310 | To use this AgentEngine in another session:
311 | INFO:vertexai.agent_engines:To use this AgentEngine in another session:
312 | agent_engine = vertexai.agent_engines.get('projects/123456789101/locations/us-central1/reasoningEngines/3670952665795123456')
313 | INFO:vertexai.agent_engines:agent_engine = vertexai.agent_engines.get('projects/123456789101/locations/us-central1/reasoningEngines/3670952665795123456')
314 | Cleaning up the temp folder: /tmp/agent_engine_deploy_src/20250801_103024
315 | SUCCESS: Agent Engine deployment completed successfully.
316 | --- Operation 'agent_engine_deploy' complete. ---
317 | INFO: Restoring .env file from backup: './falcon_agent/.env.bak'.
318 | 
319 | ```
320 | 
321 | 
322 | </details>
323 | 
324 | <br>
325 | 
326 | Once the agent is deployed on Agent Engine, you can register it on Agentspace to work with an Agent Engine Application.
327 | 
328 | Make sure you have the Agent Engine Number from the previous step
329 | 
330 | 
331 | 1. Go to the Agentspace [page](https://console.cloud.google.com/gen-app-builder/engines) in Google Cloud Console.
332 | 2. Create an App (Type - Agentspace)
333 | 3. Note down the app details including the app name (e.g. google-security-agent-app_1750057151234)
334 | 4. Make sure that you have the Agent Space Admin role while performing the following actions
335 | 5. Enable Discovery Engine API for your project
336 | 6. Provide the following roles to the Discovery Engine Service Account
337 |    - Vertex AI viewer
338 |    - Vertex AI user
339 | 7. Please note that these roles need to be provided into the project housing your Agent Engine Agent. Also you need to enable the show Google provided role grants to access the Discovery Engine Service Account.
340 | 
341 | Update the environment variables `PROJECT_NUMBER`, `AGENT_LOCATION`, `REASONING_ENGINE_NUMBER` and `AGENT_SPACE_APP_NAME` in the `# Agentspace Specific` section.
342 | 
343 | Now to register the agent and make it available to your application use the following command.
344 | 
345 | ```bash
346 | cd examples/adk/
347 | ./adk_agent_operations.sh agentspace_register
348 | ```
349 | 
350 | <details>
351 | 
352 | <summary><b>Sample Output - Agentspace Registration</b></summary>
353 | 
354 | 
355 | ```bash
356 | INFO: Operation mode selected: 'agentspace_register'.
357 | --- Loading environment variables from './falcon_agent/.env' ---
358 | --- Environment variables loaded. ---
359 | --- Validating required environment variables for 'agentspace_register' mode ---
360 | INFO: Variable 'GOOGLE_GENAI_USE_VERTEXAI' is set and valid.
361 | INFO: Variable 'GOOGLE_MODEL' is set and valid.
362 | INFO: Variable 'FALCON_CLIENT_ID' is set and valid.
363 | INFO: Variable 'FALCON_CLIENT_SECRET' is set and valid.
364 | INFO: Variable 'FALCON_BASE_URL' is set and valid.
365 | INFO: Variable 'FALCON_AGENT_PROMPT' is set and valid.
366 | INFO: Variable 'PROJECT_ID' is set and valid.
367 | INFO: Variable 'REGION' is set and valid.
368 | INFO: Variable 'PROJECT_NUMBER' is set and valid.
369 | INFO: Variable 'AGENT_LOCATION' is set and valid.
370 | INFO: Variable 'REASONING_ENGINE_NUMBER' is set and valid.
371 | INFO: Variable 'AGENT_SPACE_APP_NAME' is set and valid.
372 | --- All required environment variables are VALID. ---
373 | INFO: Registering ADK Agent with AgentSpace...
374 | INFO: Sending POST request to: https://discoveryengine.googleapis.com/v1alpha/projects/security-xyzabc-123456/locations/global/collections/default_collection/engines/google-security-agent-app_1750057112345/assistants/default_assistant/agents
375 | DEBUG: Request Body :
376 | {
377 |     "displayName": "Crowdstrike Falcon Agent",
378 |     "description": "Allows users interact with Crowdstrike Falcon backend",
379 |     "adk_agent_definition":
380 |     {
381 |         "tool_settings": {
382 |             "tool_description": "Crowdstrike Falcon tools"
383 |         },
384 |         "provisioned_reasoning_engine": {
385 |             "reasoning_engine":"projects/707099123456/locations/us-central1/reasoningEngines/5047646776881234567"
386 |         }
387 |     }
388 | }
389 | ...
390 | {
391 |   "name": "projects/707099123456/locations/global/collections/default_collection/engines/google-security-agent-app_1750057112345/assistants/default_assistant/agents/2662627860861234567",
392 |   "displayName": "Crowdstrike Falcon Agent",
393 |   "description": "Allows users interact with Crowdstrike Falcon backend",
394 |   "createTime": "2025-08-03T15:39:03.129318186Z",
395 |   "adkAgentDefinition": {
396 |     "toolSettings": {
397 |       "toolDescription": "Crowdstrike Falcon tools"
398 |     },
399 |     "provisionedReasoningEngine": {
400 |       "reasoningEngine": "projects/707099123456/locations/us-central1/reasoningEngines/5047646776881234567"
401 |     }
402 |   },
403 |   "state": "ENABLED"
404 | }
405 | 
406 | SUCCESS: cURL command completed successfully for AgentSpace registration.
407 | --- Operation 'agentspace_register' complete. ---
408 | 
409 | ```
410 | </details>
411 | 
412 | <br>
413 | 
414 | > You can find more about Agentspace registration [here](https://cloud.google.com/agentspace/agentspace-enterprise/docs/assistant#create-assistant-existing-app).
415 | 
416 | Now you can access the agent in the Agentspace application you created earlier.
417 | 
418 | 
419 | In case you want to delete the agent from the Agentspace application, use the following set of commands (replace the variables as needed).
420 | 
421 | <details>
422 | 
423 | <summary><b>List and deregister the Agent</b></summary>
424 | 
425 | ```bash
426 | # List the agents for your application
427 | 
428 | curl -X GET -H "Authorization: Bearer $(gcloud auth print-access-token)" \
429 | -H "Content-Type: application/json" \
430 | -H "X-Goog-User-Project: $PROJECT_ID" \
431 | "https://discoveryengine.googleapis.com/v1alpha/projects/$PROJECT_ID/locations/global/collections/default_collection/engines/$AGENT_ENGINE_APP_NAME/assistants/default_assistant/agents"
432 | 
433 | # note down the agent number (export as REASONING_ENGINE_NUMBER) and use that in the next command.
434 | 
435 | curl -X DELETE -H "Authorization: Bearer $(gcloud auth print-access-token)" \
436 | -H "Content-Type: application/json" \
437 | -H "X-Goog-User-Project: $PROJECT_ID" \
438 | https://discoveryengine.googleapis.com/v1alpha/projects/$PROJECT_ID/locations/global/collections/default_collection/engines/$AGENT_ENGINE_APP_NAME/assistants/default_assistant/agents/$REASONING_ENGINE_NUMBER
439 | 
440 | 
441 | ```
442 | </details>
443 | 
444 | ### Securing access, Evaluating, Optimizing performance and costs
445 | 
446 | #### Securing access
447 |   1. For local runs make sure that you are not using a shared machine
448 |   2. For Cloud Run deployment you can use - [Control access on an individual service or job](https://cloud.google.com/run/docs/securing/managing-access#control-service-or-job-access) - that is the default behavior for this deployment.
449 |   3. For agent running in Agentspace - you can provide access (Predefined role - `Discovery Engine User`) selectively by navigating to Agentspace-Apps-Your App -Integration-Grant Permissions.
450 | 
451 | #### Evaluating
452 | It is advised to evaluate the agent for the trajectory it takes and the output it produces - you can use [ADK documentation](https://google.github.io/adk-docs/evaluate/) to evaluate this agent. You can also test with different models.
453 | 
454 | #### Optimizing performance and costs
455 | Various native performance improvements are already part of the codebase. You can further optimize the performance and reduce the LLM costs by controlling the value of the environment variable `MAX_PREV_USER_INTERACTIONS`. You can test how many previous conversations (instead of ALL conversations by default) work for your use case (recommended 5). You can also use the appropriate [Gemini Model](https://ai.google.dev/gemini-api/docs/models#model-variations) for both cost and performance optimizations.
456 | 
```

--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------

```markdown
  1 | ![CrowdStrike Falcon](https://raw.githubusercontent.com/CrowdStrike/falconpy/main/docs/asset/cs-logo.png)
  2 | 
  3 | # falcon-mcp
  4 | 
  5 | [![PyPI version](https://badge.fury.io/py/falcon-mcp.svg)](https://badge.fury.io/py/falcon-mcp)
  6 | [![PyPI - Python Version](https://img.shields.io/pypi/pyversions/falcon-mcp)](https://pypi.org/project/falcon-mcp/)
  7 | [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
  8 | 
  9 | **falcon-mcp** is a Model Context Protocol (MCP) server that connects AI agents with the CrowdStrike Falcon platform, powering intelligent security analysis in your agentic workflows. It delivers programmatic access to essential security capabilities—including detections, incidents, and behaviors—establishing the foundation for advanced security operations and automation.
 10 | 
 11 | > [!IMPORTANT]
 12 | > **🚧 Public Preview**: This project is currently in public preview and under active development. Features and functionality may change before the stable 1.0 release. While we encourage exploration and testing, please avoid production deployments. We welcome your feedback through [GitHub Issues](https://github.com/crowdstrike/falcon-mcp/issues) to help shape the final release.
 13 | 
 14 | ## Table of Contents
 15 | 
 16 | - [API Credentials \& Required Scopes](#api-credentials--required-scopes)
 17 |   - [Setting Up CrowdStrike API Credentials](#setting-up-crowdstrike-api-credentials)
 18 |   - [Required API Scopes by Module](#required-api-scopes-by-module)
 19 | - [Available Modules, Tools \& Resources](#available-modules-tools--resources)
 20 |   - [Cloud Security Module](#cloud-security-module)
 21 |   - [Core Functionality (Built into Server)](#core-functionality-built-into-server)
 22 |   - [Detections Module](#detections-module)
 23 |   - [Discover Module](#discover-module)
 24 |   - [Hosts Module](#hosts-module)
 25 |   - [Identity Protection Module](#identity-protection-module)
 26 |   - [Incidents Module](#incidents-module)
 27 |   - [Intel Module](#intel-module)
 28 |   - [Sensor Usage Module](#sensor-usage-module)
 29 |   - [Serverless Module](#serverless-module)
 30 |   - [Spotlight Module](#spotlight-module)
 31 | - [Installation \& Setup](#installation--setup)
 32 |   - [Prerequisites](#prerequisites)
 33 |   - [Environment Configuration](#environment-configuration)
 34 |   - [Installation](#installation)
 35 | - [Usage](#usage)
 36 |   - [Command Line](#command-line)
 37 |   - [Module Configuration](#module-configuration)
 38 |   - [Additional Command Line Options](#additional-command-line-options)
 39 |   - [As a Library](#as-a-library)
 40 |   - [Running Examples](#running-examples)
 41 | - [Container Usage](#container-usage)
 42 |   - [Using Pre-built Image (Recommended)](#using-pre-built-image-recommended)
 43 |   - [Building Locally (Development)](#building-locally-development)
 44 | - [Editor/Assistant Integration](#editorassistant-integration)
 45 |   - [Using `uvx` (recommended)](#using-uvx-recommended)
 46 |   - [With Module Selection](#with-module-selection)
 47 |   - [Using Individual Environment Variables](#using-individual-environment-variables)
 48 |   - [Docker Version](#docker-version)
 49 | - [Additional Deployment Options](#additional-deployment-options)
 50 |   - [Amazon Bedrock AgentCore](#amazon-bedrock-agentcore)
 51 |   - [Google Cloud (Cloud Run and Vertex AI)](#google-cloud-cloud-run-and-vertex-ai)
 52 | - [Contributing](#contributing)
 53 |   - [Getting Started for Contributors](#getting-started-for-contributors)
 54 |   - [Running Tests](#running-tests)
 55 |   - [Developer Documentation](#developer-documentation)
 56 | - [License](#license)
 57 | - [Support](#support)
 58 | 
 59 | ## API Credentials & Required Scopes
 60 | 
 61 | ### Setting Up CrowdStrike API Credentials
 62 | 
 63 | Before using the Falcon MCP Server, you need to create API credentials in your CrowdStrike console:
 64 | 
 65 | 1. **Log into your CrowdStrike console**
 66 | 2. **Navigate to Support > API Clients and Keys**
 67 | 3. **Click "Add new API client"**
 68 | 4. **Configure your API client**:
 69 |    - **Client Name**: Choose a descriptive name (e.g., "Falcon MCP Server")
 70 |    - **Description**: Optional description for your records
 71 |    - **API Scopes**: Select the scopes based on which modules you plan to use (see below)
 72 | 
 73 | > **Important**: Ensure your API client has the necessary scopes for the modules you plan to use. You can always update scopes later in the CrowdStrike console.
 74 | 
 75 | ### Required API Scopes by Module
 76 | 
 77 | The Falcon MCP Server supports different modules, each requiring specific API scopes:
 78 | 
 79 | | Module | Required API Scopes | Purpose |
 80 | |-|-|-|
 81 | | **Cloud Security** | `Falcon Container Image:read` | Find and analyze kubernetes containers inventory and container imges vulnerabilities |
 82 | | **Core** | _No additional scopes_ | Basic connectivity and system information |
 83 | | **Detections** | `Alerts:read` | Find and analyze detections to understand malicious activity |
 84 | | **Discover** | `Assets:read` | Search and analyze application inventory across your environment |
 85 | | **Hosts** | `Hosts:read` | Manage and query host/device information |
 86 | | **Identity Protection** | `Identity Protection Entities:read`<br>`Identity Protection Timeline:read`<br>`Identity Protection Detections:read`<br>`Identity Protection Assessment:read`<br>`Identity Protection GraphQL:write` | Comprehensive entity investigation and identity protection analysis |
 87 | | **Incidents** | `Incidents:read` | Analyze security incidents and coordinated activities |
 88 | | **Intel** | `Actors (Falcon Intelligence):read`<br>`Indicators (Falcon Intelligence):read`<br>`Reports (Falcon Intelligence):read` | Research threat actors, IOCs, and intelligence reports |
 89 | | **Sensor Usage** | `Sensor Usage:read` | Access and analyze sensor usage data |
 90 | | **Serverless** | `Falcon Container Image:read` | Search for vulnerabilities in serverless functions across cloud service providers |
 91 | | **Spotlight** | `Vulnerabilities:read` | Manage and analyze vulnerability data and security assessments |
 92 | 
 93 | ## Available Modules, Tools & Resources
 94 | 
 95 | > [!IMPORTANT]
 96 | > ⚠️ **Important Note on FQL Guide Resources**: Several modules include FQL (Falcon Query Language) guide resources that provide comprehensive query documentation and examples. While these resources are designed to assist AI assistants and users with query construction, **FQL has nuanced syntax requirements and field-specific behaviors** that may not be immediately apparent. AI-generated FQL filters should be **tested and validated** before use in production environments. We recommend starting with simple queries and gradually building complexity while verifying results in a test environment first.
 97 | 
 98 | **About Tools & Resources**: This server provides both tools (actions you can perform) and resources (documentation and context). Tools execute operations like searching for detections or analyzing threats, while resources provide comprehensive documentation like FQL query guides that AI assistants can reference for context without requiring tool calls.
 99 | 
100 | ### Cloud Security Module
101 | 
102 | **API Scopes Required**:
103 | 
104 | - `Falcon Container Image:read`
105 | 
106 | Provides tools for accessing and analyzing CrowdStrike Cloud Security resources:
107 | 
108 | - `falcon_search_kubernetes_containers`: Search for containers from CrowdStrike Kubernetes & Containers inventory
109 | - `falcon_count_kubernetes_containers`: Count for containers by filter criteria from CrowdStrike Kubernetes & Containers inventory
110 | - `falcon_search_images_vulnerabilities`: Search for images vulnerabilities from CrowdStrike Image Assessments
111 | 
112 | **Resources**:
113 | 
114 | - `falcon://cloud/kubernetes-containers/fql-guide`: Comprehensive FQL documentation and examples for kubernetes containers searches
115 | - `falcon://cloud/images-vulnerabilities/fql-guide`: Comprehensive FQL documentation and examples for images vulnerabilities searches
116 | 
117 | **Use Cases**: Manage kubernetes containers inventory, container images vulnerabilities analysis
118 | 
119 | ### Core Functionality (Built into Server)
120 | 
121 | **API Scopes**: _None required beyond basic API access_
122 | 
123 | The server provides core tools for interacting with the Falcon API:
124 | 
125 | - `falcon_check_connectivity`: Check connectivity to the Falcon API
126 | - `falcon_list_enabled_modules`: Lists enabled modules in the falcon-mcp server
127 |     > These modules are determined by the `--modules` [flag](#module-configuration) when starting the server. If no modules are specified, all available modules are enabled.
128 | - `falcon_list_modules`: Lists all available modules in the falcon-mcp server
129 | 
130 | ### Detections Module
131 | 
132 | **API Scopes Required**: `Alerts:read`
133 | 
134 | Provides tools for accessing and analyzing CrowdStrike Falcon detections:
135 | 
136 | - `falcon_search_detections`: Find and analyze detections to understand malicious activity in your environment
137 | - `falcon_get_detection_details`: Get comprehensive detection details for specific detection IDs to understand security threats
138 | 
139 | **Resources**:
140 | 
141 | - `falcon://detections/search/fql-guide`: Comprehensive FQL documentation and examples for detection searches
142 | 
143 | **Use Cases**: Threat hunting, security analysis, incident response, malware investigation
144 | 
145 | ### Discover Module
146 | 
147 | **API Scopes Required**: `Assets:read`
148 | 
149 | Provides tools for accessing and managing CrowdStrike Falcon Discover applications and unmanaged assets:
150 | 
151 | - `falcon_search_applications`: Search for applications in your CrowdStrike environment
152 | - `falcon_search_unmanaged_assets`: Search for unmanaged assets (systems without Falcon sensor installed) that have been discovered by managed systems
153 | 
154 | **Resources**:
155 | 
156 | - `falcon://discover/applications/fql-guide`: Comprehensive FQL documentation and examples for application searches
157 | - `falcon://discover/hosts/fql-guide`: Comprehensive FQL documentation and examples for unmanaged assets searches
158 | 
159 | **Use Cases**: Application inventory management, software asset management, license compliance, vulnerability assessment, unmanaged asset discovery, security gap analysis
160 | 
161 | ### Hosts Module
162 | 
163 | **API Scopes Required**: `Hosts:read`
164 | 
165 | Provides tools for accessing and managing CrowdStrike Falcon hosts/devices:
166 | 
167 | - `falcon_search_hosts`: Search for hosts in your CrowdStrike environment
168 | - `falcon_get_host_details`: Retrieve detailed information for specified host device IDs
169 | 
170 | **Resources**:
171 | 
172 | - `falcon://hosts/search/fql-guide`: Comprehensive FQL documentation and examples for host searches
173 | 
174 | **Use Cases**: Asset management, device inventory, host monitoring, compliance reporting
175 | 
176 | ### Identity Protection Module
177 | 
178 | **API Scopes Required**: `Identity Protection Entities:read`, `Identity Protection Timeline:read`, `Identity Protection Detections:read`, `Identity Protection Assessment:read`, `Identity Protection GraphQL:write`
179 | 
180 | Provides tools for accessing and managing CrowdStrike Falcon Identity Protection capabilities:
181 | 
182 | - `idp_investigate_entity`: Entity investigation tool for analyzing users, endpoints, and other entities with support for timeline analysis, relationship mapping, and risk assessment
183 | 
184 | **Use Cases**: Entity investigation, identity protection analysis, user behavior analysis, endpoint security assessment, relationship mapping, risk assessment
185 | 
186 | ### Incidents Module
187 | 
188 | **API Scopes Required**: `Incidents:read`
189 | 
190 | Provides tools for accessing and analyzing CrowdStrike Falcon incidents:
191 | 
192 | - `falcon_show_crowd_score`: View calculated CrowdScores and security posture metrics for your environment
193 | - `falcon_search_incidents`: Find and analyze security incidents to understand coordinated activity in your environment
194 | - `falcon_get_incident_details`: Get comprehensive incident details to understand attack patterns and coordinated activities
195 | - `falcon_search_behaviors`: Find and analyze behaviors to understand suspicious activity in your environment
196 | - `falcon_get_behavior_details`: Get detailed behavior information to understand attack techniques and tactics
197 | 
198 | **Resources**:
199 | 
200 | - `falcon://incidents/crowd-score/fql-guide`: Comprehensive FQL documentation for CrowdScore queries
201 | - `falcon://incidents/search/fql-guide`: Comprehensive FQL documentation and examples for incident searches
202 | - `falcon://incidents/behaviors/fql-guide`: Comprehensive FQL documentation and examples for behavior searches
203 | 
204 | **Use Cases**: Incident management, threat assessment, attack pattern analysis, security posture monitoring
205 | 
206 | ### Intel Module
207 | 
208 | **API Scopes Required**:
209 | 
210 | - `Actors (Falcon Intelligence):read`
211 | - `Indicators (Falcon Intelligence):read`
212 | - `Reports (Falcon Intelligence):read`
213 | 
214 | Provides tools for accessing and analyzing CrowdStrike Intelligence:
215 | 
216 | - `falcon_search_actors`: Research threat actors and adversary groups tracked by CrowdStrike intelligence
217 | - `falcon_search_indicators`: Search for threat indicators and indicators of compromise (IOCs) from CrowdStrike intelligence
218 | - `falcon_search_reports`: Access CrowdStrike intelligence publications and threat reports
219 | 
220 | **Resources**:
221 | 
222 | - `falcon://intel/actors/fql-guide`: Comprehensive FQL documentation and examples for threat actor searches
223 | - `falcon://intel/indicators/fql-guide`: Comprehensive FQL documentation and examples for indicator searches
224 | - `falcon://intel/reports/fql-guide`: Comprehensive FQL documentation and examples for intelligence report searches
225 | 
226 | **Use Cases**: Threat intelligence research, adversary tracking, IOC analysis, threat landscape assessment
227 | 
228 | ### Sensor Usage Module
229 | 
230 | **API Scopes Required**: `Sensor Usage:read`
231 | 
232 | Provides tools for accessing and analyzing CrowdStrike Falcon sensor usage data:
233 | 
234 | - `falcon_search_sensor_usage`: Search for weekly sensor usage data in your CrowdStrike environment
235 | 
236 | **Resources**:
237 | 
238 | - `falcon://sensor-usage/weekly/fql-guide`: Comprehensive FQL documentation and examples for sensor usage searches
239 | 
240 | **Use Cases**: Sensor deployment monitoring, license utilization analysis, sensor health tracking
241 | 
242 | ### Serverless Module
243 | 
244 | **API Scopes Required**: `Falcon Container Image:read`
245 | 
246 | Provides tools for accessing and managing CrowdStrike Falcon Serverless Vulnerabilities:
247 | 
248 | - `falcon_search_serverless_vulnerabilities`: Search for vulnerabilities in your serverless functions across all cloud service providers
249 | 
250 | **Resources**:
251 | 
252 | - `falcon://serverless/vulnerabilities/fql-guide`: Comprehensive FQL documentation and examples for serverless vulnerabilities searches
253 | 
254 | **Use Cases**: Serverless security assessment, vulnerability management, cloud security monitoring
255 | 
256 | ### Spotlight Module
257 | 
258 | **API Scopes Required**: `Vulnerabilities:read`
259 | 
260 | Provides tools for accessing and managing CrowdStrike Spotlight vulnerabilities:
261 | 
262 | - `falcon_search_vulnerabilities`: Search for vulnerabilities in your CrowdStrike environment
263 | 
264 | **Resources**:
265 | 
266 | - `falcon://spotlight/vulnerabilities/fql-guide`: Comprehensive FQL documentation and examples for vulnerability searches
267 | 
268 | **Use Cases**: Vulnerability management, security assessments, compliance reporting, risk analysis, patch prioritization
269 | 
270 | ## Installation & Setup
271 | 
272 | ### Prerequisites
273 | 
274 | - Python 3.11 or higher
275 | - [`uv`](https://docs.astral.sh/uv/) or pip
276 | - CrowdStrike Falcon API credentials (see above)
277 | 
278 | ### Environment Configuration
279 | 
280 | You can configure your CrowdStrike API credentials in several ways:
281 | 
282 | #### Use a `.env` File
283 | 
284 | If you prefer using a `.env` file, you have several options:
285 | 
286 | ##### Option 1: Copy from cloned repository (if you've cloned it)
287 | 
288 | ```bash
289 | cp .env.example .env
290 | ```
291 | 
292 | ##### Option 2: Download the example file from GitHub
293 | 
294 | ```bash
295 | curl -o .env https://raw.githubusercontent.com/CrowdStrike/falcon-mcp/main/.env.example
296 | ```
297 | 
298 | ##### Option 3: Create manually with the following content
299 | 
300 | ```bash
301 | # Required Configuration
302 | FALCON_CLIENT_ID=your-client-id
303 | FALCON_CLIENT_SECRET=your-client-secret
304 | FALCON_BASE_URL=https://api.crowdstrike.com
305 | 
306 | # Optional Configuration (uncomment and modify as needed)
307 | #FALCON_MCP_MODULES=detections,incidents,intel
308 | #FALCON_MCP_TRANSPORT=stdio
309 | #FALCON_MCP_DEBUG=false
310 | #FALCON_MCP_HOST=127.0.0.1
311 | #FALCON_MCP_PORT=8000
312 | ```
313 | 
314 | #### Environment Variables
315 | 
316 | Alternatively, you can use environment variables directly.
317 | 
318 | Set the following environment variables in your shell:
319 | 
320 | ```bash
321 | # Required Configuration
322 | export FALCON_CLIENT_ID="your-client-id"
323 | export FALCON_CLIENT_SECRET="your-client-secret"
324 | export FALCON_BASE_URL="https://api.crowdstrike.com"
325 | 
326 | # Optional Configuration
327 | export FALCON_MCP_MODULES="detections,incidents,intel"  # Comma-separated list (default: all modules)
328 | export FALCON_MCP_TRANSPORT="stdio"                     # Transport method: stdio, sse, streamable-http
329 | export FALCON_MCP_DEBUG="false"                         # Enable debug logging: true, false
330 | export FALCON_MCP_HOST="127.0.0.1"                      # Host for HTTP transports
331 | export FALCON_MCP_PORT="8000"                           # Port for HTTP transports
332 | ```
333 | 
334 | **CrowdStrike API Region URLs:**
335 | 
336 | - **US-1 (Default)**: `https://api.crowdstrike.com`
337 | - **US-2**: `https://api.us-2.crowdstrike.com`
338 | - **EU-1**: `https://api.eu-1.crowdstrike.com`
339 | - **US-GOV**: `https://api.laggar.gcw.crowdstrike.com`
340 | 
341 | ### Installation
342 | 
343 | > [!NOTE]
344 | > If you just want to interact with falcon-mcp via an agent chat interface rather than running the server itself, take a look at [Additional Deployment Options](#additional-deployment-options). Otherwise continue to the installations steps below.
345 | 
346 | #### Install using uv
347 | 
348 | ```bash
349 | uv tool install falcon-mcp
350 | ```
351 | 
352 | #### Install using pip
353 | 
354 | ```bash
355 | pip install falcon-mcp
356 | ```
357 | 
358 | > [!TIP]
359 | > If `falcon-mcp` isn't found, update your shell PATH.
360 | 
361 | For installation via code editors/assistants, see the [Editor/Assitant](#editorassistant-integration) section below
362 | 
363 | ## Usage
364 | 
365 | ### Command Line
366 | 
367 | Run the server with default settings (stdio transport):
368 | 
369 | ```bash
370 | falcon-mcp
371 | ```
372 | 
373 | Run with SSE transport:
374 | 
375 | ```bash
376 | falcon-mcp --transport sse
377 | ```
378 | 
379 | Run with streamable-http transport:
380 | 
381 | ```bash
382 | falcon-mcp --transport streamable-http
383 | ```
384 | 
385 | Run with streamable-http transport on custom port:
386 | 
387 | ```bash
388 | falcon-mcp --transport streamable-http --host 0.0.0.0 --port 8080
389 | ```
390 | 
391 | ### Module Configuration
392 | 
393 | The Falcon MCP Server supports multiple ways to specify which modules to enable:
394 | 
395 | #### 1. Command Line Arguments (highest priority)
396 | 
397 | Specify modules using comma-separated lists:
398 | 
399 | ```bash
400 | # Enable specific modules
401 | falcon-mcp --modules detections,incidents,intel,spotlight,idp
402 | 
403 | # Enable only one module
404 | falcon-mcp --modules detections
405 | ```
406 | 
407 | #### 2. Environment Variable (fallback)
408 | 
409 | Set the `FALCON_MCP_MODULES` environment variable:
410 | 
411 | ```bash
412 | # Export environment variable
413 | export FALCON_MCP_MODULES=detections,incidents,intel,spotlight,idp
414 | falcon-mcp
415 | 
416 | # Or set inline
417 | FALCON_MCP_MODULES=detections,incidents,intel,spotlight,idp falcon-mcp
418 | ```
419 | 
420 | #### 3. Default Behavior (all modules)
421 | 
422 | If no modules are specified via command line or environment variable, all available modules are enabled by default.
423 | 
424 | **Module Priority Order:**
425 | 
426 | 1. Command line `--modules` argument (overrides all)
427 | 2. `FALCON_MCP_MODULES` environment variable (fallback)
428 | 3. All modules (default when none specified)
429 | 
430 | ### Additional Command Line Options
431 | 
432 | For all available options:
433 | 
434 | ```bash
435 | falcon-mcp --help
436 | ```
437 | 
438 | ### As a Library
439 | 
440 | ```python
441 | from falcon_mcp.server import FalconMCPServer
442 | 
443 | # Create and run the server
444 | server = FalconMCPServer(
445 |     base_url="https://api.us-2.crowdstrike.com",  # Optional, defaults to env var
446 |     debug=True,  # Optional, enable debug logging
447 |     enabled_modules=["detections", "incidents", "spotlight", "idp"]  # Optional, defaults to all modules
448 | )
449 | 
450 | # Run with stdio transport (default)
451 | server.run()
452 | 
453 | # Or run with SSE transport
454 | server.run("sse")
455 | 
456 | # Or run with streamable-http transport
457 | server.run("streamable-http")
458 | 
459 | # Or run with streamable-http transport on custom host/port
460 | server.run("streamable-http", host="0.0.0.0", port=8080)
461 | ```
462 | 
463 | ### Running Examples
464 | 
465 | ```bash
466 | # Run with stdio transport
467 | python examples/basic_usage.py
468 | 
469 | # Run with SSE transport
470 | python examples/sse_usage.py
471 | 
472 | # Run with streamable-http transport
473 | python examples/streamable_http_usage.py
474 | ```
475 | 
476 | ## Container Usage
477 | 
478 | The Falcon MCP Server is available as a pre-built container image for easy deployment:
479 | 
480 | ### Using Pre-built Image (Recommended)
481 | 
482 | ```bash
483 | # Pull the latest pre-built image
484 | docker pull quay.io/crowdstrike/falcon-mcp:latest
485 | 
486 | # Run with .env file (recommended)
487 | docker run -i --rm --env-file /path/to/.env quay.io/crowdstrike/falcon-mcp:latest
488 | 
489 | # Run with .env file and SSE transport
490 | docker run --rm -p 8000:8000 --env-file /path/to/.env \
491 |   quay.io/crowdstrike/falcon-mcp:latest --transport sse --host 0.0.0.0
492 | 
493 | # Run with .env file and streamable-http transport
494 | docker run --rm -p 8000:8000 --env-file /path/to/.env \
495 |   quay.io/crowdstrike/falcon-mcp:latest --transport streamable-http --host 0.0.0.0
496 | 
497 | # Run with .env file and custom port
498 | docker run --rm -p 8080:8080 --env-file /path/to/.env \
499 |   quay.io/crowdstrike/falcon-mcp:latest --transport streamable-http --host 0.0.0.0 --port 8080
500 | 
501 | # Run with .env file and specific modules (stdio transport - requires -i flag)
502 | docker run -i --rm --env-file /path/to/.env \
503 |   quay.io/crowdstrike/falcon-mcp:latest --modules detections,incidents,spotlight,idp
504 | 
505 | # Use a specific version instead of latest (stdio transport - requires -i flag)
506 | docker run -i --rm --env-file /path/to/.env \
507 |   quay.io/crowdstrike/falcon-mcp:1.2.3
508 | 
509 | # Alternative: Individual environment variables (stdio transport - requires -i flag)
510 | docker run -i --rm -e FALCON_CLIENT_ID=your_client_id -e FALCON_CLIENT_SECRET=your_secret \
511 |   quay.io/crowdstrike/falcon-mcp:latest
512 | ```
513 | 
514 | ### Building Locally (Development)
515 | 
516 | For development or customization purposes, you can build the image locally:
517 | 
518 | ```bash
519 | # Build the Docker image
520 | docker build -t falcon-mcp .
521 | 
522 | # Run the locally built image
523 | docker run --rm -e FALCON_CLIENT_ID=your_client_id -e FALCON_CLIENT_SECRET=your_secret falcon-mcp
524 | ```
525 | 
526 | > [!NOTE]
527 | > When using HTTP transports in Docker, always set `--host 0.0.0.0` to allow external connections to the container.
528 | 
529 | ## Editor/Assistant Integration
530 | 
531 | You can integrate the Falcon MCP server with your editor or AI assistant. Here are configuration examples for popular MCP clients:
532 | 
533 | ### Using `uvx` (recommended)
534 | 
535 | ```json
536 | {
537 |   "mcpServers": {
538 |     "falcon-mcp": {
539 |       "command": "uvx",
540 |       "args": [
541 |         "--env-file",
542 |         "/path/to/.env",
543 |         "falcon-mcp"
544 |       ]
545 |     }
546 |   }
547 | }
548 | ```
549 | 
550 | ### With Module Selection
551 | 
552 | ```json
553 | {
554 |   "mcpServers": {
555 |     "falcon-mcp": {
556 |       "command": "uvx",
557 |       "args": [
558 |         "--env-file",
559 |         "/path/to/.env",
560 |         "falcon-mcp",
561 |         "--modules",
562 |         "detections,incidents,intel"
563 |       ]
564 |     }
565 |   }
566 | }
567 | ```
568 | 
569 | ### Using Individual Environment Variables
570 | 
571 | ```json
572 | {
573 |   "mcpServers": {
574 |     "falcon-mcp": {
575 |       "command": "uvx",
576 |       "args": ["falcon-mcp"],
577 |       "env": {
578 |         "FALCON_CLIENT_ID": "your-client-id",
579 |         "FALCON_CLIENT_SECRET": "your-client-secret",
580 |         "FALCON_BASE_URL": "https://api.crowdstrike.com"
581 |       }
582 |     }
583 |   }
584 | }
585 | ```
586 | 
587 | ### Docker Version
588 | 
589 | ```json
590 | {
591 |   "mcpServers": {
592 |     "falcon-mcp-docker": {
593 |       "command": "docker",
594 |       "args": [
595 |         "run",
596 |         "-i",
597 |         "--rm",
598 |         "--env-file",
599 |         "/full/path/to/.env",
600 |         "quay.io/crowdstrike/falcon-mcp:latest"
601 |       ]
602 |     }
603 |   }
604 | }
605 | ```
606 | 
607 | > [!NOTE]
608 | > The `-i` flag is required when using the default stdio transport.
609 | 
610 | ## Additional Deployment Options
611 | 
612 | ### Amazon Bedrock AgentCore
613 | 
614 | To deploy the MCP Server as a tool in Amazon Bedrock AgentCore, please refer to the [following document](./docs/deployment/amazon_bedrock_agentcore.md).
615 | 
616 | ### Google Cloud (Cloud Run and Vertex AI)
617 | 
618 | To deploy the MCP server as an agent within Cloud Run or Vertex AI Agent Engine (including for registration within Agentspace), refer to the [Google ADK example](./examples/adk/README.md).
619 | 
620 | ## Contributing
621 | 
622 | ### Getting Started for Contributors
623 | 
624 | 1. Clone the repository:
625 | 
626 |    ```bash
627 |    git clone https://github.com/CrowdStrike/falcon-mcp.git
628 |    cd falcon-mcp
629 |    ```
630 | 
631 | 2. Install in development mode:
632 | 
633 |    ```bash
634 |    # Create .venv and install dependencies
635 |    uv sync --all-extras
636 | 
637 |    # Activate the venv
638 |    source .venv/bin/activate
639 |    ```
640 | 
641 | > [!IMPORTANT]
642 | > This project uses [Conventional Commits](https://www.conventionalcommits.org/) for automated releases and semantic versioning. Please follow the commit message format outlined in our [Contributing Guide](docs/CONTRIBUTING.md) when submitting changes.
643 | 
644 | ### Running Tests
645 | 
646 | ```bash
647 | # Run all tests
648 | pytest
649 | 
650 | # Run end-to-end tests
651 | pytest --run-e2e tests/e2e/
652 | 
653 | # Run end-to-end tests with verbose output (note: -s is required to see output)
654 | pytest --run-e2e -v -s tests/e2e/
655 | ```
656 | 
657 | > **Note**: The `-s` flag is required to see detailed output from E2E tests.
658 | 
659 | ### Developer Documentation
660 | 
661 | - [Module Development Guide](docs/module_development.md): Instructions for implementing new modules
662 | - [Resource Development Guide](docs/resource_development.md): Instructions for implementing resources
663 | - [End-to-End Testing Guide](docs/e2e_testing.md): Guide for running and understanding E2E tests
664 | 
665 | ## License
666 | 
667 | This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
668 | 
669 | ## Support
670 | 
671 | This is a community-driven, open source project. While it is not an official CrowdStroke product, it is actively maintained by CrowdStrike and supported in collaboration with the open source developer community.
672 | 
673 | For more information, please see our [SUPPORT](SUPPORT.md) file.
674 | 
```

--------------------------------------------------------------------------------
/docs/SECURITY.md:
--------------------------------------------------------------------------------

```markdown
 1 | # Security
 2 | 
 3 | This document outlines the security policy and procedures for projects under the CrowdStrike organization.
 4 | 
 5 | ## Supported Versions
 6 | 
 7 | For each project, we aim to release security vulnerability patches for the most recent version at an accelerated cadence. Please refer to the specific project repository for details on supported versions.
 8 | 
 9 | ## Reporting a Potential Security Vulnerability
10 | 
11 | We encourage the reporting of security-related vulnerabilities. To report a suspected vulnerability in any CrowdStrike project, please use one of the following methods:
12 | 
13 | + Submitting an __issue__ to the relevant project repository.
14 | + Submitting a __pull request__ with a potential fix to the relevant project repository.
15 | + Sending an email to [email protected]__.
16 | 
17 | ## Disclosure and Mitigation Process
18 | 
19 | Upon receiving a security bug report, the issue will be triaged and assigned to a project maintainer. The maintainer will coordinate the fix and release process, typically involving:
20 | 
21 | + Initial communication with the reporter to acknowledge receipt and provide status updates.
22 | + Confirmation of the issue and determination of affected versions.
23 | + Codebase audit to identify similar potential vulnerabilities.
24 | + Preparation of patches for all supported versions.
25 |   + Patches will be submitted through pull requests, flagged as security fixes.
26 |   + After merging and successful post-merge testing, patches will be released accordingly.
27 | 
28 | ## Comments and Suggestions
29 | 
30 | We welcome suggestions for improving this process. Please share your ideas by creating an issue in the relevant project repository.
31 | 
```

--------------------------------------------------------------------------------
/docs/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------

```markdown
  1 | # CrowdStrike Community Code of Conduct
  2 | 
  3 | ## Our Pledge
  4 | 
  5 | We as members, contributors, and leaders pledge to make participation in our
  6 | community a harassment-free experience for everyone, regardless of age, body
  7 | size, visible or invisible disability, ethnicity, sex characteristics, gender
  8 | identity and expression, level of experience, education, socio-economic status,
  9 | nationality, personal appearance, race, religion, or sexual identity
 10 | and orientation.
 11 | 
 12 | We pledge to act and interact in ways that contribute to an open, welcoming,
 13 | diverse, inclusive, and healthy community.
 14 | 
 15 | ## Our Standards
 16 | 
 17 | Examples of behavior that contributes to a positive environment for our
 18 | community include:
 19 | 
 20 | * Demonstrating empathy and kindness toward other people
 21 | * Being respectful of differing opinions, viewpoints, and experiences
 22 | * Giving and gracefully accepting constructive feedback
 23 | * Accepting responsibility and apologizing to those affected by our mistakes,
 24 |   and learning from the experience
 25 | * Focusing on what is best not just for us as individuals, but for the
 26 |   overall community
 27 | 
 28 | Examples of unacceptable behavior include:
 29 | 
 30 | * The use of sexualized language or imagery, and sexual attention or
 31 |   advances of any kind
 32 | * Trolling, insulting or derogatory comments, and personal or political attacks
 33 | * Public or private harassment
 34 | * Publishing others' private information, such as a physical or email
 35 |   address, without their explicit permission
 36 | * Other conduct which could reasonably be considered inappropriate in a
 37 |   professional setting
 38 | 
 39 | ## Enforcement Responsibilities
 40 | 
 41 | Community leaders are responsible for clarifying and enforcing our standards of
 42 | acceptable behavior and will take appropriate and fair corrective action in
 43 | response to any behavior that they deem inappropriate, threatening, offensive,
 44 | or harmful.
 45 | 
 46 | Community leaders have the right and responsibility to remove, edit, or reject
 47 | comments, commits, code, wiki edits, issues, and other contributions that are
 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation
 49 | decisions when appropriate.
 50 | 
 51 | ## Scope
 52 | 
 53 | This Code of Conduct applies within all community spaces, and also applies when
 54 | an individual is officially representing the community in public spaces.
 55 | Examples of representing our community include using an official e-mail address,
 56 | posting via an official social media account, or acting as an appointed
 57 | representative at an online or offline event.
 58 | 
 59 | ## Enforcement
 60 | 
 61 | Each project has one or more project maintainers. These individuals are
 62 | responsible for enforcing the Code of Conduct within a given project.
 63 | 
 64 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
 65 | reported to the project maintainers at any time. Concerns can also be escalated
 66 | directly to community leaders at:
 67 | 
 68 | * [[email protected]](mailto:[email protected])
 69 | * [Ethics and Compliance Hotline](https://crowdstrike.ethicspoint.com/)
 70 | 
 71 | All complaints will be reviewed and investigated promptly and fairly.
 72 | 
 73 | Community leaders are obligated to respect the privacy and security of the
 74 | reporter of any incident.
 75 | 
 76 | ### Enforcement Guidelines
 77 | 
 78 | Community leaders will follow these Community Impact Guidelines in determining
 79 | the consequences for any action they deem in violation of this Code of Conduct:
 80 | 
 81 | #### 1. Correction
 82 | 
 83 | **Community Impact**: Use of inappropriate language or other behavior deemed
 84 | unprofessional or unwelcome in the community.
 85 | 
 86 | **Consequence**: A private, written warning from community leaders, providing
 87 | clarity around the nature of the violation and an explanation of why the
 88 | behavior was inappropriate. A public apology may be requested.
 89 | 
 90 | #### 2. Warning
 91 | 
 92 | **Community Impact**: A violation through a single incident or series
 93 | of actions.
 94 | 
 95 | **Consequence**: A warning with consequences for continued behavior. No
 96 | interaction with the people involved, including unsolicited interaction with
 97 | those enforcing the Code of Conduct, for a specified period of time. This
 98 | includes avoiding interactions in community spaces as well as external channels
 99 | like social media. Violating these terms may lead to a temporary or
100 | permanent ban.
101 | 
102 | #### 3. Temporary Ban
103 | 
104 | **Community Impact**: A serious violation of community standards, including
105 | sustained inappropriate behavior.
106 | 
107 | **Consequence**: A temporary ban from any sort of interaction or public
108 | communication with the community for a specified period of time. No public or
109 | private interaction with the people involved, including unsolicited interaction
110 | with those enforcing the Code of Conduct, is allowed during this period.
111 | Violating these terms may lead to a permanent ban.
112 | 
113 | #### 4. Permanent Ban
114 | 
115 | **Community Impact**: Demonstrating a pattern of violation of community
116 | standards, including sustained inappropriate behavior,  harassment of an
117 | individual, or aggression toward or disparagement of classes of individuals.
118 | 
119 | **Consequence**: A permanent ban from any sort of public interaction within
120 | the community.
121 | 
122 | ## Attribution
123 | 
124 | This Code of Conduct is adapted from the Contributor Covenant homepage,
125 | version 2.0, available at
126 | [https://www.contributor-covenant.org/version/2/0/code_of_conduct.html](https://www.contributor-covenant.org/version/2/0/code_of_conduct.html).
127 | 
128 | Community Impact Guidelines were inspired by [Mozilla's code of conduct
129 | enforcement ladder](https://github.com/mozilla/diversity).
130 | 
131 | For answers to common questions about this code of conduct, see the FAQ at
132 | [https://www.contributor-covenant.org/faq](https://www.contributor-covenant.org/faq). Translations are available at
133 | [https://www.contributor-covenant.org/translations](https://www.contributor-covenant.org/translations).
134 | 
135 | ## Improving the Code of Conduct
136 | 
137 | Suggestions welcome on how to improve this code of conduct!
138 | 
139 | * Have a suggestion or idea to discuss? Open a discussion
140 | at [https://github.com/CrowdStrike/community/discussions](https://github.com/CrowdStrike/community/discussions)!
141 | 
142 | * Want to submit a pull request with recommended changes? Submit a PR
143 | against [https://github.com/CrowdStrike/community/blob/main/docs/pages/code-of-conduct.md](https://github.com/CrowdStrike/community/blob/main/docs/pages/code-of-conduct.md).
144 | 
```

--------------------------------------------------------------------------------
/docs/CONTRIBUTING.md:
--------------------------------------------------------------------------------

```markdown
  1 | # Welcome
  2 | 
  3 | Welcome and thank you for your interest in contributing to a CrowdStrike project! We recognize contributing to a project is no small feat! The guidance here aspires to help onboard new community members into how CrowdStrike-led projects tend to operate, and by extension, make the contribution process easier.
  4 | 
  5 | ## How do I make a contribution?
  6 | 
  7 | Never made an open source contribution before? Wondering how contributions work in CrowdStrike projects? Here is a quick rundown!
  8 | 
  9 | 1. Find an issue that you are interested in addressing, or a feature you would like to add. These are often documented in the project repositories themselves, frequently in the `issues` section.
 10 | 
 11 | 1. Fork the repository associated with project to your GitHub account. This means that you will have a copy of the repository under *your-GitHub-username/repository-name*.
 12 | 
 13 |    Guidance on how to fork a repository can be found at [https://docs.github.com/en/github/getting-started-with-github/fork-a-repo#fork-an-example-repository](https://docs.github.com/en/github/getting-started-with-github/fork-a-repo#fork-an-example-repository).
 14 | 
 15 | 1. Clone the repository to your local machine using ``git clone https://github.com/github-username/repository-name.git``.
 16 | 
 17 |     GitHub provides documentation on this process, including screenshots, here:
 18 | [https://docs.github.com/en/github/creating-cloning-and-archiving-repositories/cloning-a-repository#about-cloning-a-repository](https://docs.github.com/en/github/creating-cloning-and-archiving-repositories/cloning-a-repository#about-cloning-a-repository)
 19 | 
 20 | 1. Create a new branch for your changes. This ensures your modifications can be uniquely identified and can help prevent rebasing and history problems. A local development branch can be created by running a command similar to:
 21 | 
 22 |     ``git checkout -b BRANCH-NAME-HERE``
 23 | 
 24 | 1. Make the appropriate changes for the issue you are trying to address or the feature you would like to add.
 25 | 
 26 | 1. Follow [this guide](https://google.github.io/styleguide/pyguide.html#docstrings) for docstrings.
 27 | 
 28 | 1. Run [`ruff`](https://docs.astral.sh/ruff/) to format your code and check for linting issues. This helps maintain consistent code style across the project.
 29 | 
 30 |     ``ruff check . --select I``
 31 |     ``ruff check .``
 32 | 
 33 | 1. Add the file contents of the changed files to the "snapshot" git uses to manage the state of the project (also known as the index). Here is the git command that will add your changes:
 34 | 
 35 |     ``git add insert-paths-of-changed-files-here``
 36 | 
 37 | 1. **Use conventional commits** to store the contents of the index with a descriptive, standardized message. This project uses [Conventional Commits](https://www.conventionalcommits.org/) to enable automated release workflows and maintain proper semantic versioning.
 38 | 
 39 |     **Conventional Commit Format:**
 40 | 
 41 |     ```text
 42 |     <type>[optional scope]: <description>
 43 | 
 44 |     [optional body]
 45 | 
 46 |     [optional footer(s)]
 47 |     ```
 48 | 
 49 |     **Common Types Used in This Project:**
 50 |     - `feat:` - A new feature (triggers minor version bump)
 51 |     - `fix:` - A bug fix (triggers patch version bump)
 52 |     - `docs:` - Documentation only changes
 53 |     - `refactor:` - Code changes that neither fix bugs nor add features
 54 |     - `test:` - Adding missing tests or correcting existing tests
 55 |     - `chore:` - Changes to build process, auxiliary tools, or maintenance
 56 | 
 57 |     **Examples with Good Scoping (Recommended):**
 58 | 
 59 |     ```bash
 60 |     # Module changes with specific scopes (preferred)
 61 |     git commit -m "feat(modules/cloud): add list kubernetes clusters tool"
 62 |     git commit -m "feat(modules/hosts): add list devices tool"
 63 |     git commit -m "fix(modules/detections): resolve authentication error"
 64 | 
 65 |     # Resource changes
 66 |     git commit -m "refactor(resources): reword FQL guide in cloud resource"
 67 |     git commit -m "feat(resources): add FQL guide for hosts module"
 68 | 
 69 |     # Documentation changes with scope
 70 |     git commit -m "docs(contributing): update conventional commits guidance"
 71 |     git commit -m "docs(modules): enhance module development guide"
 72 | 
 73 |     # Infrastructure changes
 74 |     git commit -m "feat(ci): add automated testing workflow"
 75 |     git commit -m "chore(docker): update container configurations"
 76 |     ```
 77 | 
 78 |     **How Scoped Commits Improve Changelogs:**
 79 | 
 80 |     The above commits would generate organized changelog entries like:
 81 | 
 82 |     ```markdown
 83 |     # Features
 84 |     - modules/cloud: add list kubernetes clusters tool
 85 |     - modules/hosts: add list devices tool
 86 |     - resources: add FQL guide for hosts module
 87 |     - ci: add automated testing workflow
 88 | 
 89 |     # Bug Fixes
 90 |     - modules/detections: resolve authentication error
 91 | 
 92 |     # Refactors
 93 |     - resources: reword FQL guide in cloud resource
 94 | 
 95 |     # Documentation
 96 |     - contributing: update conventional commits guidance
 97 |     - modules: enhance module development guide
 98 |     ```
 99 | 
100 |     **Basic Examples (Less Preferred but Acceptable):**
101 | 
102 |     ```bash
103 |     # General examples without specific scopes
104 |     git commit -m "feat: add new functionality"
105 |     git commit -m "fix: resolve issue in application"
106 |     git commit -m "docs: update documentation"
107 |     ```
108 | 
109 |     **Breaking Changes:**
110 |     For breaking changes, add `!` after the type or include `BREAKING CHANGE:` in the footer:
111 | 
112 |     ```bash
113 |     git commit -m "feat!: change API authentication method"
114 |     # or
115 |     git commit -m "feat: update authentication system
116 | 
117 |     BREAKING CHANGE: API key format has changed"
118 |     ```
119 | 
120 |     **Why Conventional Commits?**
121 |     - **Automated Releases**: Enables automatic semantic version bumps and changelog generation
122 |     - **Clear History**: Makes it easy to understand what type of changes were made
123 |     - **Consistent Format**: Standardizes commit messages across all contributors
124 | 
125 |     For more details, see the [Conventional Commits specification](https://www.conventionalcommits.org/).
126 | 
127 | 1. Push your local changes back to your account on github.com:
128 | 
129 |     ``git push origin BRANCH-NAME-HERE``
130 | 
131 | 1. Submit a pull request to the upstream project. Documentation on this process, including screen shots, can be found at [https://docs.github.com/en/github/collaborating-with-issues-and-pull-requests/creating-a-pull-request-from-a-fork](https://docs.github.com/en/github/collaborating-with-issues-and-pull-requests/creating-a-pull-request-from-a-fork)
132 | 
133 | 1. Once submitted, a maintainer will review your pull request. They may ask for additional changes, or clarification, so keep an eye out for communication! GitHub automatically sends an email to your email address whenever someone comments on your pull request.
134 | 
135 | 1. While not all pull requests may be merged, celebrate your contribution whether or not your pull request is merged! All changes move the project forward, and we thank you for helping the community!
136 | 
137 | ### Rebase Early, Rebase Often
138 | 
139 | Projects tend to move at a fast pace, which means your fork may become behind upstream. Keeping your local fork in sync with upstream is called `rebasing`. This ensures your local copy is frequently refreshed with the latest changes from the community.
140 | 
141 | Frequenty rebasing is *strongly* encouraged. If your local copy falls to far behind, you may encounter merge conflicts when submitting pull request. If this happens, you will have to triage (often by hand!) the differences in your local repository versus the changes upstream.
142 | 
143 | - Documentation on how to sync/rebase your fork can be found at [https://docs.github.com/en/github/collaborating-with-issues-and-pull-requests/syncing-a-fork](https://docs.github.com/en/github/collaborating-with-issues-and-pull-requests/syncing-a-fork)
144 | 
145 | - For handling merge conflicts, refer to [https://opensource.com/article/20/4/git-merge-conflict](https://opensource.com/article/20/4/git-merge-conflict)
146 | 
147 | ## Where can I go for help?
148 | 
149 | ### Submitting a Ticket
150 | 
151 | General questions relating a project should be opened in that projects repository. Examples would be troubleshooting errors, submitting bug reports, or asking a general question/request for clarification.
152 | 
153 | If your question is of the broader CrowdStrike community, please [open a community discussion](https://github.com/CrowdStrike/community/discussions/new).
154 | 
155 | ### Submitting a New Project Idea
156 | 
157 |  If you do not see a project, repository, or would like the community to consider working on a specific piece of technology, please [open a community ticket](https://github.com/CrowdStrike/community/issues/new).
158 | 
159 | ## What does the Code of Conduct mean for me?
160 | 
161 | Our community Code of Conduct helps us establish community norms and how they'll be enforced. Community members are expected to treat each other with respect and courtesy regardless of their identity.
162 | 
163 | CrowdStrike open source project maintainers are responsible for enforcing the CrowdStrike Code of Conduct within the project, issues may be raised directly to the maintainer should the need arise.
164 | 
165 | ### Escalation Path
166 | 
167 | If you do not feel your concern has been addressed, if you are unable to communicate your concern with project maintainers, or if you feel the situation warrants, please escalate to:
168 | 
169 | - [[email protected]](mailto:[email protected])
170 | - [Ethics and Compliance Hotline](https://crowdstrike.ethicspoint.com/)
171 | 
```

--------------------------------------------------------------------------------
/falcon_mcp/resources/__init__.py:
--------------------------------------------------------------------------------

```python
1 | 
```

--------------------------------------------------------------------------------
/tests/e2e/__init__.py:
--------------------------------------------------------------------------------

```python
1 | 
```

--------------------------------------------------------------------------------
/tests/e2e/utils/__init__.py:
--------------------------------------------------------------------------------

```python
1 | """E2E test utils."""
2 | 
```

--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/config.yml:
--------------------------------------------------------------------------------

```yaml
1 | blank_issues_enabled: false
2 | 
```

--------------------------------------------------------------------------------
/examples/adk/falcon_agent/__init__.py:
--------------------------------------------------------------------------------

```python
1 | from . import agent as agent
2 | 
```

--------------------------------------------------------------------------------
/tests/e2e/modules/__init__.py:
--------------------------------------------------------------------------------

```python
1 | """E2E tests for modules."""
2 | 
```

--------------------------------------------------------------------------------
/falcon_mcp/__init__.py:
--------------------------------------------------------------------------------

```python
1 | """
2 | Falcon MCP Server package
3 | """
4 | 
```

--------------------------------------------------------------------------------
/tests/__init__.py:
--------------------------------------------------------------------------------

```python
1 | """
2 | Test package for Falcon MCP Server
3 | """
4 | 
```

--------------------------------------------------------------------------------
/tests/common/__init__.py:
--------------------------------------------------------------------------------

```python
1 | """
2 | Common tests package for Falcon MCP Server
3 | """
4 | 
```

--------------------------------------------------------------------------------
/tests/modules/__init__.py:
--------------------------------------------------------------------------------

```python
1 | """
2 | Module tests package for Falcon MCP Server
3 | """
4 | 
```

--------------------------------------------------------------------------------
/falcon_mcp/common/__init__.py:
--------------------------------------------------------------------------------

```python
1 | """
2 | Common utilities package for Falcon MCP Server
3 | """
4 | 
```

--------------------------------------------------------------------------------
/examples/adk/falcon_agent/requirements.txt:
--------------------------------------------------------------------------------

```
1 | google-adk[eval]==1.8.0
2 | falcon-mcp
3 | google-cloud-aiplatform[agent_engines]==1.105.0
4 | cloudpickle==3.1.1
5 | pydantic==2.11.7
6 | 
```

--------------------------------------------------------------------------------
/docs/deployment/google_cloud.md:
--------------------------------------------------------------------------------

```markdown
1 | # Deploying to Google Cloud (Cloud Run, Vertex AI Agent Engine, and Agentspace)
2 | 
3 | Refer to the [Google ADK example](../../examples/adk/README.md).
4 | 
```

--------------------------------------------------------------------------------
/falcon_mcp/modules/__init__.py:
--------------------------------------------------------------------------------

```python
1 | # ruff: noqa: F401
2 | """
3 | Modules package for Falcon MCP Server
4 | 
5 | Modules are automatically discovered by the registry system via dynamic import scanning.
6 | No manual imports are required - the registry uses pkgutil.iter_modules() and 
7 | importlib.import_module() to find and load all *Module classes at runtime.
8 | """
9 | 
```

--------------------------------------------------------------------------------
/examples/mcp_config.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |   "servers": [
 3 |     {
 4 |       "name": "falcon-stdio",
 5 |       "transport": {
 6 |         "type": "stdio",
 7 |         "command": "python -m falcon_mcp.server"
 8 |       }
 9 |     },
10 |     {
11 |       "name": "falcon-sse",
12 |       "transport": {
13 |         "type": "sse",
14 |         "url": "http://127.0.0.1:8000/sse"
15 |       }
16 |     },
17 |     {
18 |       "name": "falcon-streamable-http",
19 |       "transport": {
20 |         "type": "streamable-http",
21 |         "url": "http://127.0.0.1:8000/mcp"
22 |       }
23 |     }
24 |   ]
25 | }
26 | 
```

--------------------------------------------------------------------------------
/.github/workflows/markdown-lint.yml:
--------------------------------------------------------------------------------

```yaml
 1 | name: Markdown Lint
 2 | 
 3 | on:
 4 |   push:
 5 |     branches: [ main ]
 6 |     paths:
 7 |       - '**.md'
 8 |   pull_request:
 9 |     branches: [ main ]
10 |     paths:
11 |       - '**.md'
12 | 
13 | permissions:
14 |   contents: read
15 | 
16 | jobs:
17 |   markdown-lint:
18 |     runs-on: ubuntu-latest
19 |     steps:
20 |     - name: Harden the runner (Audit all outbound calls)
21 |       uses: step-security/harden-runner@f4a75cfd619ee5ce8d5b864b0d183aff3c69b55a # v2.13.1
22 |       with:
23 |         egress-policy: audit
24 | 
25 |     - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8
26 |     - name: Lint Markdown files
27 |       uses: DavidAnson/markdownlint-cli2-action@992badcdf24e3b8eb7e87ff9287fe931bcb00c6e
28 |       with:
29 |         config: '.markdownlint.json'
30 |         globs: |
31 |           README.md
32 |           docs/**/*.md
33 | 
```

--------------------------------------------------------------------------------
/examples/basic_usage.py:
--------------------------------------------------------------------------------

```python
 1 | #!/usr/bin/env python3
 2 | """
 3 | Basic usage example for Falcon MCP Server.
 4 | 
 5 | This script demonstrates how to initialize and run the Falcon MCP server.
 6 | """
 7 | 
 8 | import os
 9 | 
10 | from dotenv import load_dotenv
11 | 
12 | from falcon_mcp.server import FalconMCPServer
13 | 
14 | 
15 | def main():
16 |     """Run the Falcon MCP server with default settings."""
17 |     # Load environment variables from .env file
18 |     load_dotenv()
19 | 
20 |     # Create and run the server with stdio transport
21 |     server = FalconMCPServer(
22 |         # You can override the base URL if needed
23 |         # base_url="https://api.us-2.crowdstrike.com",
24 |         debug=os.environ.get("DEBUG", "").lower() == "true",
25 |     )
26 | 
27 |     # Run the server with stdio transport (default)
28 |     server.run()
29 | 
30 | 
31 | if __name__ == "__main__":
32 |     main()
33 | 
```

--------------------------------------------------------------------------------
/examples/sse_usage.py:
--------------------------------------------------------------------------------

```python
 1 | #!/usr/bin/env python3
 2 | """
 3 | SSE transport example for Falcon MCP Server.
 4 | 
 5 | This script demonstrates how to initialize and run the Falcon MCP server with SSE transport.
 6 | """
 7 | 
 8 | import os
 9 | 
10 | from dotenv import load_dotenv
11 | 
12 | from falcon_mcp.server import FalconMCPServer
13 | 
14 | 
15 | def main():
16 |     """Run the Falcon MCP server with SSE transport."""
17 |     # Load environment variables from .env file
18 |     load_dotenv()
19 | 
20 |     # Create and run the server with SSE transport
21 |     server = FalconMCPServer(
22 |         # You can override the base URL if needed
23 |         # base_url="https://api.us-2.crowdstrike.com",
24 |         debug=os.environ.get("DEBUG", "").lower() == "true",
25 |     )
26 | 
27 |     # Run the server with SSE transport
28 |     server.run("sse")
29 | 
30 | 
31 | if __name__ == "__main__":
32 |     main()
33 | 
```

--------------------------------------------------------------------------------
/examples/adk/falcon_agent/env.properties:
--------------------------------------------------------------------------------

```
 1 | # General Agent Configuration
 2 | GOOGLE_GENAI_USE_VERTEXAI=False
 3 | GOOGLE_API_KEY=NOT_SET
 4 | GOOGLE_MODEL=NOT_SET
 5 | FALCON_CLIENT_ID=NOT_SET
 6 | FALCON_CLIENT_SECRET=NOT_SET
 7 | FALCON_BASE_URL=NOT_SET
 8 | # Should be single line and only use single quotes.
 9 | FALCON_AGENT_PROMPT=NOT_SET
10 | 
11 | # Cloud Run Specific
12 | PROJECT_ID=NOT_SET
13 | REGION=NOT_SET
14 | 
15 | # Agent Engine Specific - Should be using format - gs://your-agent-engine-staging-bucket
16 | AGENT_ENGINE_STAGING_BUCKET=NOT_SET
17 | 
18 | # Agentspace Specific
19 | PROJECT_NUMBER=NOT_SET
20 | # only 'global' location supported
21 | AGENT_LOCATION=NOT_SET
22 | REASONING_ENGINE_NUMBER=NOT_SET
23 | AGENT_SPACE_APP_NAME=NOT_SET
24 | 
25 | # Other variables
26 | # default -1 means send all user conversations to the LLM
27 | # recommended value - 5. Please check the documentation for more.
28 | MAX_PREV_USER_INTERACTIONS=-1
29 | 
```

--------------------------------------------------------------------------------
/SUPPORT.md:
--------------------------------------------------------------------------------

```markdown
 1 | # Support
 2 | 
 3 | This is a community-driven, open source project. While it is not an official CrowdStrike product, it is actively maintained by CrowdStrike and supported in collaboration with the open source developer community.
 4 | 
 5 | ## How to Request Help
 6 | 
 7 | Support for this project is primarily provided through this GitHub repository. When you submit issues publicly, you contribute to our shared knowledge base, improve self-service options for others, and often receive faster solutions.
 8 | 
 9 | To request assistance, please open a GitHub Issue. This is the appropriate channel for questions, bug reports, feature requests, enhancement suggestions, and documentation updates.
10 | 
11 | For CrowdStrike customers who prefer direct engagement, you may alternatively contact CrowdStrike Technical Support through your established support channels.
12 | 
```

--------------------------------------------------------------------------------
/tests/conftest.py:
--------------------------------------------------------------------------------

```python
 1 | """
 2 | Pytest configuration file for the tests.
 3 | """
 4 | 
 5 | import pytest
 6 | 
 7 | 
 8 | def pytest_addoption(parser):
 9 |     """
10 |     Add the --run-e2e option to pytest.
11 |     """
12 |     parser.addoption(
13 |         "--run-e2e",
14 |         action="store_true",
15 |         default=False,
16 |         help="run e2e tests",
17 |     )
18 | 
19 | 
20 | def pytest_configure(config):
21 |     """
22 |     Register the e2e marker.
23 |     """
24 |     config.addinivalue_line("markers", "e2e: mark test as e2e to run")
25 | 
26 | 
27 | def pytest_collection_modifyitems(config, items):
28 |     """
29 |     Skip e2e tests if --run-e2e is not given.
30 |     """
31 |     if config.getoption("--run-e2e"):
32 |         return
33 |     skip_e2e = pytest.mark.skip(reason="need --run-e2e option to run")
34 |     for item in items:
35 |         if "e2e" in item.keywords:
36 |             item.add_marker(skip_e2e)
37 | 
38 | 
39 | @pytest.fixture
40 | def verbosity_level(request):
41 |     """Return the verbosity level from pytest config."""
42 |     return request.config.option.verbose
43 | 
```

--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------

```yaml
 1 | version: 2
 2 | updates:
 3 |   # Python dependencies configuration (uv ecosystem)
 4 |   - package-ecosystem: "uv"
 5 |     directory: "/"
 6 |     schedule:
 7 |       interval: "daily"
 8 |     commit-message:
 9 |       prefix: "deps(python):"
10 |     labels:
11 |       - "dependencies"
12 |       - "python"
13 | 
14 |   # Backup configuration (pip ecosystem)
15 |   # - package-ecosystem: "pip"
16 |   #   directory: "/"
17 |   #   schedule:
18 |   #     interval: "daily"
19 |   #   commit-message:
20 |   #     prefix: "deps(python):"
21 |   #   labels:
22 |   #     - "dependencies"
23 |   #     - "python"
24 | 
25 |   # GitHub Actions configuration
26 |   - package-ecosystem: "github-actions"
27 |     directory: "/"
28 |     schedule:
29 |       interval: "weekly"
30 |     commit-message:
31 |       prefix: "deps(actions):"
32 |     labels:
33 |       - "dependencies"
34 |       - "github-actions"
35 | 
36 |   # Docker configuration
37 |   - package-ecosystem: "docker"
38 |     directory: "/"
39 |     schedule:
40 |       interval: "weekly"
41 |     commit-message:
42 |       prefix: "deps(docker):"
43 |     labels:
44 |       - "dependencies"
45 |       - "docker"
46 | 
```

--------------------------------------------------------------------------------
/.github/workflows/python-lint.yml:
--------------------------------------------------------------------------------

```yaml
 1 | name: Python Lint
 2 | 
 3 | on:
 4 |   push:
 5 |     branches: [main]
 6 |   pull_request:
 7 |     branches: [main]
 8 | 
 9 | permissions:
10 |   contents: read
11 | 
12 | jobs:
13 |   lint:
14 |     runs-on: ubuntu-latest
15 |     steps:
16 |       - name: Harden the runner (Audit all outbound calls)
17 |         uses: step-security/harden-runner@f4a75cfd619ee5ce8d5b864b0d183aff3c69b55a # v2.13.1
18 |         with:
19 |           egress-policy: audit
20 | 
21 |       - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8
22 |       - name: Set up Python
23 |         uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065
24 |         with:
25 |           python-version: "3.11"
26 |           cache: "pip"
27 |       - name: Install the latest version of uv
28 |         uses: astral-sh/setup-uv@39eb6c9dde236bbc368681611e63120a6eb4afac
29 |         with:
30 |           version: "latest"
31 |           activate-environment: true
32 | 
33 |       - name: Install dependencies
34 |         run: |
35 |           uv sync --extra dev
36 | 
37 |       - name: Lint imports
38 |         run: |
39 |           ruff check . --select I
40 | 
41 |       - name: Lint with Ruff
42 |         run: |
43 |           ruff check .
44 | 
```

--------------------------------------------------------------------------------
/.github/workflows/python-test.yml:
--------------------------------------------------------------------------------

```yaml
 1 | name: Python Tests
 2 | 
 3 | on:
 4 |   push:
 5 |     branches: [main]
 6 |   pull_request:
 7 |     branches: [main]
 8 | 
 9 | permissions:
10 |   contents: read
11 | 
12 | jobs:
13 |   test:
14 |     runs-on: ubuntu-latest
15 |     strategy:
16 |       matrix:
17 |         python-version: ["3.11", "3.12"]
18 | 
19 |     steps:
20 |       - name: Harden the runner (Audit all outbound calls)
21 |         uses: step-security/harden-runner@f4a75cfd619ee5ce8d5b864b0d183aff3c69b55a # v2.13.1
22 |         with:
23 |           egress-policy: audit
24 | 
25 |       - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8
26 |       - name: Set up Python ${{ matrix.python-version }}
27 |         uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065
28 |         with:
29 |           python-version: ${{ matrix.python-version }}
30 |           cache: "pip"
31 |       - name: Install the latest version of uv
32 |         uses: astral-sh/setup-uv@39eb6c9dde236bbc368681611e63120a6eb4afac
33 |         with:
34 |           version: "latest"
35 |           activate-environment: true
36 | 
37 |       - name: Install dependencies
38 |         run: |
39 |           uv sync --extra dev
40 | 
41 |       - name: Test with pytest
42 |         run: |
43 |           pytest
44 | 
```

--------------------------------------------------------------------------------
/falcon_mcp/common/logging.py:
--------------------------------------------------------------------------------

```python
 1 | """
 2 | Logging configuration for Falcon MCP Server
 3 | 
 4 | This module provides logging utilities for the Falcon MCP server.
 5 | """
 6 | 
 7 | import logging
 8 | import sys
 9 | from typing import Optional
10 | 
11 | 
12 | def configure_logging(debug: bool = False, name: str = "falcon_mcp") -> logging.Logger:
13 |     """Configure logging for the Falcon MCP server.
14 | 
15 |     Args:
16 |         debug: Enable debug logging
17 |         name: Logger name
18 | 
19 |     Returns:
20 |         logging.Logger: Configured logger
21 |     """
22 |     log_level = logging.DEBUG if debug else logging.INFO
23 | 
24 |     # Configure root logger
25 |     logging.basicConfig(
26 |         level=log_level,
27 |         format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
28 |         handlers=[logging.StreamHandler(sys.stdout)],
29 |     )
30 | 
31 |     # Set third-party loggers to a higher level to reduce noise
32 |     logging.getLogger("urllib3").setLevel(logging.WARNING)
33 |     logging.getLogger("requests").setLevel(logging.WARNING)
34 | 
35 |     # Get and return the logger for this application
36 |     logger = logging.getLogger(name)
37 |     logger.setLevel(log_level)
38 | 
39 |     return logger
40 | 
41 | 
42 | def get_logger(name: Optional[str] = None) -> logging.Logger:
43 |     """Get a logger with the specified name.
44 | 
45 |     Args:
46 |         name: Logger name (defaults to "falcon_mcp")
47 | 
48 |     Returns:
49 |         logging.Logger: Logger instance
50 |     """
51 |     logger_name = name if name else "falcon_mcp"
52 |     return logging.getLogger(logger_name)
53 | 
```

--------------------------------------------------------------------------------
/examples/streamable_http_usage.py:
--------------------------------------------------------------------------------

```python
 1 | #!/usr/bin/env python3
 2 | """
 3 | Streamable HTTP transport example for Falcon MCP Server.
 4 | 
 5 | This script demonstrates how to initialize and run the Falcon MCP server
 6 | with streamable-http transport for custom integrations and web-based deployments.
 7 | """
 8 | 
 9 | import os
10 | 
11 | from dotenv import load_dotenv
12 | 
13 | from falcon_mcp.server import FalconMCPServer
14 | 
15 | 
16 | def main():
17 |     """Run the Falcon MCP server with streamable-http transport."""
18 |     # Load environment variables from .env file
19 |     load_dotenv()
20 | 
21 |     # Create and run the server with streamable-http transport
22 |     server = FalconMCPServer(
23 |         # You can override the base URL if needed
24 |         # base_url="https://api.us-2.crowdstrike.com",
25 |         debug=os.environ.get("DEBUG", "").lower() == "true",
26 |     )
27 | 
28 |     # Example 1: Run with default settings (port 8000, localhost)
29 |     print("Example 1: Default streamable-http configuration")
30 |     print("  - Host: 127.0.0.1 (localhost only)")
31 |     print("  - Port: 8000")
32 |     print("  - Path: /mcp")
33 |     print("  - URL: http://127.0.0.1:8000/mcp")
34 |     print()
35 | 
36 |     # Uncomment to run with defaults:
37 |     # server.run("streamable-http")
38 | 
39 |     # Example 2: Custom configuration
40 |     print("Example 2: Custom configuration")
41 |     print("  - Host: 0.0.0.0 (external access)")
42 |     print("  - Port: 8080")
43 |     print("  - URL: http://0.0.0.0:8080/mcp")
44 |     print()
45 | 
46 |     # Run with custom requirements
47 |     server.run("streamable-http", host="0.0.0.0", port=8080)
48 | 
49 | 
50 | if __name__ == "__main__":
51 |     main()
52 | 
```

--------------------------------------------------------------------------------
/pyproject.toml:
--------------------------------------------------------------------------------

```toml
 1 | [build-system]
 2 | requires = ["setuptools>=42", "wheel"]
 3 | build-backend = "setuptools.build_meta"
 4 | 
 5 | [project]
 6 | name = "falcon-mcp"
 7 | version = "0.3.0"
 8 | description = "CrowdStrike Falcon MCP Server"
 9 | readme = "README.md"
10 | requires-python = ">=3.11"
11 | license = {text = "MIT"}
12 | authors = [
13 |     {name = "CrowdStrike", email = "[email protected]"}
14 | ]
15 | classifiers = [
16 |     "Development Status :: 3 - Alpha",
17 |     "Intended Audience :: Developers",
18 |     "License :: OSI Approved :: MIT License",
19 |     "Programming Language :: Python :: 3",
20 |     "Programming Language :: Python :: 3.11",
21 |     "Programming Language :: Python :: 3.12",
22 |     "Programming Language :: Python :: 3.13",
23 | ]
24 | dependencies = [
25 |     "crowdstrike-falconpy>=1.3.0",
26 |     "mcp>=1.12.1,<2.0.0",
27 |     "python-dotenv>=1.1.1",
28 | ]
29 | 
30 | [project.optional-dependencies]
31 | dev = [
32 |     "pytest>=7.0.0",
33 |     "pytest-asyncio>=0.21.0",
34 |     "mypy>=1.0.0",
35 |     "langchain-openai>=0.3.28",
36 |     "mcp-use[search]>=1.3.7",
37 |     "ruff>=0.12.5",
38 |     "black>=23.0.0",
39 | ]
40 | 
41 | [project.scripts]
42 | falcon-mcp = "falcon_mcp.server:main"
43 | 
44 | [tool.black]
45 | line-length = 100
46 | target-version = ["py311"]
47 | 
48 | [tool.mypy]
49 | python_version = "3.11"
50 | warn_return_any = true
51 | warn_unused_configs = true
52 | disallow_untyped_defs = true
53 | disallow_incomplete_defs = true
54 | 
55 | [tool.ruff]
56 | target-version = "py311"
57 | line-length = 100
58 | 
59 | [tool.pytest.ini_options]
60 | testpaths = ["tests"]
61 | asyncio_mode = "auto"
62 | filterwarnings = [
63 |     "ignore::DeprecationWarning:websockets.*:",
64 |     "ignore::DeprecationWarning:uvicorn.protocols.websockets.*:",
65 |     "ignore::pydantic.PydanticDeprecatedSince20:langchain_core.*:"
66 | ]
67 | 
```

--------------------------------------------------------------------------------
/tests/common/test_api_scopes.py:
--------------------------------------------------------------------------------

```python
 1 | """
 2 | Tests for the API scope utilities.
 3 | """
 4 | 
 5 | import unittest
 6 | 
 7 | from falcon_mcp.common.api_scopes import API_SCOPE_REQUIREMENTS, get_required_scopes
 8 | 
 9 | 
10 | class TestApiScopes(unittest.TestCase):
11 |     """Test cases for the API scope utilities."""
12 | 
13 |     def test_api_scope_requirements_structure(self):
14 |         """Test API_SCOPE_REQUIREMENTS dictionary structure."""
15 |         # Verify it's a dictionary
16 |         self.assertIsInstance(API_SCOPE_REQUIREMENTS, dict)
17 | 
18 |         # Verify it has entries
19 |         self.assertGreater(len(API_SCOPE_REQUIREMENTS), 0)
20 | 
21 |         # Verify structure of entries (keys are strings, values are lists of strings)
22 |         for operation, scopes in API_SCOPE_REQUIREMENTS.items():
23 |             self.assertIsInstance(operation, str)
24 |             self.assertIsInstance(scopes, list)
25 |             for scope in scopes:
26 |                 self.assertIsInstance(scope, str)
27 | 
28 |     def test_get_required_scopes(self):
29 |         """Test get_required_scopes function."""
30 |         # Test with known operations
31 |         self.assertEqual(get_required_scopes("GetQueriesAlertsV2"), ["Alerts:read"])
32 |         self.assertEqual(get_required_scopes("PostEntitiesAlertsV2"), ["Alerts:read"])
33 |         self.assertEqual(get_required_scopes("QueryIncidents"), ["Incidents:read"])
34 | 
35 |         # Test with unknown operation
36 |         self.assertEqual(get_required_scopes("UnknownOperation"), [])
37 | 
38 |         # Test with empty string
39 |         self.assertEqual(get_required_scopes(""), [])
40 | 
41 |         # Test with None (should handle gracefully)
42 |         self.assertEqual(get_required_scopes(None), [])
43 | 
44 | 
45 | if __name__ == "__main__":
46 |     unittest.main()
47 | 
```

--------------------------------------------------------------------------------
/.github/workflows/python-test-e2e.yml:
--------------------------------------------------------------------------------

```yaml
 1 | name: Manual E2E Tests
 2 | 
 3 | on:
 4 |   workflow_dispatch:
 5 |     inputs:
 6 |       models:
 7 |         description: "Models to test"
 8 |         required: false
 9 |         default: "gpt-4.1-mini,gpt-4o-mini"
10 |         type: string
11 | 
12 | permissions:
13 |   contents: read
14 |   actions: write
15 | 
16 | jobs:
17 |   test:
18 |     runs-on: ubuntu-latest
19 | 
20 |     steps:
21 |       - name: Harden the runner (Audit all outbound calls)
22 |         uses: step-security/harden-runner@f4a75cfd619ee5ce8d5b864b0d183aff3c69b55a # v2.13.1
23 |         with:
24 |           egress-policy: audit
25 | 
26 |       - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8
27 |       - name: Set up Python 3.12
28 |         uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065
29 |         with:
30 |           python-version: "3.12"
31 |           cache: "pip"
32 |       - name: Install the latest version of uv
33 |         uses: astral-sh/setup-uv@39eb6c9dde236bbc368681611e63120a6eb4afac
34 |         with:
35 |           version: "latest"
36 |           activate-environment: true
37 | 
38 |       - name: Install dependencies
39 |         run: |
40 |           uv sync --extra dev
41 | 
42 |       - name: Test with pytest
43 |         env:
44 |           OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
45 |           MODELS_TO_TEST: ${{ inputs.models }}
46 |           MCP_USE_ANONYMIZED_TELEMETRY: false
47 |         run: |
48 |           pytest --run-e2e
49 | 
50 |       - name: Generate HTML report
51 |         if: always()
52 |         run: |
53 |           python scripts/generate_e2e_report.py
54 | 
55 |       - name: Upload HTML report artifact
56 |         uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02
57 |         if: always()
58 |         with:
59 |           name: e2e-test-report
60 |           path: ./static_test_report.html
61 | 
```

--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------

```dockerfile
 1 | # Use a Python image with uv pre-installed
 2 | # ghcr.io/astral-sh/uv:python3.13-alpine (multi-arch: amd64, arm64)
 3 | FROM ghcr.io/astral-sh/uv@sha256:3ce89663b5309e77087de25ca805c49988f2716cdb2c6469b1dec2764f58b141 AS uv
 4 | 
 5 | # Install the project into `/app`
 6 | WORKDIR /app
 7 | 
 8 | # Enable bytecode compilation
 9 | ENV UV_COMPILE_BYTECODE=1
10 | 
11 | # Copy from the cache instead of linking since it's a mounted volume
12 | ENV UV_LINK_MODE=copy
13 | 
14 | # Generate proper TOML lockfile first
15 | RUN --mount=type=bind,source=pyproject.toml,target=pyproject.toml \
16 |     uv lock
17 | 
18 | # Install the project's dependencies using the lockfile
19 | RUN --mount=type=cache,target=/root/.cache/uv \
20 |     --mount=type=bind,source=pyproject.toml,target=pyproject.toml \
21 |     --mount=type=bind,source=uv.lock,target=uv.lock \
22 |     uv sync --frozen --no-install-project --no-dev --no-editable
23 | 
24 | # Then, add the rest of the project source code and install it
25 | ADD . /app
26 | RUN --mount=type=cache,target=/root/.cache/uv \
27 |     --mount=type=bind,source=uv.lock,target=uv.lock \
28 |     uv sync --frozen --no-dev --no-editable
29 | 
30 | # Remove unnecessary files from the virtual environment before copying
31 | RUN find /app/.venv -name '__pycache__' -type d -exec rm -rf {} + && \
32 |     find /app/.venv -name '*.pyc' -delete && \
33 |     find /app/.venv -name '*.pyo' -delete && \
34 |     echo "Cleaned up .venv"
35 | 
36 | # Final stage
37 | # python:3.13-alpine (multi-arch: amd64, arm64)
38 | FROM python@sha256:9ba6d8cbebf0fb6546ae71f2a1c14f6ffd2fdab83af7fa5669734ef30ad48844
39 | 
40 | # Create a non-root user 'app'
41 | RUN adduser -D -h /home/app -s /bin/sh app
42 | WORKDIR /app
43 | USER app
44 | 
45 | COPY --from=uv --chown=app:app /app/.venv /app/.venv
46 | 
47 | # Place executables in the environment at the front of the path
48 | ENV PATH="/app/.venv/bin:$PATH"
49 | 
50 | ENTRYPOINT ["falcon-mcp"]
51 | 
```

--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------

```yaml
 1 | name: release-please
 2 | on:
 3 |   push:
 4 |     branches: [main]
 5 | 
 6 | permissions:
 7 |   contents: write
 8 |   pull-requests: write
 9 |   issues: write
10 | 
11 | jobs:
12 |   release-please:
13 |     runs-on: ubuntu-latest
14 |     steps:
15 |       - name: Harden the runner (Audit all outbound calls)
16 |         uses: step-security/harden-runner@f4a75cfd619ee5ce8d5b864b0d183aff3c69b55a # v2.13.1
17 |         with:
18 |           egress-policy: audit
19 | 
20 |       - uses: google-github-actions/release-please-action@db8f2c60ee802b3748b512940dde88eabd7b7e01 # v3.7.13
21 |         id: release-please
22 |         with:
23 |           release-type: python
24 |           package-name: falcon-mcp
25 |           pull-request-header: ':rocket: New Release Incoming! :rocket:'
26 |           changelog-types: '[{"type":"feat","section":"Features","hidden":false},{"type":"fix","section":"Bug Fixes","hidden":false},{"type":"refactor","section":"Refactoring","hidden":false},{"type":"chore","section":"Miscellaneous","hidden":true}]'
27 |           # Add any extra files that contain version references
28 |           # extra-files: |
29 |           #   README.md
30 | 
31 |       - name: Checkout
32 |         uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8
33 |         if: ${{ steps.release-please.outputs.release_created }}
34 | 
35 |       - name: Set up Python
36 |         uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065
37 |         with:
38 |           python-version: '3.11'
39 |         if: ${{ steps.release-please.outputs.release_created }}
40 | 
41 |       - name: Build and publish to PyPI
42 |         run: |
43 |           pip install uv
44 |           uv pip install --system build twine
45 |           python -m build
46 |           python -m twine upload dist/* --username __token__ --password ${{ secrets.PYPI_API_TOKEN }}
47 |         if: ${{ steps.release-please.outputs.release_created }}
48 | 
```

--------------------------------------------------------------------------------
/.github/workflows/docker-build-test.yml:
--------------------------------------------------------------------------------

```yaml
 1 | name: Docker Build Test
 2 | 
 3 | on:
 4 |   pull_request:
 5 |     branches: [ main ]
 6 |     paths:
 7 |       - 'Dockerfile'
 8 |       - 'pyproject.toml'
 9 |       - 'uv.lock'
10 |       - 'falcon_mcp/**'
11 |       - '.github/workflows/docker-build-test.yml'
12 | 
13 | permissions:
14 |   contents: read
15 | 
16 | jobs:
17 |   docker-build-test:
18 |     runs-on: ubuntu-latest
19 |     steps:
20 |       - name: Harden the runner (Audit all outbound calls)
21 |         uses: step-security/harden-runner@f4a75cfd619ee5ce8d5b864b0d183aff3c69b55a # v2.13.1
22 |         with:
23 |           egress-policy: audit
24 | 
25 |       - name: Checkout code
26 |         uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8
27 | 
28 |       - name: Set up Docker Buildx
29 |         uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435
30 | 
31 |       - name: Build multi-platform Docker image
32 |         uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83
33 |         with:
34 |           context: .
35 |           platforms: linux/amd64,linux/arm64
36 |           push: false
37 |           cache-from: type=gha
38 |           cache-to: type=gha,mode=max
39 |           tags: |
40 |             falcon-mcp:test
41 |             falcon-mcp:pr-${{ github.event.pull_request.number }}
42 | 
43 |       - name: Test Docker image (amd64)
44 |         run: |
45 |           # Build for local testing (amd64 only for running tests)
46 |           docker buildx build \
47 |             --platform linux/amd64 \
48 |             --load \
49 |             --tag falcon-mcp:test-local \
50 |             .
51 | 
52 |           # Test basic functionality - should show help without errors
53 |           echo "Testing falcon-mcp --help..."
54 |           docker run --rm falcon-mcp:test-local --help
55 | 
56 |           # Test version command | TBD: Add version check
57 |           #echo "Testing falcon-mcp --version..."
58 |           #docker run --rm falcon-mcp:test-local --version || true
59 | 
60 |           echo "✅ Docker image smoke tests passed!"
61 | 
```

--------------------------------------------------------------------------------
/falcon_mcp/registry.py:
--------------------------------------------------------------------------------

```python
 1 | """
 2 | Module registry for Falcon MCP Server
 3 | 
 4 | This module provides a registry of available modules for the Falcon MCP server.
 5 | """
 6 | 
 7 | import importlib
 8 | import os
 9 | import pkgutil
10 | from typing import Dict, List, Type
11 | 
12 | from falcon_mcp.common.logging import get_logger
13 | 
14 | logger = get_logger(__name__)
15 | 
16 | # Forward reference for type hints
17 | # Using string to avoid circular import
18 | MODULE_TYPE = "BaseModule"  # type: ignore
19 | 
20 | 
21 | # This will be populated by the discovery process
22 | AVAILABLE_MODULES: Dict[str, Type[MODULE_TYPE]] = {}
23 | 
24 | 
25 | def discover_modules():
26 |     """Discover available modules by scanning the modules directory."""
27 |     # Get the path to the modules directory
28 |     current_dir = os.path.dirname(__file__)
29 |     modules_path = os.path.join(current_dir, "modules")
30 | 
31 |     # Scan for module files
32 |     for _, name, is_pkg in pkgutil.iter_modules([modules_path]):
33 |         if not is_pkg and name != "base":  # Skip base.py and packages
34 |             # Import the module
35 |             module = importlib.import_module(f"falcon_mcp.modules.{name}")
36 | 
37 |             # Look for *Module classes
38 |             for attr_name in dir(module):
39 |                 if attr_name.endswith("Module") and attr_name != "BaseModule":
40 |                     # Get the class
41 |                     module_class = getattr(module, attr_name)
42 |                     # Register it
43 |                     module_name = attr_name.lower().replace("module", "")
44 |                     AVAILABLE_MODULES[module_name] = module_class
45 |                     logger.debug("Discovered module: %s", module_name)
46 | 
47 | 
48 | def get_available_modules() -> Dict[str, Type[MODULE_TYPE]]:
49 |     """Get available modules dict, discovering if needed (lazy loading).
50 | 
51 |     Returns:
52 |         Dict mapping module names to module classes
53 |     """
54 |     if not AVAILABLE_MODULES:
55 |         logger.debug("No modules discovered yet, performing lazy discovery")
56 |         discover_modules()
57 |     return AVAILABLE_MODULES
58 | 
59 | 
60 | def get_module_names() -> List[str]:
61 |     """Get the names of all registered modules, discovering if needed (lazy loading).
62 | 
63 |     Returns:
64 |         List of module names
65 |     """
66 |     return list(get_available_modules().keys())
67 | 
```

--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/question.yaml:
--------------------------------------------------------------------------------

```yaml
 1 | name: ❓ Question or Discussion
 2 | description: Ask questions or start discussions about falcon-mcp
 3 | title: "[Question]: "
 4 | labels: ["question"]
 5 | 
 6 | body:
 7 |   - type: markdown
 8 |     attributes:
 9 |       value: |
10 |         Thank you for your interest in falcon-mcp! 🚀
11 | 
12 |         We're here to help answer your questions.
13 | 
14 |   - type: checkboxes
15 |     id: checks
16 |     attributes:
17 |       label: Initial Checks
18 |       description: Just making sure you've checked the basics first.
19 |       options:
20 |         - label: I searched existing issues and discussions
21 |           required: true
22 |         - label: I checked the README and documentation
23 |           required: true
24 | 
25 |   - type: dropdown
26 |     id: question_type
27 |     attributes:
28 |       label: Question Type
29 |       description: What kind of help do you need?
30 |       options:
31 |         - Installation and setup
32 |         - Configuration and authentication
33 |         - Module usage and capabilities
34 |         - Integration with MCP clients
35 |         - Performance and troubleshooting
36 |         - Development and contributing
37 |         - API scopes and permissions
38 |         - Best practices
39 |         - Other
40 |     validations:
41 |       required: true
42 | 
43 |   - type: textarea
44 |     id: question
45 |     attributes:
46 |       label: Your Question
47 |       description: |
48 |         What would you like to know?
49 | 
50 |         Please be as specific as possible so we can give you the best help. 🙏
51 |     validations:
52 |       required: true
53 | 
54 |   - type: textarea
55 |     id: context
56 |     attributes:
57 |       label: Context (Optional)
58 |       description: |
59 |         Any additional context that might help us understand your situation?
60 | 
61 |         Your use case, setup, or what you're trying to achieve.
62 |       placeholder: |
63 |         I'm trying to...
64 |         My setup is...
65 |         I've already tried...
66 | 
67 |   - type: textarea
68 |     id: environment
69 |     attributes:
70 |       label: Environment (Optional)
71 |       description: |
72 |         If relevant, please share basic environment information.
73 | 
74 |         **Please don't include API credentials or sensitive information!**
75 |       placeholder: |
76 |         - Installation method: pip/uvx/docker
77 |         - Python version: 3.11
78 |         - Operating System: macOS/Windows/Linux
79 |         - MCP Client: Claude Desktop/Cline/etc.
80 |       render: text
81 | 
```

--------------------------------------------------------------------------------
/tests/modules/utils/test_modules.py:
--------------------------------------------------------------------------------

```python
 1 | import unittest
 2 | from unittest.mock import MagicMock
 3 | 
 4 | from mcp.server import FastMCP
 5 | 
 6 | from falcon_mcp.client import FalconClient
 7 | 
 8 | 
 9 | class TestModules(unittest.TestCase):
10 |     def setup_module(self, module_class):
11 |         """
12 |         Set up test fixtures with the specified module class.
13 | 
14 |         Args:
15 |             module_class: The module class to instantiate
16 |         """
17 |         # Create a mock client
18 |         self.mock_client = MagicMock(spec=FalconClient)
19 | 
20 |         # Create a mock server
21 |         self.mock_server = MagicMock(spec=FastMCP)
22 | 
23 |         # Create the module
24 |         self.module = module_class(self.mock_client)
25 | 
26 |     def assert_tools_registered(self, expected_tools):
27 |         """
28 |         Helper method to verify that a module correctly registers its tools.
29 | 
30 |         Args:
31 |             expected_tools: List of tool names that should be registered
32 |         """
33 |         # Call register_tools
34 |         self.module.register_tools(self.mock_server)
35 | 
36 |         # Verify that add_tool was called for each tool
37 |         self.assertEqual(self.mock_server.add_tool.call_count, len(expected_tools))
38 | 
39 |         # Get the tool names that were registered
40 |         registered_tools = [
41 |             call.kwargs["name"] for call in self.mock_server.add_tool.call_args_list
42 |         ]
43 | 
44 |         # Verify that all expected tools were registered
45 |         for tool in expected_tools:
46 |             self.assertIn(tool, registered_tools)
47 | 
48 |     def assert_resources_registered(self, expected_resources):
49 |         """
50 |         Helper method to verify that a module correctly registers its resources.
51 | 
52 |         Args:
53 |             expected_tools: List of resources names that should be registered
54 |         """
55 |         # Call register_tools
56 |         self.module.register_resources(self.mock_server)
57 | 
58 |         # Verify that add_resource was called for each resource
59 |         self.assertEqual(
60 |             self.mock_server.add_resource.call_count, len(expected_resources)
61 |         )
62 | 
63 |         # Get the tool names that were registered
64 |         registered_resources = [
65 |             call.kwargs["resource"].name
66 |             for call in self.mock_server.add_resource.call_args_list
67 |         ]
68 | 
69 |         # Verify that all expected tools were registered
70 |         for tool in expected_resources:
71 |             self.assertIn(tool, registered_resources)
72 | 
```

--------------------------------------------------------------------------------
/falcon_mcp/common/api_scopes.py:
--------------------------------------------------------------------------------

```python
 1 | """
 2 | API scope definitions and utilities for Falcon MCP Server
 3 | 
 4 | This module provides API scope definitions and related utilities for the Falcon MCP server.
 5 | """
 6 | 
 7 | from typing import List, Optional
 8 | 
 9 | from .logging import get_logger
10 | 
11 | logger = get_logger(__name__)
12 | 
13 | # Map of API operations to required scopes
14 | # This can be expanded as more modules and operations are added
15 | API_SCOPE_REQUIREMENTS = {
16 |     # Alerts operations (migrated from detections)
17 |     "GetQueriesAlertsV2": ["Alerts:read"],
18 |     "PostEntitiesAlertsV2": ["Alerts:read"],
19 |     # Hosts operations
20 |     "QueryDevicesByFilter": ["Hosts:read"],
21 |     "PostDeviceDetailsV2": ["Hosts:read"],
22 |     # Incidents operations
23 |     "QueryIncidents": ["Incidents:read"],
24 |     "GetIncidentDetails": ["Incidents:read"],
25 |     "CrowdScore": ["Incidents:read"],
26 |     "GetIncidents": ["Incidents:read"],
27 |     "GetBehaviors": ["Incidents:read"],
28 |     "QueryBehaviors": ["Incidents:read"],
29 |     # Intel operations
30 |     "QueryIntelActorEntities": ["Actors (Falcon Intelligence):read"],
31 |     "QueryIntelIndicatorEntities": ["Indicators (Falcon Intelligence):read"],
32 |     "QueryIntelReportEntities": ["Reports (Falcon Intelligence):read"],
33 |     # Spotlight operations
34 |     "combinedQueryVulnerabilities": ["Vulnerabilities:read"],
35 |     # Discover operations
36 |     "combined_applications": ["Assets:read"],
37 |     "combined_hosts": ["Assets:read"],
38 |     # Cloud operations
39 |     "ReadContainerCombined": ["Falcon Container Image:read"],
40 |     "ReadContainerCount": ["Falcon Container Image:read"],
41 |     "ReadCombinedVulnerabilities": ["Falcon Container Image:read"],
42 |     # Identity Protection operations
43 |     "api_preempt_proxy_post_graphql": [
44 |         "Identity Protection Entities:read",
45 |         "Identity Protection Timeline:read",
46 |         "Identity Protection Detections:read",
47 |         "Identity Protection Assessment:read",
48 |         "Identity Protection GraphQL:write",
49 |     ],
50 |     # Sensor Usage operations
51 |     "GetSensorUsageWeekly": ["Sensor Usage:read"],
52 |     # Serverless operations
53 |     "GetCombinedVulnerabilitiesSARIF": ["Falcon Container Image:read"],
54 |     # Add more mappings as needed
55 | }
56 | 
57 | 
58 | def get_required_scopes(operation: Optional[str]) -> List[str]:
59 |     """Get the required API scopes for a specific operation.
60 | 
61 |     Args:
62 |         operation: The API operation name
63 | 
64 |     Returns:
65 |         List[str]: List of required API scopes
66 |     """
67 |     return API_SCOPE_REQUIREMENTS.get(operation, [])
68 | 
```

--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature-request.yaml:
--------------------------------------------------------------------------------

```yaml
 1 | name: 🚀 Feature Request
 2 | description: Suggest a new feature for falcon-mcp
 3 | title: "[Feature Request]: "
 4 | labels: ["enhancement"]
 5 | 
 6 | body:
 7 |   - type: markdown
 8 |     attributes:
 9 |       value: |
10 |         Thank you for contributing to falcon-mcp! 🚀
11 | 
12 |         Your ideas help make this project better for everyone.
13 | 
14 |   - type: textarea
15 |     id: description
16 |     attributes:
17 |       label: Feature Description
18 |       description: |
19 |         Please describe the feature you'd like to see added.
20 | 
21 |         Be as detailed as possible about what you want and why it would be valuable. 🙏
22 |     validations:
23 |       required: true
24 | 
25 |   - type: textarea
26 |     id: use_case
27 |     attributes:
28 |       label: Use Case
29 |       description: |
30 |         What problem would this feature solve? How would you use it?
31 | 
32 |         Real-world examples help us understand the value and priority.
33 |       placeholder: |
34 |         As a security analyst, I want to...
35 |         This would help me...
36 |         Currently I have to...
37 |     validations:
38 |       required: true
39 | 
40 |   - type: dropdown
41 |     id: module_area
42 |     attributes:
43 |       label: Related Module/Area
44 |       description: Which part of falcon-mcp would this feature affect?
45 |       options:
46 |         - Core functionality
47 |         - Detections module
48 |         - Incidents module
49 |         - Intel module
50 |         - Hosts module
51 |         - Identity Protection module
52 |         - Cloud Security module
53 |         - Discover module
54 |         - Spotlight (vulnerabilities) module
55 |         - Sensor Usage module
56 |         - Serverless module
57 |         - New module needed
58 |         - Documentation/Examples
59 |         - Not sure
60 |     validations:
61 |       required: true
62 | 
63 |   - type: textarea
64 |     id: proposed_solution
65 |     attributes:
66 |       label: Proposed Solution (Optional)
67 |       description: |
68 |         Do you have ideas for how this could be implemented?
69 | 
70 |         Code examples, API endpoints, or similar features in other tools are helpful!
71 |       placeholder: |
72 |         Maybe something like:
73 | 
74 |         ```python
75 |         # Example of how it might work
76 |         ```
77 | 
78 |   - type: textarea
79 |     id: alternatives
80 |     attributes:
81 |       label: Alternatives Considered (Optional)
82 |       description: |
83 |         Are there other ways to solve this problem?
84 | 
85 |         Have you tried any workarounds or alternative approaches?
86 | 
87 |   - type: textarea
88 |     id: additional
89 |     attributes:
90 |       label: Additional Context (Optional)
91 |       description: |
92 |         Anything else that might help us understand your request?
93 | 
94 |         Links to documentation, similar features, or related issues are helpful!
95 | 
```

--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug.yaml:
--------------------------------------------------------------------------------

```yaml
 1 | name: 🐛 Bug Report
 2 | description: Report a bug or unexpected behavior in falcon-mcp
 3 | title: "[Bug]: "
 4 | labels: ["bug", "triage"]
 5 | 
 6 | body:
 7 |   - type: markdown
 8 |     attributes:
 9 |       value: |
10 |         Thank you for reporting an issue to falcon-mcp! 🚀
11 | 
12 |   - type: checkboxes
13 |     id: checks
14 |     attributes:
15 |       label: Initial Checks
16 |       description: Just making sure you're using the latest version and searched existing issues.
17 |       options:
18 |         - label: I confirm that I'm using the latest version of falcon-mcp
19 |           required: true
20 |         - label: I searched existing issues before opening this report
21 |           required: true
22 | 
23 |   - type: textarea
24 |     id: description
25 |     attributes:
26 |       label: Bug Description
27 |       description: |
28 |         Please explain what you're seeing and what you expected to see.
29 | 
30 |         Include any error messages or unexpected behavior. 🙏
31 |     validations:
32 |       required: true
33 | 
34 |   - type: textarea
35 |     id: reproduction
36 |     attributes:
37 |       label: Steps to Reproduce
38 |       description: |
39 |         How can we reproduce this issue? Please be as specific as possible.
40 |       placeholder: |
41 |         1. Set up falcon-mcp with...
42 |         2. Run command...
43 |         3. Observe error...
44 |     validations:
45 |       required: true
46 | 
47 |   - type: dropdown
48 |     id: installation
49 |     attributes:
50 |       label: Installation Method
51 |       description: How did you install falcon-mcp?
52 |       options:
53 |         - pip install falcon-mcp
54 |         - uvx run falcon-mcp
55 |         - Docker container
56 |         - Development setup (git clone)
57 |         - Other (please specify in description)
58 |     validations:
59 |       required: true
60 | 
61 |   - type: textarea
62 |     id: environment
63 |     attributes:
64 |       label: Environment Details
65 |       description: |
66 |         Please share relevant environment information.
67 | 
68 |         **Please don't include API credentials or sensitive information!**
69 |       placeholder: |
70 |         - Python version: 3.11
71 |         - Operating System: macOS 14.1
72 |         - MCP Client: Claude Desktop
73 |         - Enabled modules: detections,incidents,hosts
74 |         - Falcon API region: us-1
75 |       render: text
76 |     validations:
77 |       required: true
78 | 
79 |   - type: textarea
80 |     id: logs
81 |     attributes:
82 |       label: Error Logs (Optional)
83 |       description: |
84 |         If you have error logs, please include them here.
85 | 
86 |         **Important: Remove any sensitive information like API keys before pasting!**
87 |       render: text
88 | 
89 |   - type: textarea
90 |     id: additional
91 |     attributes:
92 |       label: Additional Context (Optional)
93 |       description: |
94 |         Anything else that might help us understand the issue?
95 | 
96 |         Screenshots, configuration files, or related issues are helpful!
97 | 
```

--------------------------------------------------------------------------------
/falcon_mcp/resources/sensor_usage.py:
--------------------------------------------------------------------------------

```python
  1 | """
  2 | Contains Sensor Usage resources.
  3 | """
  4 | 
  5 | from falcon_mcp.common.utils import generate_md_table
  6 | 
  7 | # List of tuples containing filter options data: (name, type, operators, description)
  8 | SEARCH_SENSOR_USAGE_FQL_FILTERS = [
  9 |     (
 10 |         "Name",
 11 |         "Type",
 12 |         "Operators",
 13 |         "Description"
 14 |     ),
 15 |     (
 16 |         "event_date",
 17 |         "Date",
 18 |         "Yes",
 19 |         """
 20 |         The final date of the results to be returned in ISO 8601 format (YYYY-MM-DD).
 21 |         Data is available for retrieval starting with the current date minus 2 days
 22 |         and going back 395 days.
 23 | 
 24 |         Data is not available for the current date or the current date minus 1 day.
 25 | 
 26 |         Default: the current date minus 2 days
 27 | 
 28 |         Ex: event_date:'2024-06-11'
 29 |         """
 30 |     ),
 31 |     (
 32 |         "period",
 33 |         "String",
 34 |         "Yes",
 35 |         """
 36 |         The number of days of data to return. Even though this looks like a number, make sure to always use quotes for period for example '3' instead of 3.
 37 | 
 38 |         Minimum: 1
 39 |         Maximum: 395
 40 |         Default: 28
 41 | 
 42 |         Ex: period:'30'
 43 |         """
 44 |     ),
 45 |     (
 46 |         "selected_cids",
 47 |         "String",
 48 |         "No",
 49 |         """
 50 |         A comma-separated list of up to 100 CID IDs to return data for.
 51 |         This filter is available to Falcon Flight Control parent CIDs and to CIDs
 52 |         in multi-CID deployments with the access-account-billing-data feature flag enabled.
 53 | 
 54 |         Note: This field is case-sensitive and requires the correct input of capital and lowercase letters.
 55 | 
 56 |         Ex: selected_cids:'cid_1,cid_2,cid_3'
 57 |         """
 58 |     ),
 59 | ]
 60 | 
 61 | SEARCH_SENSOR_USAGE_FQL_DOCUMENTATION = """Falcon Query Language (FQL) - Sensor Usage Guide
 62 | 
 63 | === BASIC SYNTAX ===
 64 | property_name:[operator]'value'
 65 | 
 66 | === AVAILABLE OPERATORS ===
 67 | 
 68 | ✅ **WORKING OPERATORS:**
 69 | • No operator = equals (default) - ALL FIELDS
 70 | • ! = not equal to - ALL FIELDS
 71 | • > = greater than - DATE AND INTEGER FIELDS
 72 | • >= = greater than or equal - DATE AND INTEGER FIELDS
 73 | • < = less than - DATE AND INTEGER FIELDS
 74 | • <= = less than or equal - DATE AND INTEGER FIELDS
 75 | 
 76 | === DATA TYPES & SYNTAX ===
 77 | • Dates: 'YYYY-MM-DD' (ISO 8601 format)
 78 | • Integers: 30 (without quotes)
 79 | • Strings: 'value' or ['exact_value'] for exact match
 80 | 
 81 | === COMBINING CONDITIONS ===
 82 | • + = AND condition
 83 | • , = OR condition
 84 | • ( ) = Group expressions
 85 | 
 86 | === falcon_search_sensor_usage FQL filter options ===
 87 | 
 88 | """ + generate_md_table(SEARCH_SENSOR_USAGE_FQL_FILTERS) + """
 89 | 
 90 | === ✅ WORKING PATTERNS ===
 91 | 
 92 | **Basic Equality:**
 93 | • event_date:'2024-06-11'
 94 | • period:'30'
 95 | • selected_cids:'cid_1,cid_2,cid_3'
 96 | 
 97 | **Combined Conditions:**
 98 | • event_date:'2024-06-11'+period:'30'
 99 | • event_date:'2024-06-11'+selected_cids:'cid_1,cid_2'
100 | 
101 | **Date Comparisons:**
102 | • event_date:>'2024-01-01'
103 | • event_date:<='2024-06-11'
104 | 
105 | **Period Comparisons:**
106 | • period:>='14'
107 | • period:<='60'
108 | 
109 | === 💡 SYNTAX RULES ===
110 | • Use single quotes around values: 'value'
111 | • Date format must be ISO 8601: 'YYYY-MM-DD'
112 | • Combine conditions with + (AND) or , (OR)
113 | • Use parentheses for grouping: (condition1,condition2)+condition3
114 | """
115 | 
```

--------------------------------------------------------------------------------
/tests/e2e/modules/test_sensor_usage.py:
--------------------------------------------------------------------------------

```python
 1 | """
 2 | E2E tests for the Sensor Usage module.
 3 | """
 4 | 
 5 | import unittest
 6 | 
 7 | import pytest
 8 | 
 9 | from tests.e2e.utils.base_e2e_test import BaseE2ETest, ensure_dict
10 | 
11 | 
12 | @pytest.mark.e2e
13 | class TestSensorUsageModuleE2E(BaseE2ETest):
14 |     """
15 |     End-to-end test suite for the Falcon MCP Server Sensor Usage Module.
16 |     """
17 | 
18 |     def test_search_sensor_usage(self):
19 |         """Verify the agent can show sensor usage for a specific event_date"""
20 | 
21 |         async def test_logic():
22 |             fixtures = [
23 |                 {
24 |                     "operation": "GetSensorUsageWeekly",
25 |                     "validator": lambda kwargs: "event_date:'2025-08-02'" in kwargs.get("parameters", {}).get("filter", ""),
26 |                     "response": {
27 |                         "status_code": 200,
28 |                         "body": {
29 |                             "resources": [
30 |                                 {
31 |                                     "containers": 42.5,
32 |                                     "public_cloud_with_containers": 42,
33 |                                     "public_cloud_without_containers": 42.75,
34 |                                     "servers_with_containers": 42.25,
35 |                                     "servers_without_containers": 42.75,
36 |                                     "workstations": 42.75,
37 |                                     "mobile": 42.75,
38 |                                     "lumos": 42.25,
39 |                                     "chrome_os": 0,
40 |                                     "date": "2025-08-02"
41 |                                 }
42 |                             ]
43 |                         },
44 |                     },
45 |                 }
46 |             ]
47 | 
48 |             self._mock_api_instance.command.side_effect = (
49 |                 self._create_mock_api_side_effect(fixtures)
50 |             )
51 | 
52 |             prompt = "Show me sensor usage on 2025-08-02"
53 |             return await self._run_agent_stream(prompt)
54 | 
55 |         def assertions(tools, result):
56 |             tool_names_called = [tool["input"]["tool_name"] for tool in tools]
57 |             self.assertIn("falcon_search_sensor_usage_fql_guide", tool_names_called)
58 |             self.assertIn("falcon_search_sensor_usage", tool_names_called)
59 | 
60 |             used_tool = tools[len(tools) - 1]
61 | 
62 |             # Verify the tool input contains the filter parameter with proper FQL syntax
63 |             tool_input = ensure_dict(used_tool["input"]["tool_input"])
64 |             self.assertIn("filter", tool_input, "Tool input should contain a 'filter' parameter")
65 |             self.assertIn("event_date:'2025-08-02", tool_input.get("filter", ""), "Filter should contain event_date:'2025-08-02' in FQL syntax")
66 | 
67 |             # # Verify API call parameters
68 |             self.assertGreaterEqual(
69 |                 self._mock_api_instance.command.call_count,
70 |                 1,
71 |                 "Expected at least 1 API call",
72 |             )
73 |             api_call_params = self._mock_api_instance.command.call_args_list[0][1].get(
74 |                 "parameters", {}
75 |             )
76 |             self.assertIn("event_date:'2025-08-02'", api_call_params.get("filter", ""))
77 | 
78 |         self.run_test_with_retries(
79 |             "test_search_sensor_usage", test_logic, assertions
80 |         )
81 | 
82 | 
83 | if __name__ == "__main__":
84 |     unittest.main()
85 | 
```

--------------------------------------------------------------------------------
/tests/common/test_logging.py:
--------------------------------------------------------------------------------

```python
 1 | """
 2 | Tests for the logging utilities.
 3 | """
 4 | 
 5 | import logging
 6 | import unittest
 7 | from unittest.mock import MagicMock, patch
 8 | 
 9 | from falcon_mcp.common.logging import configure_logging, get_logger
10 | 
11 | 
12 | class TestLoggingUtils(unittest.TestCase):
13 |     """Test cases for the logging utilities."""
14 | 
15 |     @patch("falcon_mcp.common.logging.logging.basicConfig")
16 |     @patch("falcon_mcp.common.logging.logging.getLogger")
17 |     def test_configure_logging_debug(self, mock_get_logger, mock_basic_config):
18 |         """Test configuring logging with debug enabled."""
19 |         # Setup mock
20 |         mock_logger = MagicMock()
21 |         mock_get_logger.return_value = mock_logger
22 | 
23 |         # Call configure_logging with debug=True
24 |         logger = configure_logging(debug=True, name="test_logger")
25 | 
26 |         # Verify basicConfig was called with DEBUG level
27 |         mock_basic_config.assert_called_once()
28 |         _args, kwargs = mock_basic_config.call_args
29 |         self.assertEqual(kwargs["level"], logging.DEBUG)
30 | 
31 |         # Verify logger was configured correctly
32 |         mock_get_logger.assert_called_with("test_logger")
33 |         mock_logger.setLevel.assert_called_with(logging.DEBUG)
34 | 
35 |         # Verify logger was returned
36 |         self.assertEqual(logger, mock_logger)
37 | 
38 |     @patch("falcon_mcp.common.logging.logging.basicConfig")
39 |     @patch("falcon_mcp.common.logging.logging.getLogger")
40 |     def test_configure_logging_info(self, mock_get_logger, mock_basic_config):
41 |         """Test configuring logging with debug disabled."""
42 |         # Setup mock
43 |         mock_logger = MagicMock()
44 |         mock_get_logger.return_value = mock_logger
45 | 
46 |         # Call configure_logging with debug=False
47 |         logger = configure_logging(debug=False, name="test_logger")
48 | 
49 |         # Verify basicConfig was called with INFO level
50 |         mock_basic_config.assert_called_once()
51 |         _args, kwargs = mock_basic_config.call_args
52 |         self.assertEqual(kwargs["level"], logging.INFO)
53 | 
54 |         # Verify logger was configured correctly
55 |         mock_get_logger.assert_called_with("test_logger")
56 |         mock_logger.setLevel.assert_called_with(logging.INFO)
57 | 
58 |         # Verify logger was returned
59 |         self.assertEqual(logger, mock_logger)
60 | 
61 |     @patch("falcon_mcp.common.logging.logging.getLogger")
62 |     def test_get_logger_with_name(self, mock_get_logger):
63 |         """Test getting a logger with a specific name."""
64 |         # Setup mock
65 |         mock_logger = MagicMock()
66 |         mock_get_logger.return_value = mock_logger
67 | 
68 |         # Call get_logger with a name
69 |         logger = get_logger("test_logger")
70 | 
71 |         # Verify getLogger was called with the correct name
72 |         mock_get_logger.assert_called_with("test_logger")
73 | 
74 |         # Verify logger was returned
75 |         self.assertEqual(logger, mock_logger)
76 | 
77 |     @patch("falcon_mcp.common.logging.logging.getLogger")
78 |     def test_get_logger_default_name(self, mock_get_logger):
79 |         """Test getting a logger with the default name."""
80 |         # Setup mock
81 |         mock_logger = MagicMock()
82 |         mock_get_logger.return_value = mock_logger
83 | 
84 |         # Call get_logger without a name
85 |         logger = get_logger()
86 | 
87 |         # Verify getLogger was called with the default name
88 |         mock_get_logger.assert_called_with("falcon_mcp")
89 | 
90 |         # Verify logger was returned
91 |         self.assertEqual(logger, mock_logger)
92 | 
93 | 
94 | if __name__ == "__main__":
95 |     unittest.main()
96 | 
```

--------------------------------------------------------------------------------
/falcon_mcp/modules/sensor_usage.py:
--------------------------------------------------------------------------------

```python
 1 | """
 2 | Sensor Usage module for Falcon MCP Server
 3 | 
 4 | This module provides tools for accessing CrowdStrike Falcon sensor usage data.
 5 | """
 6 | 
 7 | from typing import Any, Dict, List
 8 | 
 9 | from mcp.server import FastMCP
10 | from mcp.server.fastmcp.resources import TextResource
11 | from pydantic import AnyUrl, Field
12 | 
13 | from falcon_mcp.common.errors import handle_api_response
14 | from falcon_mcp.common.logging import get_logger
15 | from falcon_mcp.common.utils import prepare_api_parameters
16 | from falcon_mcp.modules.base import BaseModule
17 | from falcon_mcp.resources.sensor_usage import SEARCH_SENSOR_USAGE_FQL_DOCUMENTATION
18 | 
19 | logger = get_logger(__name__)
20 | 
21 | 
22 | class SensorUsageModule(BaseModule):
23 |     """Module for accessing CrowdStrike Falcon sensor usage data."""
24 | 
25 |     def register_tools(self, server: FastMCP) -> None:
26 |         """Register tools with the MCP server.
27 | 
28 |         Args:
29 |             server: MCP server instance
30 |         """
31 |         # Register tools
32 |         self._add_tool(
33 |             server=server,
34 |             method=self.search_sensor_usage,
35 |             name="search_sensor_usage",
36 |         )
37 | 
38 |     def register_resources(self, server: FastMCP) -> None:
39 |         """Register resources with the MCP server.
40 | 
41 |         Args:
42 |             server: MCP server instance
43 |         """
44 |         search_sensor_usage_fql_resource = TextResource(
45 |             uri=AnyUrl("falcon://sensor-usage/weekly/fql-guide"),
46 |             name="falcon_search_sensor_usage_fql_guide",
47 |             description="Contains the guide for the `filter` param of the `falcon_search_sensor_usage` tool.",
48 |             text=SEARCH_SENSOR_USAGE_FQL_DOCUMENTATION,
49 |         )
50 | 
51 |         self._add_resource(
52 |             server,
53 |             search_sensor_usage_fql_resource,
54 |         )
55 | 
56 |     def search_sensor_usage(
57 |         self,
58 |         filter: str | None = Field(
59 |             default=None,
60 |             description="FQL Syntax formatted string used to limit the results. IMPORTANT: use the `falcon://sensor-usage/weekly/fql-guide` resource when building this filter parameter.",
61 |             examples={"event_date:'2024-06-11'", "period:'30'"},
62 |         ),
63 |     ) -> List[Dict[str, Any]]:
64 |         """Search for sensor usage data in your CrowdStrike environment.
65 | 
66 |         IMPORTANT: You must use the `falcon://sensor-usage/weekly/fql-guide` resource when you need to use the `filter` parameter.
67 |         This resource contains the guide on how to build the FQL `filter` parameter for the `falcon_search_sensor_usage` tool.
68 |         """
69 |         # Prepare parameters for GetSensorUsageWeekly
70 |         params = prepare_api_parameters(
71 |             {
72 |                 "filter": filter,
73 |             }
74 |         )
75 | 
76 |         # Define the operation name
77 |         operation = "GetSensorUsageWeekly"
78 | 
79 |         logger.debug("Searching sensor usage with params: %s", params)
80 | 
81 |         # Make the API request
82 |         response = self.client.command(operation, parameters=params)
83 | 
84 |         # Use handle_api_response to get the results
85 |         results = handle_api_response(
86 |             response,
87 |             operation=operation,
88 |             error_message="Failed to search sensor usage",
89 |             default_result=[],
90 |         )
91 | 
92 |         # If handle_api_response returns an error dict instead of a list,
93 |         # it means there was an error, so we return it wrapped in a list
94 |         if self._is_error(results):
95 |             return [results]
96 | 
97 |         return results
98 | 
```

--------------------------------------------------------------------------------
/falcon_mcp/modules/base.py:
--------------------------------------------------------------------------------

```python
  1 | """
  2 | Base module for Falcon MCP Server
  3 | 
  4 | This module provides the base class for all Falcon MCP server modules.
  5 | """
  6 | 
  7 | from abc import ABC, abstractmethod
  8 | from typing import Any, Callable, Dict, List
  9 | 
 10 | from mcp import Resource
 11 | from mcp.server import FastMCP
 12 | 
 13 | from falcon_mcp.client import FalconClient
 14 | from falcon_mcp.common.errors import handle_api_response
 15 | from falcon_mcp.common.logging import get_logger
 16 | from falcon_mcp.common.utils import prepare_api_parameters
 17 | 
 18 | logger = get_logger(__name__)
 19 | 
 20 | 
 21 | class BaseModule(ABC):
 22 |     """Base class for all Falcon MCP server modules."""
 23 | 
 24 |     def __init__(self, client: FalconClient):
 25 |         """Initialize the module.
 26 | 
 27 |         Args:
 28 |             client: Falcon API client
 29 |         """
 30 |         self.client = client
 31 |         self.tools = []  # List to track registered tools
 32 |         self.resources = []  # List to track registered resources
 33 | 
 34 |     @abstractmethod
 35 |     def register_tools(self, server: FastMCP) -> None:
 36 |         """Register tools with the MCP server.
 37 | 
 38 |         Args:
 39 |             server: MCP server instance
 40 |         """
 41 | 
 42 |     def register_resources(self, server: FastMCP) -> None:
 43 |         """Register resources with the MCP Server.
 44 | 
 45 |         Args:
 46 |             server: MCP server instance
 47 |         """
 48 | 
 49 |     def _add_tool(self, server: FastMCP, method: Callable, name: str) -> None:
 50 |         """Add a tool to the MCP server and track it.
 51 | 
 52 |         Args:
 53 |             server: MCP server instance
 54 |             method: Method to register
 55 |             name: Tool name
 56 |         """
 57 |         prefixed_name = f"falcon_{name}"
 58 |         server.add_tool(method, name=prefixed_name)
 59 |         self.tools.append(prefixed_name)
 60 |         logger.debug("Added tool: %s", prefixed_name)
 61 | 
 62 |     def _add_resource(self, server: FastMCP, resource: Resource) -> None:
 63 |         """Add a resource to the MCP server and track it.
 64 | 
 65 |         Args:
 66 |             server: MCP server instance
 67 |             resource: Resource object
 68 |         """
 69 |         server.add_resource(resource=resource)
 70 | 
 71 |         resource_uri = resource.uri
 72 |         self.resources.append(resource_uri)
 73 |         logger.debug("Added resource: %s", resource_uri)
 74 | 
 75 |     def _base_get_by_ids(
 76 |         self,
 77 |         operation: str,
 78 |         ids: List[str],
 79 |         id_key: str = "ids",
 80 |         **additional_params,
 81 |     ) -> List[Dict[str, Any]] | Dict[str, Any]:
 82 |         """Helper method for API operations that retrieve entities by IDs.
 83 | 
 84 |         Args:
 85 |             operation: The API operation name
 86 |             ids: List of entity IDs
 87 |             id_key: The key name for IDs in the request body (default: "ids")
 88 |             **additional_params: Additional parameters to include in the request body
 89 | 
 90 |         Returns:
 91 |             List of entity details or error dict
 92 |         """
 93 |         # Build the request body with dynamic ID key and additional parameters
 94 |         body_params = {id_key: ids}
 95 |         body_params.update(additional_params)
 96 | 
 97 |         body = prepare_api_parameters(body_params)
 98 | 
 99 |         # Make the API request
100 |         response = self.client.command(operation, body=body)
101 | 
102 |         # Handle the response
103 |         return handle_api_response(
104 |             response,
105 |             operation=operation,
106 |             error_message="Failed to perform operation",
107 |             default_result=[],
108 |         )
109 | 
110 |     def _is_error(self, response: Any) -> bool:
111 |         return isinstance(response, dict) and "error" in response
112 | 
```

--------------------------------------------------------------------------------
/tests/test_registry.py:
--------------------------------------------------------------------------------

```python
 1 | """
 2 | Tests for the module registry.
 3 | """
 4 | 
 5 | import unittest
 6 | from unittest.mock import MagicMock
 7 | 
 8 | from falcon_mcp import registry
 9 | from falcon_mcp.modules.base import BaseModule
10 | 
11 | 
12 | class TestRegistry(unittest.TestCase):
13 |     """Test cases for the module registry."""
14 | 
15 |     def setUp(self):
16 |         """Set up test fixtures before each test method."""
17 |         # Clear the AVAILABLE_MODULES dictionary before each test
18 |         registry.AVAILABLE_MODULES.clear()
19 | 
20 |     def tearDown(self):
21 |         """Clean up after each test method."""
22 |         # Restore the original AVAILABLE_MODULES dictionary
23 |         registry.AVAILABLE_MODULES.clear()
24 |         # Re-discover modules to restore the original state
25 |         registry.discover_modules()
26 | 
27 |     def test_discover_modules(self):
28 |         """Test that discover_modules correctly populates AVAILABLE_MODULES."""
29 |         # Call discover_modules
30 |         registry.discover_modules()
31 | 
32 |         # Verify that AVAILABLE_MODULES is not empty
33 |         self.assertGreater(len(registry.AVAILABLE_MODULES), 0)
34 | 
35 |         # Verify that all registered modules are subclasses of BaseModule
36 |         for module_class in registry.AVAILABLE_MODULES.values():
37 |             self.assertTrue(issubclass(module_class, BaseModule))
38 | 
39 |     def test_get_module_names(self):
40 |         """Test that get_module_names returns the correct list of module names."""
41 |         # Manually populate AVAILABLE_MODULES with some test modules
42 |         registry.AVAILABLE_MODULES = {
43 |             "test1": MagicMock(),
44 |             "test2": MagicMock(),
45 |             "test3": MagicMock(),
46 |         }
47 | 
48 |         # Call get_module_names
49 |         module_names = registry.get_module_names()
50 | 
51 |         # Verify that the returned list contains all the expected module names
52 |         self.assertEqual(set(module_names), {"test1", "test2", "test3"})
53 |         self.assertEqual(len(module_names), 3)
54 | 
55 |     def test_get_module_names_lazy_discovery(self):
56 |         """Test that get_module_names performs lazy discovery when no modules are registered."""
57 |         # Ensure AVAILABLE_MODULES is empty
58 |         registry.AVAILABLE_MODULES.clear()
59 | 
60 |         # Call get_module_names (should trigger lazy discovery)
61 |         module_names = registry.get_module_names()
62 | 
63 |         # Verify that modules were discovered (should not be empty)
64 |         self.assertGreater(len(module_names), 0)
65 | 
66 |         # Verify that the expected modules are discovered
67 |         expected_modules = ["detections", "incidents", "intel"]
68 |         for module_name in expected_modules:
69 |             self.assertIn(module_name, module_names)
70 | 
71 |     def test_actual_modules_discovery(self):
72 |         """Test that actual modules in the project are discovered correctly."""
73 |         # Clear the AVAILABLE_MODULES dictionary
74 |         registry.AVAILABLE_MODULES.clear()
75 | 
76 |         # Call discover_modules
77 |         registry.discover_modules()
78 | 
79 |         # Get the list of expected module names based on the project structure
80 |         # This assumes that the project has modules like 'incidents', 'intel'
81 |         expected_modules = ["incidents", "intel"]
82 | 
83 |         # Verify that all expected modules are discovered
84 |         for module_name in expected_modules:
85 |             self.assertIn(module_name, registry.AVAILABLE_MODULES)
86 | 
87 |         # Verify that all discovered modules are subclasses of BaseModule
88 |         for module_class in registry.AVAILABLE_MODULES.values():
89 |             self.assertTrue(issubclass(module_class, BaseModule))
90 | 
91 | 
92 | if __name__ == "__main__":
93 |     unittest.main()
94 | 
```

--------------------------------------------------------------------------------
/.github/workflows/docker-build-push.yml:
--------------------------------------------------------------------------------

```yaml
 1 | name: Docker Build & Push
 2 | 
 3 | on:
 4 |   push:
 5 |     branches: [main]
 6 |   release:
 7 |     types: [published]
 8 | 
 9 | permissions:
10 |   contents: read
11 |   packages: write
12 | 
13 | jobs:
14 |   docker-build-push:
15 |     runs-on: ubuntu-latest
16 |     steps:
17 |       - name: Harden the runner (Audit all outbound calls)
18 |         uses: step-security/harden-runner@f4a75cfd619ee5ce8d5b864b0d183aff3c69b55a # v2.13.1
19 |         with:
20 |           egress-policy: audit
21 | 
22 |       - name: Checkout code
23 |         uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8
24 | 
25 |       - name: Set up Docker Buildx
26 |         uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435
27 | 
28 |       - name: Log in to Quay.io
29 |         uses: docker/login-action@184bdaa0721073962dff0199f1fb9940f07167d1
30 |         with:
31 |           registry: quay.io
32 |           username: ${{ secrets.QUAY_USERNAME }}
33 |           password: ${{ secrets.QUAY_PASSWORD }}
34 | 
35 |       - name: Extract metadata
36 |         id: meta
37 |         uses: docker/metadata-action@c1e51972afc2121e065aed6d45c65596fe445f3f
38 |         with:
39 |           images: quay.io/crowdstrike/falcon-mcp
40 |           tags: |
41 |             type=raw,value=latest,enable=${{ github.event_name == 'push' }}
42 |             type=semver,pattern={{version}},enable=${{ github.event_name == 'release' }}
43 |           flavor: |
44 |             latest=${{ github.event_name == 'push' }}
45 |           labels: |
46 |             org.opencontainers.image.title=Falcon MCP Server
47 |             org.opencontainers.image.description=Model Context Protocol server for CrowdStrike Falcon
48 |             org.opencontainers.image.vendor=CrowdStrike
49 |             org.opencontainers.image.licenses=MIT
50 |             org.opencontainers.image.source=https://github.com/CrowdStrike/falcon-mcp
51 |             org.opencontainers.image.documentation=https://github.com/CrowdStrike/falcon-mcp/blob/main/README.md
52 | 
53 |       - name: Build and push Docker image
54 |         uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83
55 |         with:
56 |           context: .
57 |           platforms: linux/amd64,linux/arm64
58 |           push: true
59 |           tags: ${{ steps.meta.outputs.tags }}
60 |           labels: ${{ steps.meta.outputs.labels }}
61 |           cache-from: type=gha
62 |           cache-to: type=gha,mode=max
63 | 
64 |       - name: Generate image summary
65 |         run: |
66 |           # Get generated tags and extract the actual tag for pull command
67 |           TAGS="${{ steps.meta.outputs.tags }}"
68 |           FULL_TAG=$(echo "$TAGS" | head -n1)
69 |           TAG_ONLY=$(echo "$FULL_TAG" | sed 's/.*://')
70 | 
71 |           if [ "${{ github.event_name }}" = "push" ]; then
72 |             EVENT_TYPE="Main Branch Push"
73 |           else
74 |             EVENT_TYPE="Release"
75 |           fi
76 | 
77 |           echo "## 🐳 Docker Image Published" >> $GITHUB_STEP_SUMMARY
78 |           echo "" >> $GITHUB_STEP_SUMMARY
79 |           echo "**Event:** $EVENT_TYPE" >> $GITHUB_STEP_SUMMARY
80 |           echo "**Registry:** quay.io/crowdstrike/falcon-mcp" >> $GITHUB_STEP_SUMMARY
81 |           echo "**Tags:**" >> $GITHUB_STEP_SUMMARY
82 |           echo '```' >> $GITHUB_STEP_SUMMARY
83 |           echo "$TAGS" >> $GITHUB_STEP_SUMMARY
84 |           echo '```' >> $GITHUB_STEP_SUMMARY
85 |           echo "" >> $GITHUB_STEP_SUMMARY
86 |           echo "**Platforms:** linux/amd64, linux/arm64" >> $GITHUB_STEP_SUMMARY
87 |           echo "" >> $GITHUB_STEP_SUMMARY
88 |           echo "**Pull Command:**" >> $GITHUB_STEP_SUMMARY
89 |           echo '```bash' >> $GITHUB_STEP_SUMMARY
90 |           echo "docker pull quay.io/crowdstrike/falcon-mcp:$TAG_ONLY" >> $GITHUB_STEP_SUMMARY
91 |           echo '```' >> $GITHUB_STEP_SUMMARY
92 | 
```

--------------------------------------------------------------------------------
/falcon_mcp/common/errors.py:
--------------------------------------------------------------------------------

```python
  1 | """
  2 | Error handling utilities for Falcon MCP Server
  3 | 
  4 | This module provides error handling utilities for the Falcon MCP server.
  5 | """
  6 | 
  7 | from typing import Any, Dict, Optional
  8 | 
  9 | from .api_scopes import get_required_scopes
 10 | from .logging import get_logger
 11 | 
 12 | logger = get_logger(__name__)
 13 | 
 14 | # Common error codes and their meanings
 15 | ERROR_CODE_DESCRIPTIONS = {
 16 |     403: "Permission denied. The API credentials don't have the required access.",
 17 |     401: "Authentication failed. The API credentials are invalid or expired.",
 18 |     404: "Resource not found. The requested resource does not exist.",
 19 |     429: "Rate limit exceeded. Too many requests in a short period.",
 20 |     500: "Server error. An unexpected error occurred on the server.",
 21 |     503: "Service unavailable. The service is temporarily unavailable.",
 22 | }
 23 | 
 24 | 
 25 | class FalconError(Exception):
 26 |     """Base exception for all Falcon MCP server errors."""
 27 | 
 28 | 
 29 | class AuthenticationError(FalconError):
 30 |     """Raised when authentication with the Falcon API fails."""
 31 | 
 32 | 
 33 | class APIError(FalconError):
 34 |     """Raised when a Falcon API request fails."""
 35 | 
 36 |     def __init__(
 37 |         self,
 38 |         message: str,
 39 |         status_code: Optional[int] = None,
 40 |         body: Optional[Dict[str, Any]] = None,
 41 |         operation: Optional[str] = None,
 42 |     ):
 43 |         self.status_code = status_code
 44 |         self.body = body
 45 |         self.operation = operation
 46 |         super().__init__(message)
 47 | 
 48 | 
 49 | def is_success_response(response: Dict[str, Any]) -> bool:
 50 |     """Check if an API response indicates success.
 51 | 
 52 |     Args:
 53 |         response: The API response dictionary
 54 | 
 55 |     Returns:
 56 |         bool: True if the response indicates success (status code 200)
 57 |     """
 58 |     return response.get("status_code") == 200
 59 | 
 60 | 
 61 | def _format_error_response(
 62 |     message: str,
 63 |     details: Optional[Dict[str, Any]] = None,
 64 |     operation: Optional[str] = None,
 65 | ) -> Dict[str, Any]:
 66 |     """Format an error as a standardized response.
 67 | 
 68 |     Args:
 69 |         message: The error message
 70 |         details: Additional error details
 71 |         operation: The API operation that failed (used for permission errors)
 72 | 
 73 |     Returns:
 74 |         Dict[str, Any]: Formatted error response
 75 |     """
 76 |     response = {"error": message}
 77 | 
 78 |     # Add details if provided
 79 |     if details:
 80 |         response["details"] = details
 81 | 
 82 |         # Special handling for permission errors (403)
 83 |         if details.get("status_code") == 403 and operation:
 84 |             required_scopes = get_required_scopes(operation)
 85 |             if required_scopes:
 86 |                 response["required_scopes"] = required_scopes
 87 |                 scopes_list = ", ".join(required_scopes)
 88 |                 response["resolution"] = (
 89 |                     f"This operation requires the following API scopes: {scopes_list}. "
 90 |                     "Please ensure your API client has been granted these scopes in the "
 91 |                     "CrowdStrike Falcon console."
 92 |                 )
 93 | 
 94 |     # Log the error
 95 |     logger.error("Error: %s", message)
 96 | 
 97 |     return response
 98 | 
 99 | 
100 | def handle_api_response(
101 |     response: Dict[str, Any],
102 |     operation: str,
103 |     error_message: str = "API request failed",
104 |     default_result: Any = None,
105 | ) -> Dict[str, Any] | Any:
106 |     """Handle an API response, returning either the result or an error.
107 | 
108 |     Args:
109 |         response: The API response dictionary
110 |         operation: The API operation that was performed
111 |         error_message: The error message to use if the request failed
112 |         default_result: The default result to return if the response is empty
113 | 
114 |     Returns:
115 |         Dict[str, Any]|Any: The result or an error response
116 |     """
117 |     status_code = response.get("status_code")
118 | 
119 |     if status_code != 200:
120 |         # Get a more descriptive error message based on status code
121 |         status_message = ERROR_CODE_DESCRIPTIONS.get(
122 |             status_code, f"Request failed with status code {status_code}"
123 |         )
124 | 
125 |         # For permission errors, add more context
126 |         if status_code == 403:
127 |             required_scopes = get_required_scopes(operation)
128 |             if required_scopes:
129 |                 status_message += f" Required scopes: {', '.join(required_scopes)}"
130 | 
131 |         # Log the error
132 |         logger.error("Error: %s: %s", error_message, status_message)
133 | 
134 |         return _format_error_response(
135 |             f"{error_message}: {status_message}", details=response, operation=operation
136 |         )
137 | 
138 |     # Extract resources from the response body
139 |     resources = response.get("body", {}).get("resources", [])
140 | 
141 |     if not resources and default_result is not None:
142 |         return default_result
143 | 
144 |     return resources
145 | 
```

--------------------------------------------------------------------------------
/tests/modules/test_cloud.py:
--------------------------------------------------------------------------------

```python
  1 | """
  2 | Tests for the Cloud module.
  3 | """
  4 | 
  5 | import unittest
  6 | 
  7 | from falcon_mcp.modules.cloud import CloudModule
  8 | from tests.modules.utils.test_modules import TestModules
  9 | 
 10 | 
 11 | class TestCloudModule(TestModules):
 12 |     """Test cases for the Cloud module."""
 13 | 
 14 |     def setUp(self):
 15 |         """Set up test fixtures."""
 16 |         self.setup_module(CloudModule)
 17 | 
 18 |     def test_register_tools(self):
 19 |         """Test registering tools with the server."""
 20 |         expected_tools = [
 21 |             "falcon_search_kubernetes_containers",
 22 |             "falcon_count_kubernetes_containers",
 23 |             "falcon_search_images_vulnerabilities",
 24 |         ]
 25 |         self.assert_tools_registered(expected_tools)
 26 | 
 27 |     def test_register_resources(self):
 28 |         """Test registering resources with the server."""
 29 |         expected_resources = [
 30 |             "falcon_kubernetes_containers_fql_filter_guide",
 31 |             "falcon_images_vulnerabilities_fql_filter_guide",
 32 |         ]
 33 |         self.assert_resources_registered(expected_resources)
 34 | 
 35 |     def test_search_kubernetes_containers(self):
 36 |         """Test searching for kubernetes containers."""
 37 |         mock_response = {
 38 |             "status_code": 200,
 39 |             "body": {"resources": ["container_1", "container_2"]},
 40 |         }
 41 |         self.mock_client.command.return_value = mock_response
 42 | 
 43 |         result = self.module.search_kubernetes_containers(
 44 |             filter="cloud_name:'AWS'", limit=1
 45 |         )
 46 | 
 47 |         self.assertEqual(self.mock_client.command.call_count, 1)
 48 | 
 49 |         first_call = self.mock_client.command.call_args_list[0]
 50 |         self.assertEqual(first_call[0][0], "ReadContainerCombined")
 51 |         self.assertEqual(first_call[1]["parameters"]["filter"], "cloud_name:'AWS'")
 52 |         self.assertEqual(first_call[1]["parameters"]["limit"], 1)
 53 |         self.assertEqual(result, ["container_1", "container_2"])
 54 | 
 55 |     def test_search_kubernetes_containers_errors(self):
 56 |         """Test searching for kubernetes containers with API error."""
 57 |         mock_response = {
 58 |             "status_code": 400,
 59 |             "body": {"errors": [{"message": "Invalid filter"}]},
 60 |         }
 61 |         self.mock_client.command.return_value = mock_response
 62 | 
 63 |         result = self.module.search_kubernetes_containers(filter="invalid_filter")
 64 | 
 65 |         self.assertIsInstance(result, dict)
 66 |         self.assertIn("error", result)
 67 |         self.assertIn("details", result)
 68 | 
 69 |     def test_count_kubernetes_containers(self):
 70 |         """Test count for kubernetes containers."""
 71 |         mock_response = {"status_code": 200, "body": {"resources": [500]}}
 72 |         self.mock_client.command.return_value = mock_response
 73 | 
 74 |         result = self.module.count_kubernetes_containers(filter="cloud_region:'us-1'")
 75 | 
 76 |         self.assertEqual(self.mock_client.command.call_count, 1)
 77 | 
 78 |         first_call = self.mock_client.command.call_args_list[0]
 79 |         self.assertEqual(first_call[0][0], "ReadContainerCount")
 80 |         self.assertEqual(first_call[1]["parameters"]["filter"], "cloud_region:'us-1'")
 81 |         self.assertEqual(result, [500])
 82 | 
 83 |     def test_count_kubernetes_containers_errors(self):
 84 |         """Test count for kubernetes containers with API error."""
 85 |         mock_response = {
 86 |             "status_code": 500,
 87 |             "body": {"errors": [{"message": "internal error"}]},
 88 |         }
 89 |         self.mock_client.command.return_value = mock_response
 90 | 
 91 |         result = self.module.search_kubernetes_containers(filter="invalid_filter")
 92 | 
 93 |         self.assertIsInstance(result, dict)
 94 |         self.assertIn("error", result)
 95 |         self.assertIn("details", result)
 96 | 
 97 |     def test_search_images_vulnerabilities(self):
 98 |         """Test search for images vulnerabilities."""
 99 |         mock_response = {"status_code": 200, "body": {"resources": ["cve_id_1"]}}
100 |         self.mock_client.command.return_value = mock_response
101 | 
102 |         result = self.module.search_images_vulnerabilities(
103 |             filter="cvss_score:>5", limit=1
104 |         )
105 | 
106 |         self.assertEqual(self.mock_client.command.call_count, 1)
107 | 
108 |         first_call = self.mock_client.command.call_args_list[0]
109 |         self.assertEqual(first_call[0][0], "ReadCombinedVulnerabilities")
110 |         self.assertEqual(first_call[1]["parameters"]["filter"], "cvss_score:>5")
111 |         self.assertEqual(first_call[1]["parameters"]["limit"], 1)
112 |         self.assertEqual(result, ["cve_id_1"])
113 | 
114 |     def test_search_images_vulnerabilities_errors(self):
115 |         """Test search for images vulnerabilities with API error."""
116 |         mock_response = {
117 |             "status_code": 400,
118 |             "body": {"errors": [{"message": "invalid sort"}]},
119 |         }
120 |         self.mock_client.command.return_value = mock_response
121 | 
122 |         result = self.module.search_kubernetes_containers(sort="1|1")
123 | 
124 |         self.assertIsInstance(result, dict)
125 |         self.assertIn("error", result)
126 |         self.assertIn("details", result)
127 | 
128 | 
129 | if __name__ == "__main__":
130 |     unittest.main()
131 | 
```

--------------------------------------------------------------------------------
/docs/e2e_testing.md:
--------------------------------------------------------------------------------

```markdown
  1 | # End-to-End Testing Guide
  2 | 
  3 | This document provides guidance on running and understanding the end-to-end tests for the Falcon MCP Server.
  4 | 
  5 | ## Configuration
  6 | 
  7 | The E2E tests can be configured using environment variables or a `.env` file. For development and testing, copy the development example file:
  8 | 
  9 | ```bash
 10 | cp .env.dev.example .env
 11 | ```
 12 | 
 13 | Then configure the E2E testing variables:
 14 | 
 15 | ### LLM Configuration
 16 | 
 17 | ```bash
 18 | # API key for OpenAI or compatible API
 19 | OPENAI_API_KEY=your-api-key
 20 | 
 21 | # Optional: Custom base URL for LLM API (for VPN-only or custom endpoints)
 22 | OPENAI_BASE_URL=https://your-custom-llm-endpoint.com/v1
 23 | 
 24 | # Optional: Comma-separated list of models to test against
 25 | MODELS_TO_TEST=example-model-1,example-model-2
 26 | ```
 27 | 
 28 | If not specified, the tests will use the default models defined in `tests/e2e/utils/base_e2e_test.py`.
 29 | 
 30 | ## Running E2E Tests
 31 | 
 32 | End-to-end tests are marked with the `@pytest.mark.e2e` decorator and require the `--run-e2e` flag to run:
 33 | 
 34 | ```bash
 35 | # Run all E2E tests
 36 | pytest --run-e2e tests/e2e/
 37 | 
 38 | # Run a specific E2E test
 39 | pytest --run-e2e tests/e2e/test_mcp_server.py::TestFalconMCPServerE2E::test_get_top_3_high_severity_detections
 40 | ```
 41 | 
 42 | > [!IMPORTANT]
 43 | > When running E2E tests with verbose output, the `-s` flag is **required** to see any meaningful output.
 44 | > This is because pytest normally captures stdout/stderr, and our tests output information via print statements.
 45 | > Without the `-s` flag, you won't see any of the detailed output, even with `-v` or `-vv` flags.
 46 | 
 47 | ## Verbose Output
 48 | 
 49 | The E2E tests support different levels of verbosity, but **all require the `-s` flag** to display detailed output:
 50 | 
 51 | ### Standard Output (No Verbosity)
 52 | 
 53 | By default, tests run with minimal output and the agent runs silently:
 54 | 
 55 | ```bash
 56 | pytest --run-e2e -s tests/e2e/
 57 | ```
 58 | 
 59 | ### Verbose Output
 60 | 
 61 | To see more detailed output, including basic agent debug information, use both `-v` and `-s` flags:
 62 | 
 63 | ```bash
 64 | pytest --run-e2e -v -s tests/e2e/
 65 | ```
 66 | 
 67 | With this level of verbosity, you'll see:
 68 | 
 69 | - Test execution progress
 70 | - Basic agent operations
 71 | - Tool calls and responses
 72 | - Test success/failure information
 73 | 
 74 | ### Extra Verbose Output
 75 | 
 76 | For even more detailed output, including all agent events and detailed debugging information:
 77 | 
 78 | ```bash
 79 | pytest --run-e2e -vv -s tests/e2e/
 80 | ```
 81 | 
 82 | This level shows everything from the verbose level plus:
 83 | 
 84 | - Detailed agent thought processes
 85 | - Step-by-step execution flow
 86 | - Complete prompt and response content
 87 | - Detailed tool execution information
 88 | 
 89 | > [!NOTE]
 90 | > The `-s` flag disables pytest's output capture, allowing all print statements to be displayed.
 91 | > Without this flag, you won't see any of the detailed output from the tests.
 92 | >
 93 | > The verbosity level (`-v`, `-vv`) controls both test output verbosity AND agent debug output.
 94 | > Higher verbosity levels are extremely useful when diagnosing test failures or unexpected agent behavior.
 95 | 
 96 | ## Test Retry Logic
 97 | 
 98 | The E2E tests use a retry mechanism to handle the non-deterministic nature of LLM responses. Each test is run multiple times against different models, and the test passes if a certain percentage of runs succeed.
 99 | 
100 | The retry configuration can be found at the top of `tests/e2e/utils/base_e2e_test.py`:
101 | 
102 | ```python
103 | # Default models to test against
104 | DEFAULT_MODLES_TO_TEST = ["gpt-4.1-mini", "gpt-4o-mini"]
105 | # Default number of times to run each test
106 | DEFAULT_RUNS_PER_TEST = 2
107 | # Default success threshold for passing a test
108 | DEFAULT_SUCCESS_TRESHOLD = 0.7
109 | ```
110 | 
111 | This means each test will run 2 times for each model and the test will pass if at least 70% of the runs succeed.
112 | 
113 | Each of these can be overridden by using the appropriate environment variable:
114 | 
115 | - MODELS_TO_TEST
116 | - RUNS_PER_TEST
117 | - SUCCESS_THRESHOLD
118 | 
119 | For example:
120 | 
121 | ```bash
122 | # Test with Claude models
123 | MODELS_TO_TEST=example-model-1,example-model-2 pytest --run-e2e -s tests/e2e/
124 | ```
125 | 
126 | ## Troubleshooting
127 | 
128 | ### Not Seeing Any Output?
129 | 
130 | If you're running tests with `-v` but not seeing any detailed output, make sure you've included the `-s` flag:
131 | 
132 | ```bash
133 | # CORRECT: Will show detailed output
134 | pytest --run-e2e -v -s tests/e2e/
135 | 
136 | # INCORRECT: Will not show detailed output
137 | pytest --run-e2e -v tests/e2e/
138 | ```
139 | 
140 | ### Diagnosing Test Failures
141 | 
142 | If a test is failing, try running it with full debug output (`-v -s` or `-vv -s` flags) to see what's happening. Look for:
143 | 
144 | 1. Connection issues with the MCP server
145 | 2. Unexpected LLM responses
146 | 3. Assertion failures in the test logic
147 | 4. Agent debugging information (enabled with verbosity)
148 | 
149 | The verbose output will show you the exact prompts, responses, and tool calls, which can help diagnose the issue. With higher verbosity levels, you'll also see detailed agent debugging information that can help identify why the agent isn't behaving as expected.
150 | 
151 | ### Using Custom LLM Endpoints
152 | 
153 | If you need to use a custom LLM endpoint (e.g., for VPN-only accessible models), set the `OPENAI_BASE_URL` environment variable:
154 | 
155 | ```bash
156 | # Use a custom LLM endpoint
157 | OPENAI_BASE_URL=https://your-custom-llm-endpoint.com/v1 pytest --run-e2e -s tests/e2e/
158 | ```
159 | 
160 | This is particularly useful when testing with models that are only accessible through specific endpoints or when using a proxy server.
161 | 
```

--------------------------------------------------------------------------------
/tests/modules/test_incidents.py:
--------------------------------------------------------------------------------

```python
  1 | """
  2 | Tests for the Incidents module.
  3 | """
  4 | 
  5 | import unittest
  6 | 
  7 | from falcon_mcp.modules.incidents import IncidentsModule
  8 | from tests.modules.utils.test_modules import TestModules
  9 | 
 10 | 
 11 | class TestIncidentsModule(TestModules):
 12 |     """Test cases for the Incidents module."""
 13 | 
 14 |     def setUp(self):
 15 |         """Set up test fixtures."""
 16 |         self.setup_module(IncidentsModule)
 17 | 
 18 |     def test_register_tools(self):
 19 |         """Test registering tools with the server."""
 20 |         expected_tools = [
 21 |             "falcon_show_crowd_score",
 22 |             "falcon_get_incident_details",
 23 |             "falcon_search_incidents",
 24 |             "falcon_get_behavior_details",
 25 |             "falcon_search_behaviors",
 26 |         ]
 27 |         self.assert_tools_registered(expected_tools)
 28 | 
 29 |     def test_register_resources(self):
 30 |         """Test registering resources with the server."""
 31 |         expected_resources = [
 32 |             "falcon_show_crowd_score_fql_guide",
 33 |             "falcon_search_incidents_fql_guide",
 34 |             "falcon_search_behaviors_fql_guide",
 35 |         ]
 36 |         self.assert_resources_registered(expected_resources)
 37 | 
 38 |     def test_crowd_score(self):
 39 |         """Test querying CrowdScore with successful response."""
 40 |         # Setup mock response with sample scores
 41 |         mock_response = {
 42 |             "status_code": 200,
 43 |             "body": {
 44 |                 "resources": [
 45 |                     {"id": "score1", "score": 50, "adjusted_score": 60},
 46 |                     {"id": "score2", "score": 70, "adjusted_score": 80},
 47 |                 ]
 48 |             },
 49 |         }
 50 |         self.mock_client.command.return_value = mock_response
 51 | 
 52 |         # Call crowd_score with test parameters
 53 |         result = self.module.show_crowd_score(
 54 |             filter="test filter",
 55 |             limit=100,
 56 |             offset=0,
 57 |             sort="modified_timestamp.desc",
 58 |         )
 59 | 
 60 |         # Verify client command was called correctly
 61 |         self.mock_client.command.assert_called_once_with(
 62 |             "CrowdScore",
 63 |             parameters={
 64 |                 "filter": "test filter",
 65 |                 "limit": 100,
 66 |                 "offset": 0,
 67 |                 "sort": "modified_timestamp.desc",
 68 |             },
 69 |         )
 70 | 
 71 |         # Verify result contains expected values
 72 |         self.assertEqual(result["average_score"], 60)  # (50 + 70) / 2
 73 |         self.assertEqual(result["average_adjusted_score"], 70)  # (60 + 80) / 2
 74 |         self.assertEqual(len(result["scores"]), 2)
 75 |         self.assertEqual(result["scores"][0]["id"], "score1")
 76 |         self.assertEqual(result["scores"][1]["id"], "score2")
 77 | 
 78 |     def test_crowd_score_empty_response(self):
 79 |         """Test querying CrowdScore with empty response."""
 80 |         # Setup mock response with empty resources
 81 |         mock_response = {"status_code": 200, "body": {"resources": []}}
 82 |         self.mock_client.command.return_value = mock_response
 83 | 
 84 |         # Call crowd_score
 85 |         result = self.module.show_crowd_score()
 86 | 
 87 |         # Verify client command was called with the correct operation
 88 |         self.assertEqual(self.mock_client.command.call_count, 1)
 89 |         call_args = self.mock_client.command.call_args
 90 |         self.assertEqual(call_args[0][0], "CrowdScore")
 91 | 
 92 |         # Verify result contains expected default values
 93 |         self.assertEqual(result["average_score"], 0)
 94 |         self.assertEqual(result["average_adjusted_score"], 0)
 95 |         self.assertEqual(result["scores"], [])
 96 | 
 97 |     def test_crowd_score_error(self):
 98 |         """Test querying CrowdScore with API error."""
 99 |         # Setup mock response with error
100 |         mock_response = {
101 |             "status_code": 400,
102 |             "body": {"errors": [{"message": "Invalid query"}]},
103 |         }
104 |         self.mock_client.command.return_value = mock_response
105 | 
106 |         # Call crowd_score
107 |         result = self.module.show_crowd_score(filter="invalid query")
108 | 
109 |         # Verify result contains error
110 |         self.assertIn("error", result)
111 |         self.assertIn("details", result)
112 |         # Check that the error message starts with the expected prefix
113 |         self.assertTrue(result["error"].startswith("Failed to perform operation"))
114 | 
115 |     def test_crowd_score_with_default_parameters_and_rounding(self):
116 |         """Test querying CrowdScore with default parameters and rounding"""
117 |         # Setup mock response
118 |         mock_response = {
119 |             "status_code": 200,
120 |             "body": {
121 |                 "resources": [
122 |                     {"id": "score1", "score": 30, "adjusted_score": 40},
123 |                     {"id": "score1", "score": 31, "adjusted_score": 41},
124 |                 ]
125 |             },
126 |         }
127 |         self.mock_client.command.return_value = mock_response
128 | 
129 |         # Call crowd_score with no parameters (using defaults)
130 |         result = self.module.show_crowd_score()
131 | 
132 |         # Verify client command was called with the correct operation
133 |         self.assertEqual(self.mock_client.command.call_count, 1)
134 |         call_args = self.mock_client.command.call_args
135 |         self.assertEqual(call_args[0][0], "CrowdScore")
136 | 
137 |         # Verify result
138 |         self.assertEqual(result["average_score"], 30)
139 |         self.assertEqual(result["average_adjusted_score"], 40)
140 | 
141 | 
142 | if __name__ == "__main__":
143 |     unittest.main()
144 | 
```

--------------------------------------------------------------------------------
/tests/e2e/modules/test_serverless.py:
--------------------------------------------------------------------------------

```python
  1 | """
  2 | E2E tests for the Serverless module.
  3 | """
  4 | 
  5 | import unittest
  6 | 
  7 | import pytest
  8 | 
  9 | from tests.e2e.utils.base_e2e_test import BaseE2ETest, ensure_dict
 10 | 
 11 | 
 12 | @pytest.mark.e2e
 13 | class TestServerlessModuleE2E(BaseE2ETest):
 14 |     """
 15 |     End-to-end test suite for the Falcon MCP Server Serverless Module.
 16 |     """
 17 | 
 18 |     def test_search_serverless_vulnerabilities(self):
 19 |         """Verify the agent can search for high severity vulnerabilities in serverless environment"""
 20 | 
 21 |         async def test_logic():
 22 |             response = {
 23 |                 "status_code": 200,
 24 |                 "body": {
 25 |                     "runs": [
 26 |                         {
 27 |                             "tool": {
 28 |                                 "driver": {
 29 |                                     "name": "CrowdStrike",
 30 |                                     "informationUri": "https://www.crowdstrike.com/",
 31 |                                     "rules": [
 32 |                                         {
 33 |                                             "id": "CVE-2023-45678",
 34 |                                             "name": "PythonPackageVulnerability",
 35 |                                             "shortDescription": {"text": "Security vulnerability in package xyz"},
 36 |                                             "fullDescription": {"text": "A critical vulnerability was found in package xyz that could lead to remote code execution"},
 37 |                                             "help": {"text": "Package: xyz\nInstalled Version: 1.2.3\nVulnerability: CVE-2023-45678\nSeverity: HIGH\nRemediation: [Upgrade to version 2.0.0]"},
 38 |                                             "properties": {
 39 |                                                 "severity": "HIGH",
 40 |                                                 "cvssBaseScore": 8.5,
 41 |                                                 "remediations": ["Upgrade to version 2.0.0"],
 42 |                                                 "cloudProvider": "AWS",
 43 |                                                 "region": "us-west-2",
 44 |                                                 "functionName": "sample-lambda-function"
 45 |                                             }
 46 |                                         }
 47 |                                     ]
 48 |                                 }
 49 |                             }
 50 |                         }
 51 |                     ]
 52 |                 },
 53 |             }
 54 | 
 55 |             fixtures = [
 56 |                 {
 57 |                     "operation": "GetCombinedVulnerabilitiesSARIF",
 58 |                     "validator": lambda kwargs: "severity:'HIGH'+cloud_provider:'aws'" in kwargs.get("parameters", {}).get("filter", ""),
 59 |                     "response": response,
 60 |                 },
 61 |                 {
 62 |                     "operation": "GetCombinedVulnerabilitiesSARIF",
 63 |                     "validator": lambda kwargs: "cloud_provider:'aws'+severity:'HIGH'" in kwargs.get("parameters", {}).get("filter", ""),
 64 |                     "response": response,
 65 |                 }
 66 |             ]
 67 | 
 68 |             self._mock_api_instance.command.side_effect = (
 69 |                 self._create_mock_api_side_effect(fixtures)
 70 |             )
 71 | 
 72 |             prompt = "get vulnerabilities in my serverless environment with severity high only and part of AWS"
 73 |             return await self._run_agent_stream(prompt)
 74 | 
 75 |         def assertions(tools, result):
 76 |             tool_names_called = [tool["input"]["tool_name"] for tool in tools]
 77 |             self.assertIn("falcon_serverless_vulnerabilities_fql_guide", tool_names_called)
 78 |             self.assertIn("falcon_search_serverless_vulnerabilities", tool_names_called)
 79 | 
 80 |             # Find the search_serverless_vulnerabilities tool call
 81 |             search_tool_call = None
 82 |             for tool in tools:
 83 |                 if tool["input"]["tool_name"] == "falcon_search_serverless_vulnerabilities":
 84 |                     search_tool_call = tool
 85 |                     break
 86 |             
 87 |             self.assertIsNotNone(search_tool_call, "Expected falcon_search_serverless_vulnerabilities tool to be called")
 88 | 
 89 |             # # Verify the tool input contains the filter parameter with proper FQL syntax
 90 |             tool_input = ensure_dict(search_tool_call["input"]["tool_input"])
 91 |             self.assertIn("filter", tool_input, "Tool input should contain a 'filter' parameter")
 92 |             self.assertIn("severity:'HIGH'", tool_input.get("filter", ""), "Filter should contain severity:'HIGH' in FQL syntax")
 93 | 
 94 |             # # Verify API call parameters
 95 |             self.assertGreaterEqual(
 96 |                 self._mock_api_instance.command.call_count,
 97 |                 1,
 98 |                 "Expected at least 1 API call",
 99 |             )
100 |             api_call_args = self._mock_api_instance.command.call_args_list[0]
101 |             self.assertEqual(api_call_args[0][0], "GetCombinedVulnerabilitiesSARIF")
102 |             api_call_params = api_call_args[1].get("parameters", {})
103 |             self.assertIn("severity:'HIGH'", api_call_params.get("filter", ""))
104 |             self.assertIn("cloud_provider:'aws'", api_call_params.get("filter", ""))
105 | 
106 |         self.run_test_with_retries(
107 |             "test_search_serverless_vulnerabilities", test_logic, assertions
108 |         )
109 | 
110 | 
111 | if __name__ == "__main__":
112 |     unittest.main()
113 | 
```
Page 1/5FirstPrevNextLast