MCP Toolsets#

Purpose#

Seizu exposes a Model Context Protocol (MCP) server at /api/v1/mcp. LLM agents such as Claude can connect to this endpoint and call tools that run read-only Cypher queries against the Neo4j graph database.

Tools are grouped into toolsets. Each toolset contains one or more tools, where every tool is a parameterised Cypher query. Both toolsets and tools maintain a full version history so changes can be audited and reverted.

Action confirmations#

Built-in tools that require approval ask an MCP client using protocol 2026-07-28 to collect that approval. MCP_CONFIRMATION_ELICITATION_MODE chooses how:

Mode

Behaviour

url (default)

The client shows a link to Seizu’s confirmation page. The person approves or denies there, signed in as themselves, then lets the call continue.

form

The client renders the approval dialog itself and reports the answer back. One round trip fewer, but the client can answer without asking anyone.

permission

form for callers holding chat:bypass_permissions, url for everyone else.

off

Never elicit; return the confirmation payload and its URL as tool content.

Prefer url where clients are not under your control. In form mode the approval is only as trustworthy as the client: nothing in the protocol proves a person saw the dialog, so a client that answers automatically approves every action the caller is otherwise permitted to take. In url mode the decision is recorded in Seizu against the user’s own session, so a client cannot fabricate it — a client that claims approval without one gets the pending confirmation back and the action does not run.

The server returns InputRequiredResult with an elicitation/create request. The client continues the original call with the returned requestState and the user’s response in inputResponses. Approval remains scoped to the caller, session, tool, target, and exact arguments, expires after ACTION_CONFIRMATION_TTL_SECONDS, and is consumed once.

A decline refuses that attempt. An identical action can request one extra prompt (ACTION_CONFIRMATION_DENIAL_RETRIES, default 1; 0 disables retries). Five unexpired denials in the same user/source/session stop further prompts, including calls with changed arguments (ACTION_CONFIRMATION_SESSION_DENIAL_LIMIT, default 5, minimum 1). This returns an error with block_reason: confirmation_denial_limit; already approved actions remain usable. The window is the confirmation lifetime (ACTION_CONFIRMATION_TTL_SECONDS, default 1800 seconds from creation). Owners can open a denied action’s confirmation URL and allow it before expiry, then retry the action. MCP form continuations cannot reverse denials. Cancel leaves a confirmation pending.

A client that cannot do the configured mode is never offered the other one; it receives the confirmation URL as content instead. URL mode requires the client to advertise elicitation.url explicitly — a bare elicitation: {} is read as form support only, because clients predating URL elicitation advertise it and then refuse a URL request. Older clients and clients without elicitation support get content too. Upstream account authorization and recovery continue to use URL interactions.

Managing Toolsets#

Navigate to MCP Toolsets in the sidebar to view all toolsets.

Built-in tools provided by Seizu (the seizu toolset) are shown with a Built-in badge and cannot be edited or deleted.

Permissions: Creating and editing toolsets and tools requires the toolsets:write / tools:write permission (seizu-admin). Deleting requires toolsets:delete / tools:delete. Restoring a historical version also requires toolsets:write / tools:write. Users with seizu-viewer or seizu-editor roles can view toolsets and tools but will not see New toolset / New tool buttons, and write/delete/restore actions in the ⋮ menu will be disabled.

From the list you can:

  • Click a toolset name to view its tools.

  • Open the ⋮ menu on any row to Edit, View Tools, View history, or Delete a toolset.

Creating a toolset#

Click New toolset. The form includes:

Field

Description

id

Immutable lower_snake_case ID. Used to namespace MCP tool names as {toolset_id}__{tool_id}.

name

A user-friendly display name.

description

Optional description shown in the UI.

enabled

When disabled, none of the toolset’s tools are exposed via MCP.

Editing a toolset#

Open the ⋮ menu and select Edit. An optional comment field records the reason for the change and appears in the version history.

Deleting a toolset#

Open the ⋮ menu and select Delete. Deleting a toolset permanently removes all of its tools.

Managing Tools#

Click a toolset row (or View Tools from the ⋮ menu) to view and manage the tools within that toolset.

Creating a tool#

Click New tool. The form includes:

Field

Description

id

Immutable lower_snake_case ID. Combined with the toolset ID to form the MCP tool name {toolset_id}__{tool_id}.

