Skip to main content
Glama
blauwers

OpenCTI MCP Server

by blauwers

OpenCTI MCP Server

The OpenCTI MCP server exposes OpenCTI threat-intelligence operations through the Model Context Protocol (MCP). It uses the official pycti client to call the OpenCTI GraphQL API.

This project is licensed under the Apache License 2.0. See LICENSE for details.

Features

Category

Tools

Indicators

lookup_indicator, list_indicators, list_indicators_page, get_indicator, summarize_indicator_intelligence, add_indicator, update_indicator, promote_observable_to_indicator, get_indicator_relationships

Observables

lookup_observable, list_observables, list_observables_page, get_observable, summarize_observable_intelligence, add_observable, enrich_observable, get_observable_indicators, get_observable_relationships

Intelligence entities

list_intelligence_entity_types, list_intelligence_entities, list_intelligence_entities_page, get_intelligence_entity, export_intelligence_entity_stix

Reports

lookup_report, list_reports, list_reports_page, create_report, add_object_to_report, get_report_objects, summarize_report_intelligence, export_report_stix

Cases

create_incident_case, create_rfi, lookup_case, list_cases, list_cases_page, add_object_to_case, update_case_status, summarize_case_intelligence

Tasks

create_task, complete_task

Investigations

create_investigation, get_investigation, list_investigations, list_investigations_page, add_to_investigation, export_investigation_as_report, start_investigation_from_container, start_investigation_from_entity, run_basic_investigation

Enrichment

list_enrichment_connectors, enrich_entity, enrich_entity_and_wait, run_available_enrichments, get_enrichment_status, get_entity_connectors

Relationships

create_relationship, lookup_relationships, find_relationship_paths, create_sighting

Intelligence context

global_search, resolve_entity, summarize_entity, expand_entity_context, find_by_stix_id, find_by_external_reference

System

get_current_identity, get_server_capabilities, get_runtime_metrics, get_recent_audit_events, list_audit_events

Related MCP server: Enrichment MCP Server

Resources

URI template

Description

opencti://indicator/{indicator_id}

Indicator details

opencti://observable/{observable_id}

Observable details

opencti://report/{report_id}

Report details with contained objects

opencti://case/{case_id}

Incident, RFI, or RFT case details

opencti://investigation/{investigation_id}

Investigation exported as a STIX 2.1 bundle

opencti://server/capabilities

Server capabilities, limits, backend version, and disabled features

Requirements

  • Python 3.10 or later

  • A running OpenCTI instance

  • An OpenCTI API token with the permissions required by the tools you enable

Installation

From the repository root:

pip install -e .

For development:

pip install -r requirements.txt
pip install -r test-requirements.txt
pip install -e .

requirements.txt and test-requirements.txt are the cross-platform source requirements used for local development. requirements.docker.lock and build-requirements.docker.lock are deterministic Linux/CPython 3.12 inputs for the production container build; they are intentionally not the default install path for Windows or macOS hosts.

To refresh the container locks from any host with Docker installed:

python scripts/refresh-docker-locks.py

That command resolves dependencies inside the same pinned Python image used by the Dockerfile, so platform-specific dependencies are locked for the deployment target rather than whichever workstation happened to run the command.

Configuration

The server reads configuration from environment variables. For local development you may load one explicitly trusted dotenv file by setting MCP_DOTENV_PATH to an absolute path. Implicit working-directory .env discovery is intentionally disabled.

Variable

Required

Default

Description

OPENCTI_URL

Yes

Base URL of the OpenCTI instance, for example http://localhost:4000

OPENCTI_TOKEN

Yes

OpenCTI API bearer token

OPENCTI_SSL_VERIFY

No

true

true, false, or a CA bundle path

LOG_LEVEL

No

info

Python log level

MCP_TRANSPORT

No

stdio

stdio or sse

MCP_SSE_HOST

No

127.0.0.1

Bind host for SSE transport

MCP_SSE_PORT

No

8000

Bind port for SSE transport

MCP_API_KEY

Required for SSE unless explicitly disabled

Bearer token required for SSE HTTP requests

MCP_IDENTITY_MODE

No

server_token

server_token uses the configured OPENCTI_TOKEN; opencti_bearer requires SSE callers to authenticate with their own OpenCTI bearer token and executes tools under that OpenCTI principal.

MCP_DEFAULT_TENANT_ID

No

default

Tenant id assigned to the primary OPENCTI_URL / OPENCTI_TOKEN connection.

MCP_TENANTS_JSON

No

unset

JSON object of additional routable tenants keyed by tenant id. Each entry must define opencti_url, opencti_token, and may override ssl_verify. Multi-tenant routing is SSE-only.

MCP_COORDINATION_BACKEND_FACTORY

No

unset

Optional module:callable import path that returns a CoordinationBackends bundle for externally managed audit, quota, and idempotency storage.

MCP_AUDIT_BUFFER_SIZE

No

1000

Maximum number of sanitized audit events retained in memory for local inspection.

MCP_AUDIT_ACTOR_MAX_STREAMS

No

4096

Maximum number of actor-local audit history and principal-metric streams retained at once.

MCP_PRINCIPAL_QUOTA_PER_MINUTE

No

0

Per-principal, per-operation local quota. 0 disables quota enforcement.

MCP_PRINCIPAL_QUOTA_MAX_ACTORS

No

4096

Maximum number of actor quota windows retained at once.

MCP_EXPORT_MAX_OBJECTS

No

50

Maximum direct STIX members included in one bounded report or investigation export.

MCP_IDEMPOTENCY_TTL_SECONDS

No

300

Retention window in seconds for completed mutation idempotency keys.

MCP_IDEMPOTENCY_MAX_ENTRIES

No

4096

Maximum number of retained idempotency records in the local backend.

MCP_REQUIRE_MUTATION_CONFIRMATION

No

false

Require confirm=true on mutating tools before execution.

MCP_IDEMPOTENCY_BACKEND

No

memory

memory for process-local replay protection or sqlite for restart-safe durable replay protection.

MCP_IDEMPOTENCY_SQLITE_PATH

Required when MCP_IDEMPOTENCY_BACKEND=sqlite

Absolute path to the SQLite idempotency database file.

MCP_OBSERVABILITY_BACKEND

No

memory

memory for process-local audit and quota state or sqlite for restart-safe, cross-process observability state.

MCP_OBSERVABILITY_SQLITE_PATH

Required when MCP_OBSERVABILITY_BACKEND=sqlite

Absolute path to the SQLite observability database file.

MCP_ALLOW_UNAUTHENTICATED_SSE

No

false

Development-only opt-in for unauthenticated SSE

MCP_MAX_BODY_BYTES

No

1048576

Maximum SSE HTTP request body size

MCP_BODY_READ_TIMEOUT

No

10

Positive, finite seconds allowed to receive an SSE request body; fractional values are supported

MCP_MAX_CONCURRENT

No

20

Maximum concurrent SSE streams and ordinary HTTP requests, enforced independently per pool

MCP_RATE_LIMIT_PER_MINUTE

No

60

Maximum SSE HTTP requests per client per minute

MCP_MAX_RATE_LIMIT_CLIENTS

No

4096

Maximum tracked SSE rate-limit client identifiers. Higher values support more unique client IPs and use more memory; lower values evict older clients sooner.

MCP_TRUST_PROXY_HEADERS

No

false

Use X-Forwarded-For, Forwarded, or X-Real-IP as the SSE rate-limit client identifier. Enable only when requests come through a trusted reverse proxy that overwrites these headers.

MCP_TRUSTED_PROXY_SOURCES

No

127.0.0.1,::1

Comma-separated proxy source IPs or CIDR ranges allowed to supply forwarded client headers when MCP_TRUST_PROXY_HEADERS=true. At least one source is required when enabled.

OPENCTI_REQUEST_TIMEOUT

No

60

Timeout in seconds for OpenCTI API requests

MCP_MAX_ARGUMENT_BYTES

No

1048576

Maximum serialized argument size for one tool call

MCP_MAX_RESPONSE_BYTES

No

4194304

Maximum serialized tool response size

Usage

stdio transport

Use stdio when an MCP client launches the server process directly:

OPENCTI_URL=http://localhost:4000 \
OPENCTI_TOKEN=<opencti-token> \
opencti-mcp

Example MCP client configuration:

{
  "mcpServers": {
    "opencti": {
      "command": "opencti-mcp",
      "env": {
        "OPENCTI_URL": "http://localhost:4000",
        "OPENCTI_TOKEN": "<opencti-token>"
      }
    }
  }
}

SSE transport

Use sse when exposing the server as an HTTP endpoint. SSE requires MCP_API_KEY by default.

MCP_TRANSPORT=sse \
MCP_SSE_HOST=127.0.0.1 \
MCP_SSE_PORT=8000 \
MCP_API_KEY=<mcp-bearer-token> \
OPENCTI_URL=http://localhost:4000 \
OPENCTI_TOKEN=<opencti-token> \
opencti-mcp

Configure MCP clients to use:

http://localhost:8000/sse

Clients must send:

Authorization: Bearer <mcp-bearer-token>

For OpenCTI-backed caller identity, switch SSE to delegated bearer mode:

MCP_TRANSPORT=sse \
MCP_IDENTITY_MODE=opencti_bearer \
OPENCTI_URL=http://localhost:4000 \
OPENCTI_TOKEN=<bootstrap-opencti-token> \
opencti-mcp

In this mode MCP_API_KEY must be unset and each client sends its own OpenCTI bearer token in Authorization. The server validates that token with OpenCTI me(), binds the resolved principal to the MCP request, and reuses the same principal for every tool and resource call in that request. get_current_identity returns the active OpenCTI principal. Successful token validations are cached for five seconds to reduce duplicate SSE message checks while keeping a narrow revocation window.

get_server_capabilities and opencti://server/capabilities expose the active transport and identity mode, the OpenCTI backend version, configured limits, the registered tool/resource inventory, and any intentionally disabled feature families so clients can negotiate behavior before invoking it. The disabled feature manifest is now empty for the shipped server surface. Optional features such as tenant routing and external coordination report their active configuration through capability fields instead of being misclassified as missing functionality. In opencti_bearer mode, the discovery surface is filtered against the caller's OpenCTI capabilities and unauthorized tool/resource calls fail fast with a stable forbidden response before the backend is invoked. The capabilities payload includes the requirement manifest used for that projection.

get_runtime_metrics exposes limiter state plus audit and idempotency counters in server-token mode. In delegated identity mode it defaults to principal-local audit counters retained inside the bounded actor stream cache and requires OpenCTI SETTINGS_SETACCESSES (or BYPASS) for scope="all" global metrics. In multi-tenant delegated deployments, unfiltered scope="all" metrics and audit views are rejected because the selected OpenCTI tenant does not confer cross-tenant operator authority; callers must stay on scope="self" or provide an explicit actor filter within the selected tenant. get_recent_audit_events exposes sanitized invocation receipts from the configured audit backend. The HTTP app also provides /live, /ready, and /metrics alongside the compatibility /health endpoint. Audit records include actor, operation, outcome, and latency only; they never store tool arguments or response payloads. get_recent_audit_events defaults to the current actor. In delegated single-tenant mode, operators with OpenCTI SETTINGS_SETACCESSES (or BYPASS) may request scope="all" for cross-principal audit inspection. list_audit_events adds newest-first opaque-cursor pagination for operator workflows that need to traverse the bounded audit stream without offset drift when new events arrive. The high-cardinality knowledge list families also expose paginated variants: list_indicators_page, list_observables_page, list_intelligence_entities_page, list_reports_page, list_cases_page, and list_investigations_page. These tools use signed opaque continuation cursors backed by OpenCTI's native cursor model and return a stable envelope containing items, has_next_page, next_cursor, and total_count when the backend supplies it. Cursors are bound to the tool and filter set that produced them, so callers must reuse the same query shape on continuation requests. list_cases_page requires an explicit case_type of incident, rfi, or rft; merged cross-type pagination remains on list_cases because OpenCTI does not expose one stable cursor domain across all three case collections. /metrics returns JSON by default for operational debugging and also supports Prometheus text exposition when clients send Accept: text/plain or Accept: application/openmetrics-text. The authentication and authorization requirements are identical in both formats. The readiness and liveness probe endpoints are intentionally unauthenticated so load balancers can reach them without an MCP session. /metrics requires the configured SSE API key in server_token mode, and in opencti_bearer mode it requires an authenticated OpenCTI principal with SETTINGS_SETACCESSES or BYPASS. Multi-tenant delegated deployments reject /metrics because the server has no distinct cross-tenant operator role.

