nagameTW/mcp-server-malcolm

πŸ”’ Security
0 Views
0 Installs

🐍 🏠 🍎 πŸͺŸ 🐧 - The first MCP server for Malcolm, the open-source network traffic analysis suite (Zeek + Suricata + Arkime + OpenSearch + NetBox). Gives AI agents structured, threat-hunting access: search and aggregate traffic, discover fields, query Suricata alerts, browse Arkime sessions, and resolve NetBox assets. Read-only by default; opt-in, audited write classes for alerts, tagging, hunts, and PCAP upload. pip install mcp-server-malcolm

Quick Install

One-Click IDE Configuration
claude_desktop_config.json
{
  "mcpServers": {
    "nagametw-mcp-server-malcolm": {
      "command": "npx",
      "args": [
        "-y",
        "nagametw-mcp-server-malcolm"
      ]
    }
  }
}
Or

Using an AI coding agent (Claude Code, Cursor, etc.)? Copy a ready-made prompt that tells it to fetch the setup instructions and install this server for you.

Documentation Overview

mcp-server-malcolm

CI PyPI Python License: MIT Glama score

English | 繁體中文

mcp-server-malcolm MCP server

The first MCP server for Malcolm, the open-source network traffic analysis platform (Zeek + Suricata + Arkime + OpenSearch, with optional NetBox).

It gives any MCP-compatible AI agent structured access to Malcolm: search and aggregate network traffic, discover field names, query Suricata alerts, browse Arkime sessions, resolve NetBox assets, and check system health. Turn on the write classes and it can also create alerts, tag sessions, launch hunts, and upload PCAP.

Read-only until you opt in

With no configuration, this server exposes read tools only. It behaves like a read-only client, and nothing it does can change data in Malcolm.

The server splits write access into five classes, each behind its own environment flag and each off by default. It doesn't register a disabled class, so that class's tools never appear in list_tools() and can't be called. At startup it prints which classes are on:

[mcp-server-malcolm] write classes: alerting=off arkime-tag=off hunt-job=off pcap-upload=off arkime-view=off

Every write is additive. Version 1 has no tool that deletes data, removes a tag, or touches user accounts. It leaves those out on purpose (see Non-goals).

Why an MCP layer

Malcolm keeps all network metadata in one OpenSearch index (arkime_sessions3-*) with non-standard field names and its own filter syntax. An LLM asked to write raw OpenSearch DSL against that index gets it wrong more often than not. This server takes that job off the model:

  • It exposes Malcolm's filter syntax instead of raw DSL.
  • It provides field discovery so the model checks field names before it queries.
  • It provides value enumeration so the model sees what values a field actually holds.
  • It covers both field vocabularies. Arkime expressions take Arkime's own names (ip.src), the rest of Malcolm takes ECS names (source.ip), and Malcolm's own field list carries only the second set. arkime_field_search supplies the first.
  • It wraps Suricata alert queries and handles the field mapping (suricata.alert.* vs rule.*).
  • It adds NetBox asset context (IP-to-device, network segments).

The failure mode this is built against is a quiet one. Malcolm answers a query against a field it does not index with an empty result rather than an error, so a model that guesses a plausible-but-wrong name reads "no such traffic" and moves on. When a search comes back empty, this server checks the fields the query named and reports the name Malcolm actually stores the value under. That lookup runs only after a result set is already empty, so nothing is added to the model's context on queries that worked.

The write side follows the same idea. Rather than hand an agent the raw OpenSearch and NetBox passthroughs that Malcolm already leaves open to any authenticated user, this server exposes a small, named, audited set of write actions. More on that under Security model.

Read tools

These are always registered.

DSL core (backend-agnostic)

