Skip to content

feat(agents): tracecat mcp server#2138

Merged
jordan-umusu merged 20 commits intomainfrom
feat/tracecat-mcp
Feb 25, 2026
Merged

feat(agents): tracecat mcp server#2138
jordan-umusu merged 20 commits intomainfrom
feat/tracecat-mcp

Conversation

@jordan-umusu
Copy link
Collaborator

@jordan-umusu jordan-umusu commented Feb 19, 2026

Problem

Tracecat had no way for external AI coding tools (Claude Desktop, Cursor, etc.) to interact with the platform programmatically. Users couldn't manage workflows,
cases, tables, or executions from their IDE or AI assistant.

What This PR Addresses

Adds a standalone MCP (Model Context Protocol) server for Tracecat, exposing ~48 tools and 3 resources that let external MCP clients manage:

  • Workspaces — list accessible workspaces
  • Workflows — CRUD, validate, publish, run (draft & published), run-and-wait
  • Executions — list, inspect, debug failed runs
  • Triggers — webhooks, case triggers, schedules (CRUD)
  • Cases — CRUD with priority/severity/status
  • Tables — CRUD for tables, rows, search
  • Secrets & Variables — list/get metadata (read-only)
  • Actions — discover, get schemas, get authoring context
  • DSL reference — resource for workflow authoring syntax

Key components:

  • tracecat/mcp/server.py — FastMCP server with all tool/resource definitions
  • tracecat/mcp/auth.py — 3 auth modes: OIDC interactive (browser login), JWT verification, token introspection
  • tracecat/mcp/config.py — env-var-driven config (host, port, auth mode, rate limits)
  • tracecat/mcp/middleware.py — rate limiting, input size limits, timeouts
  • Docker Compose — new mcp service on port 8099 across all compose files
  • Helm — deployment + service templates for k8s
  • Unit tests — ~2000 lines covering server tools, auth, config

How to Test / QA

  1. Unit tests: uv run pytest tests/unit/test_mcp_server.py tests/unit/test_mcp_auth.py tests/unit/test_mcp_config.py -x
  2. Local stack: just cluster up -d — verify the mcp container starts on port 8099
  3. Claude Desktop / Cursor: Point an MCP client at http://localhost:8099/mcp, authenticate via OIDC, and verify list_workspaceslist_workflows
    run_draft_workflow flow
  4. Auth modes: Test with TRACECAT_MCP__AUTH_MODE set to oidc_interactive, oauth_client_credentials_jwt, and oauth_client_credentials_introspection
  5. Rate limiting: Rapid-fire tool calls should be throttled per config (2 RPS default, burst 10)

Summary by cubic

Adds a first‑party MCP server for IDE integrations with org‑scoped auth and connect links over streamable HTTP. Auth is OIDC‑only with startup retries and raises fastmcp AuthorizationError on failures; includes Helm/Docker deploys, rate limits, sanitized errors, tests (including webhook path), and table export/import (CSV export patched). Also fixes spacing in the agent preset prompt.

  • New Features

    • MCP server with OIDC interactive login, service principal support, CSRF/scope checks, and auth status checks; uses fastmcp AuthorizationError on failures and retries on startup.
    • Org‑scoped connect endpoint with server URL, short‑lived org links, and copy‑ready configs for Claude Code and Cursor.
    • Middleware: per‑tool timeouts, rate limiting, input size caps; sanitized tool error messages.
    • Tables: export/import alongside CRUD; workflow import supports layout metadata with auto‑generated positions when missing.
    • Deploy: Helm Deployment/Service/Ingress for MCP under /mcp, with updated ingress routing for MCP auth/connect paths and optional annotations; Docker/Compose service (port 8099); publicMcp URL helper; values/envs for limits and startup retries; cluster script honors public URL overrides; image tag aligned.
    • Polish: null‑safe workspace settings, webhook field coercions, improved dispatch error logging, corrected scope computation and N+1 query, extensive unit tests; MCP settings link remains hidden; minor spacing fix in agent preset prompt.
  • Migration

    • Set TRACECAT_MCP__BASE_URL and optional TRACECAT_MCP__STARTUP_MAX_ATTEMPTS/TRACECAT_MCP__STARTUP_RETRY_DELAY_SECONDS.
    • OIDC is required: OIDC_ISSUER, OIDC_CLIENT_ID, OIDC_CLIENT_SECRET, OIDC_SCOPES.
    • Removed OAuth client‑credentials modes; TRACECAT_MCP__AUTH_MODE and related JWT/introspection env vars are no longer used.
    • Helm: enable MCP, set mcp.port, urls.publicMcp, and tracecat.mcp.* values; optional ingress.mcp annotations.
    • Docker: expose MCP_PORT (default 8099); cluster script assigns an MCP port per local cluster.