SSE deployments also expose /changes, an authenticated relay for OpenCTI's native /stream feed. The relay forwards Last-Event-ID plus the supported OpenCTI stream query parameters (recover, listen-delete, no-dependencies, and with-inferences) so consumers can resume delivery without the MCP server inventing its own event format.

When MCP_TENANTS_JSON is set, every non-probe SSE request may select a tenant with X-OpenCTI-Tenant. Requests without that header use MCP_DEFAULT_TENANT_ID. Delegated bearer validation, pycti client creation, quota/idempotency principal scoping, and opaque pagination cursors are all bound to the selected tenant so OpenCTI identity continues to flow from the platform that actually owns the request.

For deployments that need a managed coordination service, set MCP_COORDINATION_BACKEND_FACTORY to a trusted module:callable. The callable receives the loaded server config and returns a opencti_mcp.coordination.CoordinationBackends instance containing audit, quota, and idempotency backend implementations. This keeps the runtime neutral to the backing service while still allowing Redis, database, or hosted stores to participate in the same verified contracts.

The capability payload also makes the identity model explicit: identity_provider.source is always opencti. The server may accept a static MCP API key in server-token mode or delegated OpenCTI bearer tokens in opencti_bearer mode, but it does not act as an independent OAuth issuer.

Mutating tools expose three standard control arguments: idempotency_key, dry_run, and confirm. Reusing one idempotency key with the same actor, operation, and arguments replays the first completed result without calling OpenCTI again; reusing it with different arguments fails with idempotency_conflict. dry_run=true returns non-executing preview metadata for the mutation request and does not evaluate tool-specific validity, and operators can require confirm=true for every mutation with MCP_REQUIRE_MUTATION_CONFIRMATION=true. The default in-memory backend is process-local; set MCP_IDEMPOTENCY_BACKEND=sqlite with an absolute MCP_IDEMPOTENCY_SQLITE_PATH to retain replay protection across restarts and multiple server processes that share the same database file. The configured TTL also bounds stale in-progress reservations so a crashed worker cannot hold an idempotency key forever. When a mutating tool explicitly reports an unknown submission outcome, the reservation is quarantined as indeterminate rather than replayed as a completed result.

Audit retention and principal quota enforcement also default to in-memory storage. Set MCP_OBSERVABILITY_BACKEND=sqlite with an absolute MCP_OBSERVABILITY_SQLITE_PATH to keep sanitized audit history across restarts and share quota windows across multiple server processes that point at the same database file.

Docker

Build the MCP server image from the repository root:

docker build -t opencti-mcp .

Run it against an existing OpenCTI instance:

docker run --rm \
  -e OPENCTI_URL=http://opencti:4000 \
  -e OPENCTI_TOKEN=<opencti-token> \
  -e MCP_TRANSPORT=sse \
  -e MCP_SSE_HOST=0.0.0.0 \
  -e MCP_SSE_PORT=8000 \
  -e MCP_API_KEY=<mcp-bearer-token> \
  -p 8000:8000 \
  opencti-mcp

Docker Compose

The docker-compose.yml in this directory starts only the MCP server. Run OpenCTI separately from a compatible OpenCTI checkout or an existing deployment, then point the MCP server at that instance.

cp .env.example .env

Edit .env and set:

  • OPENCTI_URL to an OpenCTI instance reachable from the MCP container

  • OPENCTI_TOKEN to an OpenCTI API token

  • MCP_API_KEY to a random bearer token for MCP clients

For a host-running development server, use:

OPENCTI_URL=http://host.docker.internal:4000

For a containerized OpenCTI deployment, set OPENCTI_URL to the URL reachable from the MCP container, such as http://opencti:4000 when the service is attached to the same Docker network.

Start the MCP server:

docker compose up -d --build

Default endpoint:

Service

URL

MCP SSE endpoint

http://127.0.0.1:8000/sse

Readiness endpoint

http://127.0.0.1:8000/health

Stop the MCP server:

docker compose down

The tools are intended to support both analyst-driven and agentic intelligence workflows. Useful starting patterns include:

  1. Resolve and summarize a seed: resolve_entity -> summarize_entity -> expand_entity_context

  2. Investigate an observable: lookup_observable -> summarize_observable_intelligence -> run_available_enrichments -> get_enrichment_status

  3. Investigate an indicator: lookup_indicator -> summarize_indicator_intelligence -> find_relationship_paths

  4. Create an investigation workspace from a seed: run_basic_investigation or start_investigation_from_entity

  5. Review handoff context: summarize_report_intelligence or summarize_case_intelligence

Automation Guardrails

The MCP server supports agentic workflows without exposing broad administration. It does not provide user, group, connector administration, or delete tools. Automated workflows are bounded by explicit limits:

  • Graph expansion and path finding clamp depth, fan-out, queue size, explored states, backend queries, and returned paths. Path and context-expansion responses report when traversal was truncated so callers can narrow filters.

  • SSE streams and ordinary HTTP requests have separate concurrency pools, so connected clients cannot consume the slots needed to send their messages.

  • Enrichment waits clamp timeout and polling intervals. Positive timeout values share one batch deadline across submission and polling, so connector count does not multiply the requested timeout; timeout_seconds=0 submits the request and skips polling. If submission itself exceeds a positive deadline before work IDs are known, the response marks submission_timed_out and submission_outcome_unknown.

  • Available enrichment execution is limited to active connectors matching the observable type, with an execution cap. Omitting a connector ID runs the compatible set, capped at 10 connectors per MCP call to bound external side effects and backend load, and returns per-connector outcomes plus de-duplicated work IDs.

  • Investigation automation creates scoped investigation workspaces from the resolved seed and bounded related context before starting enrichment. Its phased response preserves the investigation when later connector work only partially succeeds.

  • Typed searches return partial-result warnings when only part of a fan-out succeeds and an explicit error when every backend query fails.

  • SSE transport enforces request body, rate, and concurrency limits.

  • Blocking pycti operations run in a bounded worker pool, and timeoutable background offloads use a separate budget so they cannot consume the entire pool after callers time out.

  • Object-reference writes accept at most 50 normalized object IDs per call.

  • STIX export paths are bounded before materialization. Entity export supports both the single-object simple path and a bounded one-hop full graph of direct relationships plus opposite endpoints, report export includes direct report members up to MCP_EXPORT_MAX_OBJECTS, and investigation export builds a synthetic report bundle from one bounded workspace snapshot. Investigation responses use investigated_entities_truncated for omitted members and investigated_entities_at_capacity when the returned snapshot is already at the configured write limit. Exported investigation reports mirror those signals with x_opencti_membership_truncated and x_opencti_membership_at_capacity.

Response Contracts

  • global_search always returns results, partial, and warnings. Complete backend failure adds the standard error object while retaining those keys.

  • Enrichment batches expose connector outcomes under requests; waiting tools add polled records under work_statuses. These arrays have distinct meanings and are not duplicated under compatibility aliases.

  • get_observable_indicators always returns results, partial, and warnings, so failed or incomplete resolution of relationship endpoints is distinguishable from a complete empty result.

  • lookup_case and list_cases always return results, partial, and warnings. Cross-type case queries identify any Incident, RFI, or RFT scope that could not be queried.

  • summarize_report_intelligence and summarize_case_intelligence return counts for the summaries included in the response and set objects_truncated / tasks_truncated when additional linked members were omitted by the requested limit.

  • find_by_external_reference returns at most 50 validated matches, and get_entity_connectors returns at most 50 recent enrichment work records.

  • opencti://report/{id} and opencti://case/{id} resources also set objects_truncated / tasks_truncated when their bounded collections omit additional linked members.

  • Direct entity, indicator, and observable reads return bounded public projections rather than unrestricted raw OpenCTI objects.

  • Every error uses error.code, error.message, and error.operation. Additional context such as work_id or entity_type is placed alongside the error envelope.

  • At the MCP protocol layer, structured error envelopes set isError=true, and JSON results are also exposed through structuredContent.