name

Tool display name.

description

Description shown to the LLM agent as the tool’s purpose.

cypher

A read-only Cypher query. Write operations (CREATE, MERGE, DELETE, etc.) are blocked at save time.

parameters

A list of typed parameters. Each parameter has a name, type (string, integer, float, boolean), description, required flag, and optional default value. Parameters are passed to the Cypher query as named parameters (e.g. $param_name).

enabled

When disabled, the tool is hidden from MCP clients.

Cypher is validated before saving — queries with syntax errors or write operations are rejected.

Editing a tool#

Open the ⋮ menu on a tool row and select Edit. An optional comment records the reason for the change.

Deleting a tool#

Open the ⋮ menu and select Delete.

Managing Toolsets and Tools via the CLI#

The seizu CLI provides commands for managing toolsets and tools, including CRUD operations, version history, calling tools directly, and bulk seed/export via YAML.

Toolset commands#

seizu toolsets list                              # list all toolsets
seizu toolsets get <toolset_id>                 # show a toolset
seizu toolsets create my_toolset "My Toolset" --description "desc"
seizu toolsets update <toolset_id> --name "New Name" --enabled
seizu toolsets delete <toolset_id>
seizu toolsets versions <toolset_id>            # version history
seizu toolsets version-get <toolset_id> <n>     # specific version

Tool commands#

seizu toolsets tools list <toolset_id>
seizu toolsets tools get <toolset_id> <tool_id>
seizu toolsets tools create <toolset_id> count_nodes --name "Count Nodes" \
    --cypher "MATCH (n) RETURN count(n) AS total" \
    --description "Returns total node count"
seizu toolsets tools update <toolset_id> <tool_id> --name "Count Nodes" \
    --cypher "MATCH (n) RETURN count(n) AS total" --comment "Fixed query"
seizu toolsets tools delete <toolset_id> <tool_id>
seizu toolsets tools versions <toolset_id> <tool_id>
seizu toolsets tools version-get <toolset_id> <tool_id> <n>

Calling a tool via the CLI#

Tools can be executed directly from the CLI. Arguments are passed as KEY=JSON_VALUE pairs (the value is JSON-parsed, so numbers and booleans work without quoting):

# No parameters
seizu toolsets tools call <toolset_id> <tool_id>

# With parameters
seizu toolsets tools call <toolset_id> <tool_id> --arg limit=10 --arg label='"CVE"'

# Pass all arguments as a JSON object
seizu toolsets tools call <toolset_id> <tool_id> --args-json '{"limit": 10}'

# JSON output
seizu toolsets tools call <toolset_id> <tool_id> --arg limit=10 --output json

Seeding toolsets from YAML#

Toolsets and tools can be bulk-loaded from the same YAML config file used for reports and scheduled queries:

toolsets:
  my-toolset:
    name: My Toolset
    description: A collection of graph tools
    enabled: true
    tools:
      count-nodes:
        name: Count Nodes
        description: Returns total node count
        cypher: "MATCH (n) RETURN count(n) AS total"
        enabled: true
      find-by-label:
        name: Find By Label
        description: Returns nodes matching a label
        cypher: "MATCH (n) WHERE $label IN labels(n) RETURN n LIMIT $limit"
        parameters:
          - name: label
            type: string
            description: Node label to filter by
            required: true
          - name: limit
            type: integer
            description: Maximum results
            required: false
            default: 25
        enabled: true

Seed with:

seizu seed                      # reads seed_file from ~/.config/seizu/seizu.conf
seizu seed --config path/to/config.yaml
seizu seed --dry-run            # preview without writing
seizu seed --force              # update even if content is unchanged

Export the current state back to YAML (including toolsets):

seizu export
seizu export --dry-run          # print YAML without overwriting the file

Calling a tool via the API#

Tools can be called directly via the REST API without an MCP client:

POST /api/v1/toolsets/{toolset_id}/tools/{tool_id}/call
Content-Type: application/json

{
  "arguments": {
    "param_name": "value"
  }
}

Response:

{
  "results": [
    { "column1": "value1", "column2": 42 }
  ]
}

Version History#

Both toolsets and tools keep a full version history. Open the ⋮ menu and select View history to see all past versions with their timestamps, authors, and comments. Any previous version can be restored (requires toolsets:write / tools:write), which creates a new version with a Restored from version N comment. The Restore action is disabled in the ⋮ menu for users without the required permission.

