OpenCTI MCP Server
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@OpenCTI MCP Serverlookup indicator for IP 185.220.101.29"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
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 |
|
Observables |
|
Intelligence entities |
|
Reports |
|
Cases |
|
Tasks |
|
Investigations |
|
Enrichment |
|
Relationships |
|
Intelligence context |
|
System |
|
Related MCP server: Enrichment MCP Server
Resources
URI template | Description |
| Indicator details |
| Observable details |
| Report details with contained objects |
| Incident, RFI, or RFT case details |
| Investigation exported as a STIX 2.1 bundle |
| 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.pyThat 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 |
| Yes | Base URL of the OpenCTI instance, for example | |
| Yes | OpenCTI API bearer token | |
| No |
|
|
| No |
| Python log level |
| No |
|
|
| No |
| Bind host for SSE transport |
| No |
| Bind port for SSE transport |
| Required for SSE unless explicitly disabled | Bearer token required for SSE HTTP requests | |
| No |
|
|
| No |
| Tenant id assigned to the primary |
| No | unset | JSON object of additional routable tenants keyed by tenant id. Each entry must define |
| No | unset | Optional |
| No |
| Maximum number of sanitized audit events retained in memory for local inspection. |
| No |
| Maximum number of actor-local audit history and principal-metric streams retained at once. |
| No |
| Per-principal, per-operation local quota. |
| No |
| Maximum number of actor quota windows retained at once. |
| No |
| Maximum direct STIX members included in one bounded report or investigation export. |
| No |
| Retention window in seconds for completed mutation idempotency keys. |
| No |
| Maximum number of retained idempotency records in the local backend. |
| No |
| Require |
| No |
|
|
| Required when | Absolute path to the SQLite idempotency database file. | |
| No |
|
|
| Required when | Absolute path to the SQLite observability database file. | |
| No |
| Development-only opt-in for unauthenticated SSE |
| No |
| Maximum SSE HTTP request body size |
| No |
| Positive, finite seconds allowed to receive an SSE request body; fractional values are supported |
| No |
| Maximum concurrent SSE streams and ordinary HTTP requests, enforced independently per pool |
| No |
| Maximum SSE HTTP requests per client per minute |
| No |
| Maximum tracked SSE rate-limit client identifiers. Higher values support more unique client IPs and use more memory; lower values evict older clients sooner. |
| No |
| Use |
| No |
| Comma-separated proxy source IPs or CIDR ranges allowed to supply forwarded client headers when |
| No |
| Timeout in seconds for OpenCTI API requests |
| No |
| Maximum serialized argument size for one tool call |
| No |
| 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-mcpExample 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-mcpConfigure MCP clients to use:
http://localhost:8000/sseClients 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-mcpIn 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-mcpDocker 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 .envEdit .env and set:
OPENCTI_URLto an OpenCTI instance reachable from the MCP containerOPENCTI_TOKENto an OpenCTI API tokenMCP_API_KEYto a random bearer token for MCP clients
For a host-running development server, use:
OPENCTI_URL=http://host.docker.internal:4000For 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 --buildDefault endpoint:
Service | URL |
MCP SSE endpoint |
|
Readiness endpoint |
|
Stop the MCP server:
docker compose downRecommended Workflows
The tools are intended to support both analyst-driven and agentic intelligence workflows. Useful starting patterns include:
Resolve and summarize a seed:
resolve_entity->summarize_entity->expand_entity_contextInvestigate an observable:
lookup_observable->summarize_observable_intelligence->run_available_enrichments->get_enrichment_statusInvestigate an indicator:
lookup_indicator->summarize_indicator_intelligence->find_relationship_pathsCreate an investigation workspace from a seed:
run_basic_investigationorstart_investigation_from_entityReview handoff context:
summarize_report_intelligenceorsummarize_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=0submits the request and skips polling. If submission itself exceeds a positive deadline before work IDs are known, the response markssubmission_timed_outandsubmission_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
simplepath and a bounded one-hopfullgraph of direct relationships plus opposite endpoints, report export includes direct report members up toMCP_EXPORT_MAX_OBJECTS, and investigation export builds a synthetic report bundle from one bounded workspace snapshot. Investigation responses useinvestigated_entities_truncatedfor omitted members andinvestigated_entities_at_capacitywhen the returned snapshot is already at the configured write limit. Exported investigation reports mirror those signals withx_opencti_membership_truncatedandx_opencti_membership_at_capacity.
Response Contracts
global_searchalways returnsresults,partial, andwarnings. Complete backend failure adds the standarderrorobject while retaining those keys.Enrichment batches expose connector outcomes under
requests; waiting tools add polled records underwork_statuses. These arrays have distinct meanings and are not duplicated under compatibility aliases.get_observable_indicatorsalways returnsresults,partial, andwarnings, so failed or incomplete resolution of relationship endpoints is distinguishable from a complete empty result.lookup_caseandlist_casesalways returnresults,partial, andwarnings. Cross-type case queries identify any Incident, RFI, or RFT scope that could not be queried.summarize_report_intelligenceandsummarize_case_intelligencereturn counts for the summaries included in the response and setobjects_truncated/tasks_truncatedwhen additional linked members were omitted by the requested limit.find_by_external_referencereturns at most 50 validated matches, andget_entity_connectorsreturns at most 50 recent enrichment work records.opencti://report/{id}andopencti://case/{id}resources also setobjects_truncated/tasks_truncatedwhen 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, anderror.operation. Additional context such aswork_idorentity_typeis placed alongside the error envelope.At the MCP protocol layer, structured error envelopes set
isError=true, and JSON results are also exposed throughstructuredContent.
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=95Live 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-regressionUse --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_KEYorMCP_IDENTITY_MODE=opencti_bearerunless running an isolated local development environment.Bind SSE to
127.0.0.1unless a reverse proxy provides TLS, authentication, request limits, and access controls.Leave
MCP_TRUST_PROXY_HEADERS=falseunless the MCP server only accepts traffic from a trusted reverse proxy that overwrites forwarded client IP headers. When enabling it, setMCP_TRUSTED_PROXY_SOURCESto the reverse proxy source IPs or CIDR ranges.Keep
OPENCTI_SSL_VERIFY=truein 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 APIThe 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.
This server cannot be installed
Maintenance
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
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
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