Development

pip install -r requirements.txt
pip install -r test-requirements.txt
pip install -e .

Run checks:

pytest tests/ -v
black --check src/ tests/
isort --check-only src/ tests/
flake8 src/ tests/
mypy src/

Run the MCP coverage gate:

pytest tests/ --ignore=tests/integration \
  --cov=opencti_mcp --cov-report=term-missing --cov-fail-under=95

Live regression tests are available under tests/integration/. They are skipped by default and require an OpenCTI API compatible with the installed pycti client and a token allowed to create and update test objects. The suite creates isolated regression objects with MCP regression or mcp-regression-* names and removes tracked objects after the session. Set OPENCTI_MCP_PRESERVE_TEST_ARTIFACTS=true only when retaining those objects is useful for debugging. Connector work records remain subject to the platform's normal retention policy. For release/CI validation, start OpenCTI from a separate compatible OpenCTI checkout or point the runner at an existing compatible instance:

python scripts/run-mcp-regression.py \
  --opencti-url http://127.0.0.1:4010 \
  --opencti-token <token> \
  --run-live-regression

Use --use-external-opencti as an explicit alias when targeting an already running external OpenCTI instance. Use --client-python-path <path> only when you intentionally want to test against a separate local client-python checkout instead of the declared package dependency. To run only the fast 95% coverage gate, omit --run-live-regression.

On Windows, .\scripts\run-mcp-regression.ps1 remains available as a thin wrapper around the cross-platform Python runner.

Security

  • Use a dedicated OpenCTI API token with least-privilege permissions.

  • Do not expose SSE without either MCP_API_KEY or MCP_IDENTITY_MODE=opencti_bearer unless running an isolated local development environment.

  • Bind SSE to 127.0.0.1 unless a reverse proxy provides TLS, authentication, request limits, and access controls.

  • Leave MCP_TRUST_PROXY_HEADERS=false unless the MCP server only accepts traffic from a trusted reverse proxy that overwrites forwarded client IP headers. When enabling it, set MCP_TRUSTED_PROXY_SOURCES to the reverse proxy source IPs or CIDR ranges.

  • Keep OPENCTI_SSL_VERIFY=true in production. Use a CA bundle path for private certificate authorities.

  • Tool and resource handlers return sanitized errors to MCP clients and log detailed exceptions server-side.

  • The SSE app enforces process-local request body, rate, and concurrency limits. These controls are not a replacement for reverse-proxy limits in shared or internet-facing deployments.

  • The unauthenticated readiness endpoint uses separate rate and concurrency limits (120 requests per client per minute and two concurrent requests) plus a single-flight five-second cache and one lifecycle-managed asynchronous HTTP connection pool to bound upstream health probes.

Architecture

MCP client
    |
    | MCP protocol (stdio or SSE)
    v
opencti_mcp.server
    |
    | registers tools and resources
    v
opencti_mcp.tools / opencti_mcp.resources
    |
    | pycti
    v
OpenCTI GraphQL API

The server keeps one bootstrap OpenCTI connection plus an optional set of additional SSE tenant routes declared through MCP_TENANTS_JSON. The default tenant keeps the existing behavior. When tenant routing is enabled, X-OpenCTI-Tenant selects the target backend for the request and the runtime keeps that choice in request context. In MCP_IDENTITY_MODE=server_token, each worker thread lazily creates and reuses pycti clients for the selected tenant. In MCP_IDENTITY_MODE=opencti_bearer, each authenticated request gets thread-local pycti clients bound to both the selected tenant and the caller's OpenCTI identity, and those clients are released after the request completes.

A
license - permissive license
-
quality - not tested
C
maintenance

Maintenance

–Maintainers
–Response time
–Release cycle
–Releases (12mo)
Commit activity

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/blauwers/opencti-mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server