Built-in Tools#

Seizu ships a set of read-only and admin tools that are always available (subject to RBAC) — no toolset needs to be created to use them. They appear on the Toolsets page with a Built-in badge and cannot be edited or deleted through the UI or CLI.

Built-in tools are grouped by area. Permissions for each tool mirror the equivalent REST endpoint — a user with only seizu-viewer sees the read-only tools; write/delete tools only appear for users with the matching permission.

Group

Tools

graph

graph__schema (Neo4j labels/relationship types/property keys/indexes), graph__validate_query (validation without execution), graph__explain (retained execution plan), graph__query (validated read-only Cypher with risky unindexed plans rejected by default)

reports

list / get / create / update metadata / delete / pin / set_dashboard / get_dashboard reports, plus save/list/get version history

scheduled_queries

Full CRUD plus version history and an on-demand run (picked up by the worker’s next poll). Create/update reuse the same Cypher + action-config validation as the REST routes.

spaces

CRUD for spaces and their sub-spaces, the space overview pointer, and filing reports into a space. No version history — spaces are flat records. See Spaces.

toolsets

Full CRUD for toolsets and nested tools, plus version history. Create/update tool calls reuse Cypher validation.

roles

List built-in and user-defined roles; CRUD for user-defined roles; role version history.

plugins

Install, publish, enable and delete Agent Plugins and toggle individual skills, plus revision history.

workflows

CRUD and version history for workflows, plus running one on demand.

skillsets

Legacy aliases for the plugin tools, kept for one release. See upgrading.

The sandbox group is not exposed here: those tools are available only to the built-in chat assistant, which is the only caller that has a sandbox session to act on. See Sandbox.

graph__query plans each query as part of its existing validation pass. By default, it rejects a plan when Neo4j reports a performance warning, or when a non-index scan participates in a plan whose estimated cardinality exceeds MCP_GRAPH_QUERY_UNINDEXED_MAX_ESTIMATED_ROWS. The error includes the complete plan, the maximum estimated row count, and the scan operators so the caller can rewrite the Cypher without making a separate graph__explain call. A bounded scan below the threshold is still allowed. See the backend settings to tighten or disable this policy.

Which groups are exposed is controlled by the MCP_ENABLED_BUILTINS setting (see backend configuration). All groups are enabled by default; set it to none to disable all built-ins, or to a comma-separated list (e.g. graph,reports) to enable only specific groups.

Tools from other MCP servers#

With external MCP enabled, Seizu can reach tools on other MCP servers through a configured proxy. They are discovered per user, namespaced ext__<proxy>__<tool>, and appear on the Toolsets page as read-only synthetic toolsets so you can see what each proxy offers for your identity.

They are available to the chat assistant, not re-exported from Seizu’s own MCP endpoint. An agent connected to /api/v1/mcp sees built-in and user-defined tools only. Seizu is not a gateway to your other MCP servers: a client that wants those should connect to them directly, with its own identity.

MCP Server#

The MCP server is available at /api/v1/mcp when MCP_ENABLED=true (the default).

Seizu serves the 2026-07-28 protocol revision and every earlier revision from the same endpoint, over Streamable HTTP. Clients negotiate the revision themselves — there is nothing to configure, and a client written against an older revision keeps working. Either way each request stands alone: Seizu keeps no state between MCP requests and re-derives the caller from the Bearer token every time, which is what the 2026-07-28 revision assumes by default.

Authentication uses the same Bearer JWT tokens as the REST API. In development, authentication can be disabled via DEVELOPMENT_ONLY_REQUIRE_AUTH=false.

Tool names are namespaced as {toolset_id}__{tool_id} (double underscore separator) for user-defined tools, or {group}__{action} for built-ins (e.g. graph__query, reports__list). Only tools in enabled toolsets and built-in groups included in MCP_ENABLED_BUILTINS are exposed to MCP clients.

Connecting Claude#

The MCP endpoint is always at <base-url>/api/v1/mcp, where <base-url> is the scheme, host, and port on which Seizu is reachable. The backend port is controlled by the PORT setting (default: 8080). If Seizu sits behind a reverse proxy or load balancer, use the public URL — set MCP_RESOURCE_URL to that URL so OAuth discovery headers point to the right place.