Written for commit c357b45. Summary will update on new commits.

@jordan-umusu jordan-umusu added the agents LLM agents label Feb 19, 2026
@jordan-umusu jordan-umusu changed the title Feat/tracecat mcp feat(agents): tracecat mcp server Feb 19, 2026
@jordan-umusu
Copy link
Collaborator Author

@cubic review

@cubic-dev-ai
Copy link
Contributor

cubic-dev-ai bot commented Feb 19, 2026

@cubic review

@jordan-umusu I have started the AI code review. It will take a few minutes to complete.

@jordan-umusu
Copy link
Collaborator Author

@codex review

Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

7 issues found across 42 files

Prompt for AI agents (all issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="tracecat/agent/preset/prompts.py">

<violation number="1" location="tracecat/agent/preset/prompts.py:52">
P3: Add a leading space (or newline) so this new sentence doesn’t run into the previous one in the composed prompt.</violation>
</file>

<file name="scripts/cluster">

<violation number="1" location="scripts/cluster:282">
P2: NEXT_PUBLIC_API_URL ignores a PUBLIC_API_URL override, so the frontend can keep calling the default app host even when PUBLIC_API_URL points elsewhere. Default NEXT_PUBLIC_API_URL to PUBLIC_API_URL to keep the frontend aligned with the API override.</violation>
</file>

<file name="tracecat/mcp/middleware.py">

<violation number="1" location="tracecat/mcp/middleware.py:43">
P2: `sys.getsizeof` measures Python object memory, not payload bytes; this can mis-enforce the input size limit (e.g., multi-byte strings). Use encoded byte length instead to enforce the configured byte limit accurately.</violation>
</file>

<file name="tracecat/agent/mcp/internal_tools.py">

<violation number="1" location="tracecat/agent/mcp/internal_tools.py:118">
P2: Bug: `or` fallback is incorrect for empty sets. If a workspace secret exists but has no decrypted keys, `set()` is falsy in Python, so `workspace_inventory.get(secret_name) or org_inventory.get(secret_name)` silently falls through to the org inventory. Use an explicit `is not None` check to correctly distinguish "secret absent" from "secret present with no keys".</violation>
</file>

<file name="tracecat/organization/router.py">

<violation number="1" location="tracecat/organization/router.py:174">
P2: Avoid returning raw ValueError messages from MCP config/auth helpers. These messages include internal configuration details (e.g., missing env vars) and should be sanitized before sending to clients.</violation>
</file>

<file name="tests/unit/test_mcp_server.py">

<violation number="1" location="tests/unit/test_mcp_server.py:46">
P2: Tests under tests/unit are expected to be integration-style with no mocks. These tests rely heavily on monkeypatch, which conflicts with the project’s testing guidelines for this directory.</violation>
</file>

<file name="tracecat/mcp/server.py">

<violation number="1" location="tracecat/mcp/server.py:1">
P1: Infinite loop when the dependency graph has cycles. The BFS re-enqueues nodes whenever it finds a longer path (`new_depth > depth[child]`), which never terminates if a cycle is reachable from a root. Since this is called automatically when users provide workflow YAML without a layout, a cyclic `depends_on` will hang the MCP server. Add a visited-count guard or cap the maximum depth to the number of actions.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 1d2020c159

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

@blacksmith-sh

This comment has been minimized.

@jordan-umusu jordan-umusu marked this pull request as ready for review February 24, 2026 17:43
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

4 issues found across 31 files

Prompt for AI agents (all issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="docker-compose.yml">

<violation number="1" location="docker-compose.yml:312">
P1: The MCP service is pinned to an older image tag (`1.0.0-beta.5`) than the rest of the stack. This is likely missing the new `tracecat.mcp` module and will cause the container to crash on startup. Use the same tag as the other services so the MCP code is present.</violation>
</file>

<file name="tracecat/agent/mcp/internal_tools.py">

<violation number="1" location="tracecat/agent/mcp/internal_tools.py:116">
P2: Optional secrets with required keys are incorrectly treated as mandatory. The guard `if not required_keys and requirement.get("optional", False)` only skips optional secrets that have no keys. An optional secret that declares `keys` (e.g., for an optional integration) but isn't provisioned will cause the tool to be reported as unconfigured and blocked from being added to a preset via `update_preset`. The condition should skip all optional secrets, regardless of whether they declare keys.</violation>
</file>

<file name="tracecat/mcp/auth.py">

<violation number="1" location="tracecat/mcp/auth.py:624">
P1: Service principal role is created without computing effective scopes, leaving `scopes=None`. All other role-creation paths in this file call `compute_effective_scopes`. This means service principals authenticated via client-credentials will fail any downstream scope-based authorization checks. Either compute scopes for the service role or explicitly assign appropriate scopes (e.g., a wildcard like `system_role()` uses).</violation>

<violation number="2" location="tracecat/mcp/auth.py:762">
P2: N+1 query: each workspace in the loop triggers a separate `resolve_workspace_org` DB call. Consider fetching all workspace→org mappings in a single query (e.g., `select(Workspace.id, Workspace.organization_id).where(Workspace.id.in_([...]))`) and filtering in memory.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

@blacksmith-sh

This comment has been minimized.

Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 issues found across 2 files (changes from recent commits).

Prompt for AI agents (all issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="tracecat/mcp/server.py">

<violation number="1">
P2: Avoid returning raw exception details to MCP clients. Log the error and return a generic failure message instead.

(Based on your team's feedback about avoiding platform-level exception details in client responses.) [FEEDBACK_USED]</violation>

<violation number="2">
P2: Avoid returning raw exception details to MCP clients. Log the error internally, but return a sanitized message so platform/internal details aren’t leaked.

(Based on your team's feedback about avoiding platform-level exception details in client responses.) [FEEDBACK_USED]</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

@blacksmith-sh

This comment has been minimized.

Comment on lines +38 to +52
@field_validator("allowlisted_cidrs", "methods", mode="before")
@classmethod
def _coerce_none_to_empty_list(cls, v: Any) -> Any:
"""DB columns may store NULL; coerce to empty list for validation."""
if v is None:
return []
return v

@field_validator("filters", mode="before")
@classmethod
def _coerce_none_to_empty_dict(cls, v: Any) -> Any:
"""DB columns may store NULL; coerce to empty dict for validation."""
if v is None:
return {}
return v
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do the pydantic defaults not work?



@mcp.tool()
async def run_draft_workflow(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

change so we only have polling

@jordan-umusu
Copy link
Collaborator Author

@cubic re-review

@cubic-dev-ai
Copy link
Contributor

cubic-dev-ai bot commented Feb 25, 2026

@cubic re-review

@jordan-umusu I have started the AI code review. It will take a few minutes to complete.

Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

6 issues found across 33 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="tracecat/agent/preset/prompts.py">

<violation number="1" location="tracecat/agent/preset/prompts.py:52">
P3: Add a space or newline at the end of this sentence so the next literal doesn't concatenate into "do not add it.Important" in the rendered prompt.</violation>
</file>

<file name="tracecat/workflow/executions/service.py">

<violation number="1" location="tracecat/workflow/executions/service.py:887">
P2: Calling `_start_workflow` directly bypasses the `@audit_log`-decorated workflow creation path, so nowait executions no longer generate audit entries. Use the audited `create_workflow_execution_wait_for_start` in the background task to preserve audit logs.</violation>
</file>

<file name="docker-compose.yml">

<violation number="1" location="docker-compose.yml:318">
P2: Port mapping hardcodes container port 8099 while `TRACECAT_MCP__PORT` allows overriding the MCP listen port; if the env var is changed, Docker maps to the wrong internal port and the service becomes unreachable. Tie the container port to the same env var.</violation>
</file>

<file name="tracecat/mcp/server.py">

<violation number="1" location="tracecat/mcp/server.py:1">
P2: Broad `except Exception` handlers forward the raw exception message to MCP clients via `ToolError(f"Failed to ...: {e}")`. Internal details (DB errors, file paths, etc.) could leak to external callers. Use a generic message without `{e}` in the client-facing `ToolError`, and keep the detailed error only in the server-side log.

(Based on your team's feedback about not surfacing platform-level exceptions to clients.) [FEEDBACK_USED]</violation>

<violation number="2" location="tracecat/mcp/server.py:1">
P2: The `offset` parameter is accepted but silently discarded — users attempting to paginate will always receive the first page. Either remove the parameter until cursor support is implemented (to avoid misleading callers), or pass it through to `search_rows`.</violation>

<violation number="3" location="tracecat/mcp/server.py:1">
P1: Post-fetch filtering on `status` and `search` is applied to an already-limited page of DB results. If most of the first `limit` workflows don't match the filter, the tool may return zero or fewer results than actually exist. Either push the `status`/`search` filters down into the database query, or fetch enough rows to fill the requested limit after filtering.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

@jordan-umusu jordan-umusu merged commit be8fc77 into main Feb 25, 2026
12 of 16 checks passed
@jordan-umusu jordan-umusu deleted the feat/tracecat-mcp branch February 25, 2026 21:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agents LLM agents

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants