Sync upstream and fix CrowdStrike detection-ID and host-FQL error handling - #29
Open
gdandu-uptycs wants to merge 74 commits into
Open
Sync upstream and fix CrowdStrike detection-ID and host-FQL error handling#29gdandu-uptycs wants to merge 74 commits into
gdandu-uptycs wants to merge 74 commits into
Conversation
…#364) * test(compliance): add MCP protocol compliance test suite Adds tests/test_mcp_compliance.py covering MCP spec revision 2025-06-18 and JSON-RPC 2.0 conformance. Seven tests: - streamable-http Origin header validation (security) - tools/list immutability across sessions (rug-pull guard) - JSON-RPC error code conformance (-32601, -32602) - Mcp-Session-Id binding and entropy - Capability negotiation honesty - Resource URI format compliance - Read-only tool annotation correctness across modules Output schema conformance deferred to a follow-up PR. Resolves CrowdStrike#235. * refactor(test): align MCP compliance tests with project conventions - Convert bare pytest functions to unittest.TestCase / IsolatedAsyncioTestCase classes to match the style used across all other test files - Make MUTATING_TOOL_ALLOWLIST bidirectional: assert no ghost entries (deleted tools), no stale entries (tools reverted to read-only), and no unlisted mutating tools - Add starlette and sse-starlette to dev dependencies (were transitive only) - Use mcp.types.LATEST_PROTOCOL_VERSION instead of hardcoded revision string - Reset AppStatus.should_exit alongside should_exit_event in setUp/tearDown - Replace _accept_headers() function with ACCEPT_HEADERS constant - Type _parse_jsonrpc response parameter as httpx.Response - Remove from __future__ import annotations (no other test file uses it) - Remove section separator comment blocks (not used elsewhere in tests/) - Remove redundant test_all_tools_have_annotations from test_server.py (strict superset coverage now lives in the compliance suite) --------- Co-authored-by: Carlos Matos <carlos.matos@crowdstrike.com>
…rowdStrike#379) When authentication fails, the error message now includes the HTTP status code and reason from the CrowdStrike API response, plus a troubleshooting hint tailored to the failure mode (bad credentials, invalid member_cid, disabled scopes, or network/URL issues). Closes CrowdStrike#351
…udgets (CrowdStrike#376) * fix(tools): omit outputSchema from tools/list to fit client context budgets FastMCP auto-derives outputSchema from each tool's return type. With 67 registered tools, the resulting tools/list payload exceeds the per-tool context budget that VS Code Copilot (and likely other clients) applies when injecting tool definitions into the model context, so tools are silently dropped from both the model and the UI tool picker. outputSchema is intended for post-call structured output validation per MCP 2025-11-25, not for tool selection. Passing structured_output=False to every server.add_tool() call leaves output_schema=None on each Tool, which Pydantic excludes from the JSON-RPC payload (exclude_none=True). Fixes CrowdStrike#325. * test(tools): assert tools/list omits outputSchema for every registered tool Regression coverage for issue CrowdStrike#325. Three assertions: - tool.outputSchema is None for every tool returned by list_tools() - inputSchema is still emitted (guards against an over-broad fix) - the wire serialization (model_dump_json with exclude_none=True, matching the options used by the MCP session layer) does not contain an outputSchema key for any tool * refactor(tools): address review feedback on outputSchema fix Remove verbose inline comments that duplicate git history context. Replace redundant wire-format test with payload size budget assertion (110KB threshold). Add unit test verifying _add_tool forwards structured_output=False. File follow-up issue CrowdStrike#380 for description trimming. --------- Co-authored-by: Carlos Matos <carlos.matos@crowdstrike.com>
Convert async test methods in TestNGSIEMModule to synchronous wrappers using asyncio.run(). The async def + @pytest.mark.asyncio pattern had no effect because the class inherits from unittest.TestCase (via TestModules), causing all 9 async tests to be silently skipped on every run. Also fix a latent assertion in test_search_ngsiem_missing_job_id that expected a 'details' key which _format_error_response omits when passed an empty dict. Closes CrowdStrike#375
…rowdStrike#385) Replace verbose multi-paragraph FQL boilerplate and inconsistent one-liners with a researched 2-4 sentence standard covering what the tool does, when to use it, and what it returns. FQL search tools now steer models to consult the corresponding fql-guide resource before constructing filters. Removes unused EMBEDDED_FQL_SYNTAX imports from detections and rtr modules. Fixes inaccurate return claims identified through live API validation. Closes CrowdStrike#380
* feat(modules/cases): add Case Management module (CrowdStrike#386) Add a new cases module replacing the deprecated Incidents module, providing LLM-facing tools for case lifecycle management via the CrowdStrike Case Management API (FalconPy CaseManagement class). Tools: - falcon_search_cases — two-step query+get search with FQL filtering - falcon_get_cases — retrieve cases by known IDs - falcon_create_case — create case (PUT, supports inline evidence) - falcon_update_case — update case fields with optimistic concurrency - falcon_add_case_alert_evidence — attach alert composite IDs - falcon_add_case_event_evidence — attach LogScale event IDs - falcon_manage_case_tags — add/remove tags (asymmetric POST/DELETE) - falcon_list_case_templates — two-step template query+get Key implementation details: - Evidence IDs converted to object format [{"id": "..."}] per API spec - update_case nests fields under "fields" key (flat body silently no-ops) - delete_case_tags uses DELETE query params, not POST body - get_templates uses use_params=True (GET endpoint) - 40+ FQL filter fields validated against live API Closes CrowdStrike#386 * docs(modules/cases): use natural workflow-style example prompts Rewrites case tool examples to sound like real analyst questions rather than generic descriptions. Avoids numeric severity in favor of named levels (high, critical).
* chore(main): release 0.10.0 * chore: sync gemini-extension.json version to 0.10.0 * chore: sync server.json version to 0.10.0 * chore: update uv.lock --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
…riteria (CrowdStrike#396) * fix(modules/idp): resolve FieldInfo defaults before building search_criteria * refactor(modules/idp): extract unwrap_field_default utility and add zero-arg test DRY extraction: move FieldInfo unwrapping logic from nested function and inline hasattr check into a shared utility in common/utils.py. Add test coverage for the zero-argument call path to verify no FieldInfo leak. --------- Co-authored-by: Carlos Matos <carlos.matos@crowdstrike.com>
…CrowdStrike#397) * fix(modules/cloud): return int count from count_kubernetes_containers * fix(modules/cloud): handle edge cases in count extraction - default_result=0 so empty responses return int, not [] - fix copy-paste bug in error test (was calling search_ not count_) - guard against null count values from API - use specific error message for this operation * docs(modules/cloud): add returns sentence to count tool description * test(modules/cloud): remove issue reference from test docstring --------- Co-authored-by: Carlos Matos <carlos.matos@crowdstrike.com>
…CrowdStrike#391) * feat(modules/correlation-rules): add NG-SIEM Correlation Rules module Adds a new module exposing CrowdStrike Correlation Rules API (NG-SIEM detection rules) via 8 MCP tools: - falcon_search_correlation_rules — FQL-filtered combined search (read) - falcon_get_correlation_rules — fetch rules by ID (read) - falcon_create_correlation_rule — create a new CQL-based rule (write) - falcon_update_correlation_rule — update rule fields (write) - falcon_delete_correlation_rules — delete rules by ID (destructive) - falcon_publish_correlation_rule — promote a draft version to live (write) - falcon_export_correlation_rules — export rules as JSON/YAML (read) - falcon_import_correlation_rule — import a rule definition (write) Also adds: - falcon://correlation-rules/fql-guide resource with FQL field reference - API scope mappings for all 8 operations under "Correlation Rules:read/write" Module is auto-discovered by the registry; no changes to server.py required. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * refactor(modules/correlation-rules): rewrite module based on live API validation Rewrites the Correlation Rules module from 8 tools down to 4 after live API testing revealed several broken endpoints and incorrect payload formats. Key fixes: - Add customer_id to create body (was causing 401) - Remove execution_mode/use_ingest_time from create search object (read-only fields) - Fix severity to enumerated values (10/30/50/70/90) - Change default outcome from deprecated "incident" to "detection" - Remove redundant get tool (search already returns full details) - Remove publish/export/import tools (async/multipart/unnecessary) - Validate FQL filters against live API, remove broken fields - PATCH body must be a list (APIHarnessV2 does not auto-wrap) --------- Co-authored-by: Sahil Sharma <sahilsharma@ip-172-19-22-57.eu-west-1.compute.internal> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: Carlos Matos <carlos.matos@crowdstrike.com>
…rowdStrike#407) Replace deprecated tactic/technique params with mitre_attack array, fix trigger_mode from invalid per_event to verbose, and add use_ingest_time parameter to the search block.
) * feat(modules/rtr): add audit and command wait workflows * fix(modules/rtr): read sequence_id from API response in polling loop The RTR_CheckCommandStatus API returns a sequence_id field in each response that the caller must use for the next poll. The previous implementation naively incremented the counter, which is incorrect per the Falcon API docs and live API validation. * fix(docs): regenerate module docs with correct tool names The extract_registered_tool_names() fix added in this PR also corrects pre-existing wrong tool names in the idp and intel docs (method names were used instead of registered MCP tool names). Regenerated all module docs to pick up the corrections. * fix(modules/rtr): align resource URIs with project convention and add to allowlist Resource URIs must match the falcon://{module}/{path}/{kind}-guide pattern enforced by the MCP compliance test suite. Also adds run_rtr_read_only_command_and_wait to MUTATING_TOOL_ALLOWLIST since it initiates RTR commands (non-read-only annotation is correct). --------- Co-authored-by: Kenneth Lund <nek9505@icloud.com> Co-authored-by: Carlos Matos <carlos.matos@crowdstrike.com>
* feat(modules/quarantine): add quarantine workflows * refactor(modules/quarantine): simplify tool surface and align with project standards Rewrites the quarantine module from the original 7-tool PR down to 4 tools with a cleaner interface that follows established codebase patterns. - Unified update/delete tools (ids OR filter, not separate _by_ids/_by_filter) - Renamed preview_quarantine_action_counts to count_quarantine_actions - Removed get_quarantined_file_details (search already returns full details) - Removed q parameter (FQL filter covers all discovery needs) - Fixed annotations (idempotentHint=True on delete, inlined for docs generator) - Fixed docstrings to what/when/Consult/Returns standard - Fixed filter descriptions to concise pointer format - Added empty-list and empty-filter guards - Added MUTATING_TOOL_ALLOWLIST entries - Added TOOL_EXAMPLES and regenerated docs - Validated all operations against live API --------- Co-authored-by: KenUdigIT710 <nek9505@icloud.com> Co-authored-by: Carlos Matos <carlos.matos@crowdstrike.com>
…ions (CrowdStrike#408) Users in enterprise/restricted network environments can now route Falcon API traffic through an HTTP/HTTPS proxy via the --proxy CLI flag or FALCON_PROXY_URL environment variable. Closes CrowdStrike#405
Bumps [python-dotenv](https://github.com/theskumar/python-dotenv) from 1.2.1 to 1.2.2. - [Release notes](https://github.com/theskumar/python-dotenv/releases) - [Changelog](https://github.com/theskumar/python-dotenv/blob/main/CHANGELOG.md) - [Commits](theskumar/python-dotenv@v1.2.1...v1.2.2) --- updated-dependencies: - dependency-name: python-dotenv dependency-version: 1.2.2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [black](https://github.com/psf/black) from 26.1.0 to 26.5.1. - [Release notes](https://github.com/psf/black/releases) - [Changelog](https://github.com/psf/black/blob/main/CHANGES.md) - [Commits](psf/black@26.1.0...26.5.1) --- updated-dependencies: - dependency-name: black dependency-version: 26.3.1 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…ike#324) Bumps [crowdstrike-falconpy](https://github.com/CrowdStrike/falconpy) from 1.6.0 to 1.6.2. - [Release notes](https://github.com/CrowdStrike/falconpy/releases) - [Changelog](https://github.com/CrowdStrike/falconpy/blob/main/CHANGELOG.md) - [Commits](CrowdStrike/falconpy@v1.6.0...v1.6.2) --- updated-dependencies: - dependency-name: crowdstrike-falconpy dependency-version: 1.6.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.14.14 to 0.15.14. - [Release notes](https://github.com/astral-sh/ruff/releases) - [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md) - [Commits](astral-sh/ruff@0.14.14...0.15.14) --- updated-dependencies: - dependency-name: ruff dependency-version: 0.15.11 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [mypy](https://github.com/python/mypy) from 1.19.1 to 2.1.0. - [Changelog](https://github.com/python/mypy/blob/master/CHANGELOG.md) - [Commits](python/mypy@v1.19.1...v2.1.0) --- updated-dependencies: - dependency-name: mypy dependency-version: 1.20.1 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…#345) Bumps [actions/checkout](https://github.com/actions/checkout) from 5.0.0 to 6.0.2. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](actions/checkout@08c6903...de0fac2) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: 6.0.2 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…rike#369) Bumps [actions/setup-python](https://github.com/actions/setup-python) from 5.6.0 to 6.2.0. - [Release notes](https://github.com/actions/setup-python/releases) - [Commits](actions/setup-python@a26af69...a309ff8) --- updated-dependencies: - dependency-name: actions/setup-python dependency-version: 6.2.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…dStrike#347) Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4.6.2 to 7.0.1. - [Release notes](https://github.com/actions/upload-artifact/releases) - [Commits](actions/upload-artifact@ea165f8...043fb46) --- updated-dependencies: - dependency-name: actions/upload-artifact dependency-version: 7.0.1 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…o 23.2.0 (CrowdStrike#371) deps(actions): bump DavidAnson/markdownlint-cli2-action Bumps [DavidAnson/markdownlint-cli2-action](https://github.com/davidanson/markdownlint-cli2-action) from 20.0.0 to 23.2.0. - [Release notes](https://github.com/davidanson/markdownlint-cli2-action/releases) - [Commits](DavidAnson/markdownlint-cli2-action@992badc...ded1f94) --- updated-dependencies: - dependency-name: DavidAnson/markdownlint-cli2-action dependency-version: 23.2.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
) The previous Dockerfile used the `ghcr.io/astral-sh/uv:python3.13-alpine` combined image as the build stage. When Dependabot bumps the digest, it resolves to the bare distroless uv image (no /bin/sh), breaking arm64 builds. Similarly, the runtime python image pin drifted from Alpine to Debian, breaking the `adduser -D` syntax. Switch to a 3-stage build: COPY only the /uv binary from the distroless image into a python:3.13-alpine builder stage. This decouples the uv version bump from shell availability and keeps both images pinned to their correct Alpine variants.
The docs site is moving to a separate repository so these dependency updates are no longer applicable here.
Reduces PR noise from ~12 individual PRs to max 3 per month (one grouped PR per ecosystem: python, actions, docker).
…e#429) get_mitre_report with format='json' returned a raw JSON string instead of parsed Python objects. FalconPy's GetMitreReport returns bytes for both CSV and JSON, and the decoded JSON string was passed through without parsing. Parse JSON responses into a list of dicts, handle the b'null' sentinel (actors with no MITRE mappings) by returning an empty list, and guard malformed payloads with a structured error. CSV format still returns raw text. Closes CrowdStrike#383
) docs(usage): rename dynamic-mode.md and fix URL slug to /usage/dynamic-mode/ Reverts the rename from PR CrowdStrike#451 — the file is restored to dynamic-mode.md so the Astro site builds to /falcon-mcp/usage/dynamic-mode/ as intended. Updates the three cross-references in README.md, docs/usage/cli.md, and docs/getting-started/configuration.md to match the corrected slug.
…oldown (CrowdStrike#454) * revert(deps): remove constraint-dependencies block, add dependabot cooldown Prodsec flagged the [tool.uv] constraint-dependencies block added in CrowdStrike#447 as introducing package version bumps outside the normal vetting process. Removes the block entirely; the starlette>=1.3.1 floor in dev dependencies stays as it was a legitimate dependabot bump. Adds a 7-day cooldown to all three dependabot ecosystems (uv, github-actions, docker) so new releases sit for a week before a bump PR is opened, giving time for internal vetting. uv.lock regeneration left to CI. * regenerate lock file
…e#446) * feat(modules/recon): add Falcon Intelligence Recon module Adds a new ReconModule covering Falcon Intelligence Recon — dark web monitoring, leaked credentials, typosquatting detection, and breach notifications. Ships three read-only search tools following the established two-step GET pattern (query IDs → fetch full details via GET query params): - falcon_search_recon_notifications: search and retrieve full notification details including breach summaries and item metadata; disambiguated from endpoint detections in the tool description ("also called recon alerts") - falcon_search_recon_rules: list and inspect monitoring rules by topic, priority, and status - falcon_search_recon_exposed_data_records: retrieve leaked credential and PII rows associated with notifications FQL guides validated against the live API — confirmed field values include SA_DOMAIN/SA_TYPOSQUATTING (uppercase), status:'active' for rules, and newly_reported/previously_reported/confirmed_active for credential status. Documents that breach_summary.credential_statuses causes a 400 FQL parse failure and that typosquatting.* sub-fields have unconfirmed queryability on QueryNotificationsV1. Adds dynamic mode filter hints, API scope entries, TOOL_EXAMPLES for doc generation, and an alphabetical README row. All 6 FalconPy operations verified against the installed SDK; 14 live integration tests confirm correct operation names and GET-with-params response shape. * docs(modules/recon): add Counter Adversary Operations (CAO) to all tool descriptions Fixes a typo in the existing CAO reference in search_recon_notifications and adds consistent CAO mentions to search_recon_rules and search_recon_exposed_data_records so prompts using "Counter Adversary Operations" or "CAO" surface all three tools. Regenerates docs/modules/recon.md. * chore(docs): update example prompts for recon exposed-data tool * docs(modules/recon): restore falcon_search_detections cross-reference in notifications tool The disambiguation hint was present in the original committed docs but never in the source docstring, so it was silently dropped when docs were regenerated. Moving it into the docstring so it survives future regeneration runs. * docs(modules): disambiguate alert/detection/notification semantics falcon_search_detections now explicitly owns the generic term "alert" and calls out coverage across EPP, IDP, XDR, OverWatch, and NG-SIEM. Recon no longer competes for bare "alert" or "detection" — "typosquatting detections" becomes "typosquatting matches" and the cross-reference to falcon_search_detections is tightened to name specific products (endpoint, XDR, NG-SIEM). * docs(modules/detections): restore attribute keywords in search_detections docstring First pass over-generalized by dropping severity/status/hostname/time range keywords. Restored them alongside the new product-coverage sentence and alert synonym so all three routing signals are present.
…rowdStrike#455) Three transitive dependencies in the current lock are below their security fix versions: pyjwt 2.10.1 (CVE-2026-32597), idna 3.10 (CVE-2026-45409), and requests 2.32.4 (CVE-2026-25645). The other packages flagged in the same advisory scan (cryptography, python- multipart, starlette, urllib3) are already at or above their safe floor and need no change. Adds a [tool.uv] constraint-dependencies block with minimum version floors for the three affected packages. All are transitive with uncapped parents, so the resolver already picks safe versions on a fresh lock; the constraints prevent future re-resolves from quietly regressing below the safe floor. uv.lock regeneration is left to CI so only pyproject.toml is committed here.
* chore(main): release 0.13.0 * chore: sync gemini-extension.json version to 0.13.0 * chore: sync server.json version to 0.13.0 * chore: update uv.lock * chore: sync docs changelog --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
…ncies (CrowdStrike#456) The E2E testing framework (langchain_openai + mcp_use against a live LLM agent) has been unused and was pulling in 55+ transitive packages, widening the dependency attack surface unnecessarily. Removes the tests/e2e/ directory, the CI workflow, the HTML report scripts, and the e2e-testing.md doc. Drops langchain-openai and mcp-use[search] from dev dependencies along with the now-dead langchain_core filterwarnings entry. Strips the internal scan tool names, project/ticket references, and CVE identifiers from the [tool.uv] constraint-dependencies comments while keeping the version floor constraints themselves.
fix(deps): add cryptography floor constraint for CVE-2026-34180
…s, and falcon_get_cloud_groups tools (CrowdStrike#467) * feat(modules/cloud): add cloud risks FQL resource documentation * feat(modules/cloud): add falcon_search_cloud_risks tool * fix(modules/cloud): address code quality review findings - Fix pyproject.toml: replace broken packages=["falcon_mcp"] with find: include=["falcon_mcp*"] to correctly include all subpackages while excluding the plans/ directory from distribution - Add combined_cloud_risks scope to api_scopes.py ("Cloud Security API Risks:read") - Fix CLOUD_RISKS_FQL_DOCUMENTATION to use === sentinel === heading format consistent with all other FQL documentation in resources/cloud.py - Remove duplicate test_register_tools_includes_cloud_risks and test_register_resources_includes_cloud_risks_fql methods which duplicated assertions already present in test_register_tools and test_register_resources Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * feat(modules/cloud): add falcon_search_cloud_groups and falcon_get_cloud_groups tools Implements ListCloudGroupsExternal and ListCloudGroupsByIDExternal operations as MCP tools with full unit test coverage; registers API scopes for both. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * test(modules/cloud): add integration tests for cloud risks and cloud group tools Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * docs(modules/cloud): add cloud risks tools to module docs and README Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * chore(modules/cloud): fix linting and type errors - Fix import sort order in scripts/scope_preflight_cloud_risks.py (ruff I001) - Add falcon_search_cloud_risks entry to FILTER_HINTS so dynamic mode provides inline FQL field hints for the new tool - Fix test_search_cspm_assets_with_tag_filter to accept empty dict response when the filter returns zero assets (consistent with other tests in the suite) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * fix(modules/cloud): address feature-review findings in FQL guide and filter hints Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * fix(modules/cloud): strengthen unit test assertions and add cloud groups filter hint Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * fix(modules/cloud): fix filter hint sentence separator for threat_actors field * docs(modules/cloud): clarify iom_findings vs cloud_risks tool selection boundary Add symmetric cross-references between falcon_search_iom_findings and falcon_search_cloud_risks so agents can distinguish per-rule-per-resource violations (IOMs) from aggregated per-asset risk records (cloud risks). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * CR fixes * CR fixes * chore(modules/cloud): remove one-off scope preflight dev script Development scaffold used to verify cloud risks/groups API scopes during implementation. No longer referenced; scope coverage is captured in the integration tests and api_scopes.py. --------- Co-authored-by: Carlos Matos <carlos.matos@crowdstrike.com>
…e#463) Search tools query entity IDs with sort applied, then hydrate full details by ID. Some get-by-ID endpoints return entities in arbitrary order, discarding the requested sort. Add an idempotent BaseModule._reorder_by_ids helper and call it after hydration in every two-step search tool so results match the query-step order. Also correct the recon exposed-data-records sort docstring: exposure_date is a valid sort field and updated_date is not (verified against the API).
…Strike#460) The facet parameter on falcon_search_vulnerabilities only accepted a single string, forcing callers to make one request per detail block. The underlying combinedQueryVulnerabilities operation natively supports multiple facets in a single request, so widen the parameter to str | list[str] and forward it unchanged. Single-string usage remains backward compatible.
* chore(issues): update modules list The main version insert several new module. * chore(issues): add remaining modules to feature-request dropdown Add Case Management, Correlation Rules, Custom IOA, Policies, and Shield to the module area dropdown so all 24 registered modules are selectable. --------- Co-authored-by: Carlos Matos <carlos.matos@crowdstrike.com>
…d_report (CrowdStrike#464) (CrowdStrike#466) fix(scheduled_reports): send body as array in launch_scheduled_report (CrowdStrike#464) Co-authored-by: sanjibani <18418553+sanjibani@users.noreply.github.com>
* chore(main): release 0.14.0 * chore: sync gemini-extension.json version to 0.14.0 * chore: sync server.json version to 0.14.0 * chore: update uv.lock * chore: sync docs changelog --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Search tools now return a structured envelope with results, pagination (total/offset/limit/next), and filter_used, replacing the ad-hoc empty response shape. A new _base_search_with_meta captures body.meta.pagination before hydration discards it, and _build_pagination_envelope assembles the response consistently across all search modules. The envelope never synthesizes a total: when the API reports no pagination count, total is null rather than 0, so a non-null total always reflects a real value the API returned. Docstrings note when a tool cannot report a total (e.g. Shield's activity monitor), and the Shield resource pagination note is scoped to the one operation that omits it. The doc generator now handles reflowed multi-line description= literals so resource descriptions are no longer blanked, and the integration-test list-response helper tolerates a null total.
* chore(main): release 0.15.0 * chore: sync gemini-extension.json version to 0.15.0 * chore: sync server.json version to 0.15.0 * chore: update uv.lock * chore: sync docs changelog --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
… tools (CrowdStrike#474) * fix(modules): fold meta.next cursor into pagination.next for CSPM assets/IOM The CSPM assets and CSPM IOM endpoints return their next-page cursor at the top level of body.meta as meta.next, with no meta.pagination.after. The envelope builder only read the nested after key, so search_cspm_assets and search_iom_findings always reported pagination.next = null and callers could not page past the first response. Unify the cursor to a single pagination.next, reading from whichever spot the endpoint populated with deterministic precedence: meta.pagination.next, then meta.pagination.after, then top-level meta.next. _extract_pagination now folds the top-level cursor into the pagination dict at lowest precedence, and the builder honors a nested next as well as after. Closes CrowdStrike#473 * fix(modules): drop redundant offset input from dual-pagination search tools Six search tools exposed both offset and a cursor parameter, giving the model two mutually-exclusive ways to paginate. For CSPM in particular, offset and after are mutually exclusive and offset >= 10000 returns HTTP 400, so deep paging must use the cursor. Now that pagination.next reliably surfaces a real cursor, the offset input is redundant and ambiguous. Remove the offset input parameter from search_firewall_rules, search_firewall_rule_groups, search_iocs, search_vulnerabilities, search_cspm_assets, and search_shield_alerts, and point their docstrings at pagination.next as the single paging mechanism (last_id for Shield). The response envelope is unchanged — the API still echoes an informational offset key in its meta; we simply stop accepting and advertising offset as an input.
…dStrike#475) * docs(modules/ngsiem): add CQL authoring guidance to search tool The falcon_search_ngsiem tool executed CQL but gave agents nothing on how to build a valid query, so smaller models frequently emitted SPL/SQL-style syntax that the API silently free-text-matched. Enrich the query_string description and the tool docstring with the CQL pipe model, worked examples (tag filter, groupBy, sort), and links to the authoritative LogScale references. Guidance lives inline in the description rather than in a separate resource: in live testing on a smaller model, inline guidance produced valid CQL every time while a resource behind a pointer was never fetched. The docstring also notes that the API does not return detailed CQL parser errors, so queries should be built from the references up front rather than corrected from error feedback. * feat(modules/ngsiem): add CQL authoring guidance and steer models to it The falcon_search_ngsiem tool executes bring-your-own CQL, and smaller models were confidently producing invalid queries for anything beyond the simplest shapes. Add a workflow-oriented CQL guide as both an inline building-block description on query_string and a registered falcon://ngsiem/search/cql-guide resource, and inject the full guide into the tool's failure and empty-result paths so it reaches the model exactly when a query didn't work. Dynamic mode now carries a compact CQL hint on query_string, mirroring the FQL filter hints. The guide's examples are validated against the live NG-SIEM API and the CrowdStrike community-content corpus: regex uses field=/re/i (not =~), head() is oldest-first with tail() for recent, and distinct count uses the positional count(field, distinct=true) form. Also reword the repository parameter: available repositories depend on the tenant and its configuration, so it is a list of common examples rather than a closed set of valid options (xdr and custom repositories are accepted too).
* feat(server): offload blocking tool handlers to threads for concurrency Sync tool handlers ran inline on the asyncio event loop, so a single blocking FalconPy call froze the loop and serialized every other in-flight request. A single instance could only service one call at a time regardless of caller count. Offload each sync handler to a worker thread at the tool-registration boundary via an async functools.wraps wrapper, so a single instance now interleaves concurrent Falcon calls. FastMCP reads the tool schema through the wrapped signature, so parameters and descriptions are unchanged, and it detects the wrapper as async and awaits it off-loop. Already-async handlers (ngsiem) are skipped and instead await the new command_async, and a lock serializes the stale-token refresh so concurrent callers don't stampede the token endpoint. * docs(server): document thread-pool limits and pin them with tests Review follow-ups on the handler-offload change. The thread cap and cancellation semantics were load-bearing but undocumented, and two behaviors that reviewers had to rediscover are now covered by tests. Document on offload_to_thread that anyio's default limiter caps concurrency at 40 per event loop, so speedup stops tracking caller count past that point, and that the default abandon_on_cancel=False means a cancelled request holds its worker until the blocking call returns. Both are the right defaults; neither was written down. Correct the _ensure_token_fresh docstring, which claimed the refresh fires "exactly once across concurrent callers". That holds only when login succeeds and clears token_stale. On failure the token stays stale and waiting threads retry in turn, so the guarantee is really that retries are serialized rather than collapsed. Add tests for the thread cap, the cancellation contract, and the failed-login path, and switch docstrings in the touched file to single GFM backticks. The ngsiem suite routes command_async through the sync command mock to keep its existing assertions, which meant a revert to the blocking client passed unnoticed; a count comparison between the two mocks now catches that, including a single reverted call site.
…back bind (CrowdStrike#477) * docs(server): document --api-key and warn on unauthenticated non-loopback bind HTTP transports have no authentication by default, and binding to a non-loopback address such as --host 0.0.0.0 exposes an unauthenticated server with no warning. The --api-key option already exists as the mitigation, but the examples steered users toward the open-bind pattern and never showed how a client sends the key. Add a non-halting startup warning when the server binds beyond loopback without an API key, naming the host:port and pointing to --api-key. The server still starts. Managed runtimes like AWS Bedrock AgentCore sit behind their own network layer and are called out as unaffected. Rework the README and docs/ examples so the secure pattern (bind scope + --api-key) is the one users copy, add a canonical HTTP Transport Security section with both server-side and client-side (x-api-key header, MCP config and curl) examples, and flip the remote-client config so the authenticated version leads. * fix(docs): resolve MD028 blockquote lint errors in deployment docs * fix(docs): replace relative links that break on the docs portal Relative markdown links resolve against the GitHub repo path and work there, but the developer.crowdstrike.com portal renders them against the page URL, so a link like ../../.github/CONTRIBUTING.md turned into developer.crowdstrike.com/.github/contributing.md/ and 404'd. Point the two CONTRIBUTING references at the full GitHub URL (governance files live in .github/ and are not published to the portal), matching what contributing.md already does, and repoint the README#modules scope-mapping links to the portal's own Module Overview page. * update bedrock verbiage * fix(docs): resolve MD028 between AgentCore admonitions * Update falcon_mcp/server.py Co-authored-by: Gabe Alford <redhatrises@gmail.com> * fix(server): correct format-arg count in open-bind warning A prior edit shortened the warning message to a single %s but left three positional args, so logger.warning raised "TypeError: not all arguments converted during string formatting" whenever the server bound to a non-loopback host without an API key. Use %s:%d and pass host and port. --------- Co-authored-by: Gabe Alford <redhatrises@gmail.com>
…e#480) The Falcon API exposes 135 live aggregate operations. 50 of them share one request-body schema across four near-identical dialects, so building the body construction once keeps the 12 downstream per-module aggregate tools as thin wrappers instead of 12 hand-rolled builders that each rediscover the quirks. _build_aggregate_spec covers the full 21-field msa.AggregateQueryRequest superset and omits unset keys, which means the three narrower dialects need no separate code path — they are strict subsets. _base_aggregate makes the call and always wraps the body in a list, because every dialect rejects a bare object live even though swagger marks six operations as bare. Swagger turned out to be wrong on three counts, so the design follows live probing instead: the true minimal body is type + field only (the 16 "required" fields are a spec artifact), the body is always list-wrapped, and result buckets key on label rather than key. Response order across multiple specs is not preserved, but each result carries its own name, so callers identify them by name rather than position. Aggregate responses have no meta.pagination block at all, so there is no envelope to build and the API's resources list is returned directly, matching the existing non-paginated tool pattern. Type support is genuinely per-operation — casemgmt rejects a date_histogram that alerts accepts — so types pass through to the API rather than through an allowlist that would wrongly reject types valid on operations nobody probed. One behavioral addition beyond body construction: a 2xx response can still carry a body-level 400, and handle_api_response only inspects the HTTP status, so an invalid aggregate type would otherwise be discarded and surface as an empty list. That case is now formatted as a real error while keeping the true transport status in details, and the check is scoped to success statuses so a genuine 403 still reaches handle_api_response's scope-hint branch. This registers no tools; those arrive with the downstream module tasks.
The two aggregate helpers landed with 59- and 71-line docstrings, against 10-17 lines for every other _base_* helper in this file. Most of that was research notes from live-probing the endpoints: exact status codes per unsupported aggregation type, the two meanings of a 403, which swagger fields are wrong, a four-row dialect table. Useful while building it, noise for anyone reading the code afterward. Both now state the contract and stop: summary, Args, Returns, Raises. _base_aggregate drops from 71 lines to 20. What came out was API behavior rather than function contract. That a bad FQL filter returns a silent 200, or that an unsupported type surfaces as a 500, is the API's behavior and not something these helpers decide — and it is already covered by tests that fail if it changes, which is where a claim like that belongs. One quirk stays in Returns: buckets key on `label` rather than `key`, since callers have to destructure the result. Inline comments survive only where the code looks wrong or removable without them: the list-wrapped body, the 2xx-with-errors check, the empty-specs guard, and formatting the error directly. Each is now a line or two of reason instead of a paragraph of investigation. The list-wrapping comment no longer quotes the API's internal deserializer error verbatim — that string described another runtime's implementation, which has no place in this codebase and would go stale silently; it now just says the API rejects a bare object. No behavior change. The full research record is in the CrowdStrike#480 commit message and the tracking task.
* feat(modules/detections): add alert aggregation tool Adds falcon_aggregate_alerts, backed by PostAggregatesAlertsV2, so "how many by X" and "top N" questions can be answered with a single counting call instead of paging through search results. Swagger marks both PostAggregatesAlertsV1 and GetAggregateDetects deprecated, so v2 is the only viable endpoint. The parameter surface is limited to what the live API honors. Ten aggregation types work (terms, date_histogram, date_range, range, cardinality, max, min, avg, sum, percentiles); stats and significant_terms return 500 and histogram returns 400, and since an unrecognized type also yields an opaque 500 the argument is constrained to a Literal. Four fields the swagger documents — exclude, min_doc_count, max_doc_count and from — are silently ignored by the API, verified with values that would have visibly changed the result, so they are not exposed rather than offered as controls that quietly do nothing. Sorting accepts only the pipe form. `_count.desc` is rejected with a 400, which also corrects the shared helper's docstring: the sibling RTR aggregate endpoint answers the dot form with "invalid sort spec: _count.desc". include_hidden is a query parameter, not part of the body, and omitting it counts roughly 25k more alerts than passing false. _base_aggregate had no path for query parameters, so it gained a `parameters` argument; the change is additive and its existing tests are untouched. Three types also need a companion argument — date_histogram needs interval, date_range needs date_ranges, range needs ranges — and the API answers a spec missing one with an unactionable 500 at any nesting depth. The check therefore lives in _base_aggregate and recurses through sub_aggregates, so the eleven modules that will follow this pattern inherit it rather than reimplement it. Swagger lists sixteen body fields as required; only type and field actually are. The FQL guide documents the 58 aggregatable fields confirmed against a live tenant, which is a narrower set than the filterable fields, and notes that an unsupported field returns an empty result rather than an error — the same shape as a genuine zero count. * docs(modules/base): scope the aggregate sort note to the endpoint The shared helper claimed the pipe form was the only accepted sort, which overstates what is known. Sort handling is per-endpoint: on the four aggregate endpoints where the parameter is exercisable the dot form does return 400, but that is four of roughly fifty, and two others reject both forms for unrelated reasons. The helper now says accepted forms vary and leaves the specific claim to each tool, where falcon_aggregate_alerts already documents what its own endpoint takes.
…-cql-guidance # Conflicts: # falcon_mcp/modules/cloud.py # tests/modules/test_cloud.py
Caught by CI's ruff check on PR #6.
- type-annotate register_tools(server, ->None) - narrow _base_search_api_call / _base_get_by_ids unions via isinstance(dict) raise path - annotate params dict as dict[str, Any]
search_hosts dropped the guide on a search-step error and returned a bare [error], so a caller whose filter used an invalid field had nothing to correct from. The API answers only "Request failed with status code 400" / "Invalid filter expression supplied", naming neither the offending field nor the valid set. A common case is filtering on `agent_id`, which the Hosts API does not accept — the field is `device_id`. SEARCH_HOSTS_FQL_DOCUMENTATION already documents device_id; it simply never reached the caller. Use _format_fql_error_response, as detections, cloud, firewall, ioc and rtr already do. The guide and the helper were both already imported here.
…I call
get_detection_details passed `ids` straight through. When a caller rebuilds a
composite ID instead of forwarding it verbatim, the API answers "invalid CID
provided" — which does not say which part of the ID was wrong, so the caller
cannot correct it.
Validate the shapes that arise when an ID is reconstructed: the leading CID
dropped ("ind:<aid>:<id>"), the legacy "ldt:" scheme glued onto a composite
ID, only the trailing detection-id kept, or a different scheme prefix. Each
returns the offending ID, what is wrong with it, and the expected format,
without a request to Falcon.
Deliberately conservative: an unfamiliar-but-valid scheme is passed through to
the API rather than rejected here, so an unreadable-tenant ID still surfaces
as a tenant error rather than a formatting one. The legacy "ldt:<aid>:<id>"
form is also left alone. Only a bare value matching the trailing
detection-id pattern is flagged, so arbitrary single-segment IDs are untouched.
ssawant-uptycs
approved these changes
Aug 7, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Brings the fork up to date with
CrowdStrike/falcon-mcp(it was 68 commits behind, now 0), then adds two fixes for cases where a tool failure gave the caller nothing to correct from.1. Sync with upstream
Merges
upstream/main. Notable changes this pulls in:search_ngsiem, including the guide injected into error responsescount_kubernetes_containersreturns an int — supersedes our3ef9736, which fixed the same bug independentlyresults/pagination/filter_used) on search toolsfacetaccepts a listoffsetdropped from dual-pagination toolsConflict resolution:
cloud.pyandtest_cloud.pyresolved in upstream's favour, since CrowdStrike#397 supersedes our own fix and carries matching tests. Ourf61f23f(chunking_base_get_by_idsat 1000 IDs to avoid HTTP 413) is preserved.Also cherry-picks two script lint fixes (ruff E702 and mypy on
probe_pagination) that were already on another branch, soruff check .passes repo-wide.2.
hosts.py— return the FQL guide on a failed searchsearch_hostsreturned a bare[error]on a search-step failure, dropping the guide. The API answers only "Request failed with status code 400" / "Invalid filter expression supplied", naming neither the offending field nor the valid set.A common case is filtering on
agent_id, which the Hosts API does not accept — the field isdevice_id.SEARCH_HOSTS_FQL_DOCUMENTATIONalready documents it; it just never reached the caller.Now uses
_format_fql_error_response, asdetections,cloud,firewall,iocandrtralready do. Both the helper and the guide were already imported.3.
detections.py— reject malformed composite IDs before callingget_detection_detailspassedidsstraight through. When a caller rebuilds a composite ID rather than forwarding it verbatim, the API answersinvalid CID provided, which does not identify the bad part.Validates the shapes that arise from reconstruction — leading CID dropped (
ind:<aid>:<id>),ldt:glued onto a composite ID, only the trailing detection-id kept, or a different scheme prefix — and returns the offending ID, the problem, and the expected format without a request to Falcon.Deliberately conservative:
ldt:<aid>:<detect_id>is left aloneTesting
905 passed, 257 skipped, 83 subtests(was 902/257/76)ruff check .andmypycleanagent_idfilter returns 400 carrying the guide that namesdevice_id