The frontend dev server does not proxy MCP traffic; always point your MCP client at the backend directly.

Claude Code (CLI)#

Add Seizu as an MCP server with the http transport. Pass --callback-port to pin the OAuth callback to a fixed port — without it Claude picks a random port on each run, which won’t match the redirect URI registered in your OIDC provider:

claude mcp add --transport http --callback-port 8888 seizu https://your-seizu-host/api/v1/mcp

Or add it directly to .mcp.json in your project root:

{
  "mcpServers": {
    "seizu": {
      "type": "http",
      "url": "https://your-seizu-host/api/v1/mcp",
      "oauth": {
        "callbackPort": 8888
      }
    }
  }
}

Claude Desktop#

Add an MCP server entry to your Claude Desktop configuration file (~/Library/Application Support/Claude/claude_desktop_config.json on macOS, ~/.config/Claude/claude_desktop_config.json on Linux):

{
  "mcpServers": {
    "seizu": {
      "url": "https://your-seizu-host/api/v1/mcp",
      "oauth": {
        "callbackPort": 8888
      }
    }
  }
}

With OAuth authentication#

If Seizu is configured with OAuth metadata (auto-discovered from OIDC_AUTHORITY, or set explicitly via MCP_OAUTH_AUTHORIZATION_ENDPOINT / MCP_OAUTH_TOKEN_ENDPOINT), Claude will discover the OIDC provider via /api/v1/mcp/.well-known/oauth-authorization-server and prompt users to authenticate inside the client.

The callbackPort in the config above pins the OAuth callback server to port 8888. Register that port as a redirect URI in your OIDC provider before connecting:

http://localhost:8888/callback

Without this, the OAuth handshake will be rejected. For the development Authentik stack this is pre-configured automatically by the blueprint. For any other OIDC provider (Authentik in production, Okta, Keycloak, etc.) you must add it manually.

VM / remote development: If Claude is running on the VM, its OAuth callback server binds to port 8888 on the VM. Authentik redirects the browser (on your local machine) to http://localhost:8888/callback, which means your local machine’s port 8888 must be tunnelled to the VM. Add -L 8888:localhost:8888 to your SSH tunnel command alongside the other ports — see the quickstart for the full tunnel commands.

See the backend configuration for available settings.

Result limits#

A tool result is bounded so a broad query cannot materialize an unbounded amount of data in the server. Rows are serialized as they stream and the query stops at whichever bound is reached first.

Variable

Default

Description

MCP_TOOL_RESULT_MAX_ROWS

50000

Rows returned to a normal MCP call.

MCP_TOOL_RESULT_MAX_BYTES

25000000

Serialized bytes returned to a normal MCP call.

These are separate from CHAT_TOOL_RESULT_MAX_ROWS/_BYTES, which are far tighter because they protect a model’s context; an MCP client is not a model context and is not bounded by them.

Request size. These bound the response. The request is bounded separately by the MCP SDK, which rejects a Streamable HTTP body over 4 MiB with HTTP 413 Request body too large — at the transport, before the body is parsed, so it arrives as an HTTP error rather than a tool result. This applies to the whole JSON-RPC request, so it caps arguments: a very large Cypher query sent to graph__query, or a large parameter to a user-defined tool. It is not configurable through Seizu settings today.

Response shape when truncated. A result within the limits is returned unchanged. One that exceeds them is returned as an object carrying the rows that fit, where an untruncated user-defined tool result is a bare list. Clients that consume these results should handle both shapes.

A truncated result carries:

Field

Meaning

truncated

Always true

truncated_reasons

Every bound that shaped the result, in the order applied — row_limit, byte_limit, or both. A result can be cut twice: once while streaming from the database, and again when the assembled response exceeds the byte budget

returned

Rows actually emitted

total_rows

The real total — only present when the query ran to completion

total_rows_at_least

A lower bound, present instead of total_rows when the source stopped early and the true total is unknown

max_rows / max_bytes

The bound that applied

Do not treat total_rows_at_least as a total. It is the number of rows that reached the response bound, not the number the query would have produced.

Every response carrying data is bounded exactly by MCP_TOOL_RESULT_MAX_BYTES. The single exception is the message returned when not even one row fits: it is a fixed string, so a budget smaller than that message cannot be honoured. This only arises with budgets in the low hundreds of bytes.