Plain OpenSearch DSL against the configured endpoint (Malcolm's /mapi/opensearch proxy). No Malcolm-specific query shape: point the base URL at any OpenSearch-compatible backend and they still work.

ToolDescription
search_dslRun a raw OpenSearch DSL query (hits + aggregations, no hidden time window)
countCount documents matching a DSL query clause
list_indicesList indices (name/health/status/doc count)
index_mappingField mapping/schema for an index
cluster_healthOpenSearch cluster health

Core query

ToolDescription
malcolm_searchSearch network traffic with Malcolm filter syntax
malcolm_aggregateAggregate traffic by one or more fields (top-N with counts)
malcolm_alertsSearch Suricata alerts by signature, severity, IP

Field discovery (anti-hallucination)

ToolDescription
malcolm_field_searchSearch available field names by keyword, prefix, or type
malcolm_field_valuesList distinct values for a field
malcolm_field_profileShow which event.dataset types contain a field
arkime_field_searchSearch the field names Arkime expressions accept (listed again under Arkime)

These three malcolm_* tools cover the ECS names used by malcolm_search, malcolm_aggregate and the DSL tools. Anything going into an expression argument needs arkime_field_search instead: Arkime's parser accepts ip.src and rejects source.ip, and Malcolm's /mapi/fields does not list the expression names at all.

System health

ToolDescription
malcolm_service_statusReadiness of all Malcolm services plus version info
malcolm_data_coverageData freshness per sensor, doc counts per dataset, index info
malcolm_pingQuick liveness check of the Malcolm API

Asset context (NetBox)

ToolDescription
malcolm_netbox_lookupLook up an IP, device, or network prefix in NetBox
malcolm_netbox_sitesList the NetBox site directory (id, name, metadata)
malcolm_netbox_queryRead any other NetBox endpoint (services, VLANs, interfaces, VMs, contacts)

Arkime

ToolDescription
arkime_field_searchLook up the field names Arkime expressions accept (ip.src, port.dst) β€” a separate vocabulary from the ECS names malcolm_field_search returns
arkime_sessionsSearch Arkime sessions with Arkime expression syntax
arkime_session_detailFetch all fields (full SPI document) for one session
arkime_session_pcapFetch a session's PCAP and report its size and file-magic validity (metadata only, nothing written to disk)
arkime_uniqueList distinct values of one field, with optional counts
arkime_multiuniqueUnique value combinations across several fields (e.g. src.ip + dst.port pairs)
arkime_spigraphTop values of one field with a time-series graph
arkime_spiviewValue profile across several fields in one call
arkime_spigraphhierarchyHierarchical top-N breakdown across fields (nested drill-down)
arkime_connectionsSource/destination connection graph (nodes and links)
arkime_file_by_hashExtract the transferred file whose md5/sha256 matches (metadata only, nothing written to disk)

Correlation and export

ToolDescription
malcolm_related_sessionsFind all sessions related to a Zeek UID
malcolm_dashboard_exportExport an OpenSearch Dashboards saved object as JSON

Write tools (opt-in)

Each class is enabled by setting its flag to true. Nothing here runs unless you ask for it.

ClassFlagToolsEndpoint
alertingMALCOLM_MCP_ENABLE_ALERTINGmalcolm_create_alertPOST /mapi/event
arkime-tagMALCOLM_MCP_ENABLE_ARKIME_TAGSarkime_add_tagsPOST /arkime/api/sessions/addtags
hunt-jobMALCOLM_MCP_ENABLE_HUNT_JOBSarkime_create_hunt, arkime_hunt_statusPOST /arkime/api/hunt
pcap-uploadMALCOLM_MCP_ENABLE_PCAP_UPLOADmalcolm_upload_pcapPOST /server/php/submit.php
arkime-viewMALCOLM_MCP_ENABLE_ARKIME_VIEWSarkime_create_view, arkime_create_shortcutPOST /arkime/api/view, POST /arkime/api/shortcut
  • alerting: malcolm_create_alert indexes an analyst- or agent-generated finding as an alert document you can see in Malcolm's dashboards. It uses /mapi/event, Malcolm's own purpose-built write endpoint, which is the template the other classes follow.
  • arkime-tag: arkime_add_tags adds tags to sessions. It only adds; tag removal needs a higher Arkime role and its own safety design, so it's deferred.
  • hunt-job: arkime_create_hunt launches a cross-PCAP packet search (expensive, so scope the query first). arkime_hunt_status reads job progress and ships with the class.
  • pcap-upload: malcolm_upload_pcap sends a local capture file to Malcolm for ingestion, with a client-side size cap. The file must live inside MALCOLM_MCP_UPLOAD_DIR; if that staging directory is unset, uploads are refused, so the tool can never be steered into reading an arbitrary file off the host.
  • arkime-view: arkime_create_view saves a named search expression and arkime_create_shortcut saves a named value list (IOC set) referenced in expressions as $name. Both are additive β€” they let an agent persist hunting knowledge for the human team, and neither deletes or overwrites.

Every write tool carries the MCP annotations readOnlyHint: false and destructiveHint: false, so an MCP client can apply its own confirmation step before the call runs.

Security model

Malcolm's default deployment already gives any authenticated user unrestricted write access to raw OpenSearch (/mapi/opensearch/*) and full NetBox CRUD (/mapi/netbox/*). Both are bare reverse-proxies with no HTTP-verb filtering; Malcolm's own read-only mode removes them rather than trying to filter them. In the common auth modes, "logged in" means admin-equivalent.

Turning on a write class here does not open a door that was otherwise shut. That door is already open at the platform level. This server adds a curated way through it:

  • A small, named set of write actions instead of a raw passthrough.
  • Off by default, enabled one class at a time.
  • An audit line for every write attempt.
  • MCP annotations so the client can require confirmation.

This server does not expose the raw OpenSearch and NetBox write passthroughs, behind a flag or otherwise. Curating that surface is what it is for.

Audit

Every write attempt emits one line of JSON, on success and on failure:

{"ts": "2026-07-06T09:12:44Z", "tool": "arkime_add_tags", "class": "arkime-tag", "target": "ids=240601-abc", "params": {"tags": "suspicious"}, "outcome": "ok"}

outcome is one of ok, http_4xx, http_5xx, or error:<type>. Long parameter values are truncated, and PCAP bytes are never logged. The sink is stderr by default; set MALCOLM_MCP_AUDIT_FILE to append to a file instead. Read tools are not audited.

Quick start

Install

pip install mcp-server-malcolm

Or from source:

git clone https://github.com/nagameTW/mcp-server-malcolm.git
cd mcp-server-malcolm
pip install -e .

Configure

Set the connection variables for your Malcolm instance:

export MALCOLM_URL="https://malcolm.example"
export MALCOLM_USERNAME="admin"
export MALCOLM_PASSWORD="admin"
# TLS verification is ON by default. Malcolm ships self-signed certs, so point
# this at Malcolm's CA cert rather than disabling verification:
export MALCOLM_SSL_VERIFY="/path/to/malcolm-ca.crt"
# Only for an isolated localhost lab: MALCOLM_SSL_VERIFY="false" disables
# verification entirely β€” never do this against a remote host (credentials and
# query results would travel over an unauthenticated channel).
export MALCOLM_TIMEOUT="30"

Leave the write flags unset to run read-only. To enable a class, set its flag:

export MALCOLM_MCP_ENABLE_ALERTING="true"
export MALCOLM_MCP_AUDIT_FILE="/var/log/malcolm-mcp-audit.jsonl"

Run

# As an MCP server (stdio transport)
mcp-server-malcolm

# Or via the Python module
python -m mcp_server_malcolm

Usage

MCP client (config file)

Add the server to your MCP client's configuration:

{
  "mcpServers": {
    "malcolm": {
      "command": "mcp-server-malcolm",
      "env": {
        "MALCOLM_URL": "https://malcolm.example",
        "MALCOLM_USERNAME": "admin",
        "MALCOLM_PASSWORD": "admin",
        "MALCOLM_SSL_VERIFY": "/path/to/malcolm-ca.crt"
      }
    }
  }
}

For the exact config-file location, check your MCP client's docs. Many use a project-level .mcp.json or a global config file.

Python (direct import)

Use MalcolmClient without the MCP layer:

import asyncio
from mcp_server_malcolm import MalcolmClient

async def main():
    client = MalcolmClient(
        base_url="https://malcolm.example",
        username="admin",
        password="admin",
    )

    # Search network traffic
    results = await client.search(
        filters={"event.dataset": "conn", "source.ip": "192.0.2.77"},
        limit=10,
    )

    # Aggregate by protocol
    agg = await client.aggregate(
        fields="network.protocol",
        filters={"network.direction": ["inbound", "outbound"]},
    )

    # Discover field names
    fields = await client.search_fields(keyword="useragent")

    # Get distinct values
    datasets = await client.field_values(field="event.dataset")

    # Look up a NetBox asset
    asset = await client.netbox_get(
        "api/ipam/ip-addresses/",
        params={"address": "192.0.2.77"},
    )

    await client.close()

asyncio.run(main())

Write primitives live behind the _write_* methods. Only the gated write tools reach them, not the direct-import path.

Malcolm filter syntax

Malcolm uses a simple JSON filter syntax, not OpenSearch DSL:

# Exact match
{"event.dataset": "conn"}

# Multiple values (OR)
{"network.direction": ["inbound", "outbound"]}

# Negation
{"!network.transport": "icmp"}

# Field must exist (not null)
{"!related.password": null}

# Combined (AND)
{"event.dataset": "dns", "source.ip": "192.0.2.77"}

Values match exactly. Malcolm compiles this dict to an OpenSearch terms query, so there is no wildcard: {"rule.name": "*MALWARE*"} looks for a signature literally named *MALWARE* and quietly finds nothing. For substring matching either enumerate the values first with malcolm_field_values and pass the ones you want as a list, or use search_dsl and write the wildcard query yourself. malcolm_alerts does that enumeration for you on its signature and category arguments.

Examples

Search DNS queries to a suspicious domain

malcolm_search(
  filters='{"event.dataset": "dns", "zeek.dns.query": "ntp.ubuntu.com"}',
  limit=20,
  time_from="7 days ago"
)

Aggregate top talkers by protocol

malcolm_aggregate(
  fields="source.ip,destination.ip,network.protocol",
  filters='{"network.direction": ["inbound", "outbound"]}',
  limit=20
)

Verify field names before querying

malcolm_field_search(prefix="zeek.dns")
malcolm_field_values(field="event.dataset")
malcolm_field_profile(field="zeek.ssl.server_name")

# Before writing an Arkime expression, look the name up in Arkime's own
# vocabulary. This returns "ip.src | srcIp | ip | general": the first name
# goes in an expression, the second wherever a tool asks for a db field.
arkime_field_search(keyword="src")

Create an alert (alerting class enabled)

malcolm_create_alert(
  title="Periodic beacon to 192.0.2.77",
  severity=2,
  description="60s-interval C2 candidate",
  source_ip="192.0.2.10",
  dest_ip="192.0.2.77"
)

Tag sessions for review (arkime-tag class enabled)

arkime_add_tags(session_ids="240601-abc,240601-def", tags="review,beacon")

Launch a hunt (hunt-job class enabled)

arkime_create_hunt(
  name="beacon-bytes",
  search="deadbeef",
  search_type="hex",
  total_sessions=42,
  start_time=1717200000,
  stop_time=1717203600,
  expression="ip==192.0.2.77"
)

Configuration reference

VariableDefaultDescription
MALCOLM_URLhttps://localhostMalcolm base URL
MALCOLM_USERNAMEadminBasic auth username
MALCOLM_PASSWORDadminBasic auth password
MALCOLM_SSL_VERIFYtrueVerify TLS certs. true/false, or a CA-bundle path (use the path for self-signed Malcolm)
MALCOLM_TIMEOUT30HTTP request timeout (seconds)
MALCOLM_MCP_ENABLE_ALERTINGfalseEnable the alerting write class
MALCOLM_MCP_ENABLE_ARKIME_TAGSfalseEnable additive session tagging
MALCOLM_MCP_ENABLE_HUNT_JOBSfalseEnable Arkime hunt create + status
MALCOLM_MCP_ENABLE_PCAP_UPLOADfalseEnable PCAP upload (also needs MALCOLM_MCP_UPLOAD_DIR)
MALCOLM_MCP_ENABLE_ARKIME_VIEWSfalseEnable saved-view + shortcut (value-list) create
MALCOLM_MCP_UPLOAD_DIRunsetStaging dir that files must live inside to be uploadable; unset β‡’ uploads refused
MALCOLM_MCP_AUDIT_FILEunsetWrite-audit file (stderr when unset)

Malcolm API endpoints used

EndpointMethodUsed by
/mapi/documentPOSTmalcolm_search, malcolm_alerts, malcolm_related_sessions
/mapi/agg/<fields>POSTmalcolm_aggregate, malcolm_field_values, malcolm_field_profile, malcolm_data_coverage
/mapi/fieldsGETmalcolm_field_search, malcolm_field_profile
/mapi/ready, /mapi/versionGETmalcolm_service_status
/mapi/pingGETmalcolm_ping
/mapi/ingest-stats, /mapi/indicesGETmalcolm_data_coverage
/mapi/dashboard-export/<id>GETmalcolm_dashboard_export
/mapi/opensearch/<index>/_searchPOSTsearch_dsl
/mapi/opensearch/<index>/_countPOSTcount
/mapi/opensearch/_cat/indicesGETlist_indices
/mapi/opensearch/<index>/_mappingGETindex_mapping
/mapi/opensearch/_cluster/healthGETcluster_health
/mapi/netbox/*GETmalcolm_netbox_lookup, malcolm_netbox_query
/mapi/netbox-sitesGETmalcolm_netbox_sites
/mapi/eventPOSTmalcolm_create_alert (write)
/arkime/api/fieldsGETarkime_field_search
/arkime/api/sessionsGETarkime_sessions
/arkime/api/session/<id>GETarkime_session_detail
/arkime/api/sessions.pcapGETarkime_session_pcap
/arkime/api/unique, /arkime/api/multiuniqueGETarkime_unique, arkime_multiunique
/arkime/api/spigraphGETarkime_spigraph
/arkime/api/spiviewGETarkime_spiview
/arkime/api/spigraphhierarchyGETarkime_spigraphhierarchy
/arkime/api/connectionsGETarkime_connections
/arkime/api/sessions/bodyhash/<hash>GETarkime_file_by_hash
/arkime/api/sessions/addtagsPOSTarkime_add_tags (write)
/arkime/api/hunt, /arkime/api/huntsPOST, GETarkime_create_hunt, arkime_hunt_status (write + read)
/arkime/api/view, /arkime/api/shortcutPOSTarkime_create_view, arkime_create_shortcut (write)
/server/php/submit.phpPOSTmalcolm_upload_pcap (write)

These endpoint paths and body shapes match Malcolm 26.06.1 and Arkime v6.5.0. Both drift between releases, so re-check against your own version if a write tool returns an unexpected error.

Non-goals

Version 1 leaves these out on purpose:

  • Destructive writes (Arkime session delete, tag removal, user management).
  • Raw OpenSearch write or raw NetBox CRUD passthrough, behind a flag or otherwise.
  • The streamable-http transport (stdio only).

Requirements

  • Python 3.11+
  • A Malcolm instance with API access
  • Network connectivity to Malcolm (HTTPS)

License

MIT Β© nagameTW

Related MCP Servers

13bm/GhidraMCP

🐍 β˜• 🏠 - MCP server for integrating Ghidra with AI assistants. This plugin enables binary analysis, providing tools for function inspection, decompilation, memory exploration, and import/export analysis via the Model Context Protocol.

πŸ”’ Security1 views
123Ergo/unphurl-mcp

πŸ“‡ ☁️ - URL intelligence for AI agents. 13 tools for security signals and data quality: redirect behaviour, brand impersonation detection, domain age, SSL validation, parked detection, URL structural analysis, DNS enrichment.

πŸ”’ Security0 views
82ch/MCP-Dandan

🐍 πŸ“‡ 🏠 🍎 πŸͺŸ 🐧 - Real-time security framework for MCP servers that detects and blocks malicious AI agent behavior by analyzing tool call patterns and intent across multiple threat detection engines.

πŸ”’ Security0 views
9hannahnine-jpg/arc-gate-mcp

🐍 - Runtime governance for MCP tool calls. Blocks prompt injection and capability abuse before tool results reach your agent.

πŸ”’ Security0 views

Engagement

Views
0
Installs
0
Upvotes
0

Views and upvotes are unique per visitor network (hashed IP). Installs count copy actions.

Status

Health: Not checked yet

We have not completed a health check for this listing yet.

No check timestamp yet.

Unclaimed listing (imported or pending owner verification). Claim it β†’
β˜… Spotlight Slot

Feature Your MCP Server

Get maximum visibility for your server across our directory, search results, and detail pages.

Spotlight Your Server

Own this project?

This directory is pre-filled from public sources. Claim via GitHub README, site badge, or DNS TXT to get the verified badge and attach your website.

Claim this listing

Promote this listing

Optional paid placement. Free listings stay free forever.

Share & Embed

Add our SVG badge (dark/light directory styles) or embeddable widget to your site.