Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/joseluis-dev/harness-ai/llms.txt

Use this file to discover all available pages before exploring further.

The tool registry is the single source of truth for every tool the harness can invoke. Neither harness-core nor the Python runtime contain a list of MCP servers, CLI commands, or HTTP adapters. Instead, every tool — from read-only SQL queries to external notification dispatch — is represented as a row in the tools table within harness_auth_db. When an agent needs a tool, the gateway queries the registry by technical_name, evaluates the row’s state, risk_level, and required_scopes, and then delegates authorization to mcp-auth-service. Adding, deprecating, or disabling a tool requires only a database update — no redeployment of harness-core.

Core Principles

No hardcoded lists

Neither harness-core nor the runtime enumerate tools in code. The available set is the set of state = 'active' rows in tool_registry. Any if tool_name == '...' branch in core is an automatic PR rejection.

Registry is single source of truth

Tools resolved at runtime come from the persisted registry. A tool registered only in memory (without a database row) does not survive a restart. The last persisted row always wins.

Seeds are migrations

The nine Phase 0.b tools are inserted via Alembic migration 0002_tool_registry_seed, not application startup code. Seeds are versioned, idempotent (ON CONFLICT DO NOTHING), and part of the normal migration history.

Each row declares its authorization

The registry row states what scopes a tool requires and what risk_level it carries. mcp-auth-service crosses these against the client’s token scopes. The tool itself does not make authorization decisions.

Logical Schema

The tools table lives in harness_auth_db. The schema below is the canonical logical definition as implemented in Alembic migration 0001_init for harness_auth_db:
tools (
  id                          UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  technical_name              TEXT UNIQUE NOT NULL,
  -- e.g. "mcp-readonly-sql"
  visible_name                TEXT NOT NULL,
  -- e.g. "Read-only SQL Queries"
  type                        TEXT NOT NULL,
  -- mcp_http | mcp_stdio | cli | http_api | webhook_in | webhook_out
  transport                   TEXT NOT NULL,
  -- oauth | command | hmac | webhook | api
  endpoint                    TEXT,
  -- URL when type IN (mcp_http, http_api, webhook_in, webhook_out)
  command                     TEXT,
  -- binary name when type = cli
  config                      JSONB NOT NULL DEFAULT '{}',
  -- flags, SQL restrictions, headers, env key names, etc.
  state                       TEXT NOT NULL
    CHECK (state IN ('active', 'disabled', 'deprecated')),
  environment                 TEXT NOT NULL,
  -- dev | staging | prod
  operations                  JSONB NOT NULL DEFAULT '[]',
  -- [{name, description, input_schema, output_schema, scope}]
  required_scopes             TEXT[] NOT NULL DEFAULT '{}',
  -- scopes the tool requires globally
  risk_level                  TEXT NOT NULL,
  -- READ_ONLY | READ_SENSITIVE | WRITE | DESTRUCTIVE
  -- | ADMINISTRATIVE | EXTERNAL_SIDE_EFFECT
  timeout_ms                  INTEGER NOT NULL,
  retry_policy                JSONB NOT NULL DEFAULT '{}',
  -- {max_attempts, backoff, jitter}
  audit_policy                JSONB NOT NULL DEFAULT '{}',
  -- {level, include_args, include_result, retention_days}
  requires_human_confirmation BOOLEAN NOT NULL DEFAULT false,
  -- applied to WRITE / DESTRUCTIVE / EXTERNAL by default
  required_env_vars           TEXT[] NOT NULL DEFAULT '{}',
  -- env var names the tool needs (not the values)
  network_restrictions        JSONB NOT NULL DEFAULT '{}',
  -- {egress_allowlist, ingress_allowlist}
  filesystem_restrictions     JSONB NOT NULL DEFAULT '{}',
  -- {read_allowlist, write_allowlist}  (applies to type=cli)
  execution_restrictions      JSONB NOT NULL DEFAULT '{}',
  -- {cpu, memory, no_network, sandbox}  (applies to type=cli)
  version                     TEXT NOT NULL,
  created_at                  TIMESTAMP NOT NULL DEFAULT now(),
  updated_at                  TIMESTAMP NOT NULL DEFAULT now(),
  created_by                  TEXT NOT NULL
);

-- Named index aligned with the design contract (used in EXPLAIN plans):
CREATE INDEX ix_tools_technical_name ON tools (technical_name);
The operations JSONB array enables per-operation scope granularity. For example, list_views requires sql:list_views while execute_query requires sql:read. mcp-auth-service evaluates the operation-level scope, not only the global required_scopes.

Initial Seeds (Phase 0.b)

The following nine tools are inserted by Alembic migration 0002_tool_registry_seed against harness_auth_db. All rows land with state = 'active', environment = 'dev', version = '0.1.0', timeout_ms = 30000, and created_by = 'phase-0b-seed'. The seed is idempotent (ON CONFLICT (technical_name) DO NOTHING).
technical_nametypetransportrisk_levelrequired_scopesHuman confirm
mcp-readonly-sqlmcp_httpoauthREAD_ONLYsql:read, sql:list_views, sql:describe_viewNo
mcp-identity-connectormcp_httpoauthREAD_SENSITIVEidentity:readNo
mcp-document-registrymcp_httpoauthREAD_ONLYdocuments:readNo
mcp-expedientesmcp_httpoauthREAD_SENSITIVEexpedientes:readNo
mcp-reportesmcp_httpoauthREAD_ONLYreportes:runNo
mcp-notificacionesmcp_httpoauthEXTERNAL_SIDE_EFFECTexternalYes
cli-pdf-toolsclicommandREAD_ONLYdocuments:readNo
cli-ocr-toolsclicommandREAD_SENSITIVEdocuments:readNo
cli-office-converterclicommandWRITEwriteYes
MCP endpoints in dev: mcp-readonly-sql at http://mcp-readonly-sql:9003, mcp-identity-connector at http://mcp-identity-connector:9001, mcp-document-registry at http://mcp-document-registry:9004, mcp-expedientes at http://mcp-expedientes:9005, mcp-reportes at http://mcp-reportes:9007, mcp-notificaciones at http://mcp-notificaciones:9006.

Resolution and Authorization Flow

The following eight steps describe the full lifecycle from agent request to audited result:
1

Agent requests a tool by name

The agent (or workflow) declares the tool it needs using its technical_name, for example mcp-readonly-sql.
2

tool_gateway queries tool_registry

The gateway fetches the row from harness_auth_db: technical_name, state, environment, required_scopes, risk_level, transport, endpoint, config, and requires_human_confirmation.
3

mcp-auth-service issues a scoped token

The gateway requests a Client Credentials token with the minimum required scope (e.g., sql:read). mcp-auth-service validates that the requesting client’s allowed_scopes include the requested scope and that oauth_clients.state = 'active'.
4

gateway invokes the tool endpoint

The gateway sends the tool call with Authorization: Bearer <token> in the header. No token is placed in query strings or the request body.
POST /mcp/readonly-sql HTTP/1.1
Authorization: Bearer <access_token>
Content-Type: application/json

{
  "jsonrpc": "2.0",
  "id": "<uuid>",
  "method": "tools/call",
  "params": {
    "name": "execute_query",
    "arguments": { "sql": "SELECT ...", "limit": 100 }
  }
}
5

auth-sdk validates the token

The MCP server (or CLI wrapper) calls auth-sdk to verify the JWT signature and expiration, confirm the scope covers the operation, and check that both tool_registry.state and oauth_clients.state are active.
6

risk_level gate

If the operation’s risk_level requires human confirmation (DESTRUCTIVE, ADMINISTRATIVE, or EXTERNAL_SIDE_EFFECT with the flag set), the MCP responds 202 Accepted with an approval_id. Execution is held until an administrator approves via the admin panel.
7

Result is normalized

The tool response is normalized to the standard result contract before being returned to the agent.
8

Audit is written

Every step writes to audit_auth_events: tool_registry.consulted, token.used, and tool.invoked. If a human approval was required, the approval event is also recorded.

mcp-readonly-sql Configuration

The mcp-readonly-sql seed row carries a full config JSONB block that declares SQL safety controls and connection profile constraints. No connection string or secret is embedded — credentials are resolved from MCP_READONLY_SQL_DATABASE_URL at runtime. The admin panel edits this block as a declarative policy; the MCP server consumes it as a contract, not as its own authorization.
{
  "allowed_connection_profiles": [
    {
      "profile": "harness_readonly",
      "required_env_var": "MCP_READONLY_SQL_DATABASE_URL",
      "allowed_schemas": ["public_reporting"],
      "allowed_tables": [],
      "allowed_views": [
        "vw_expedientes_resumen",
        "vw_tramites_estado"
      ],
      "max_rows": 500,
      "query_timeout_ms": 5000,
      "response_size_limit_bytes": 1048576
    }
  ],
  "reject_multi_statement": true,
  "statement_allowlist": ["SELECT"],
  "statement_blocklist": [
    "INSERT", "UPDATE", "DELETE", "DROP", "ALTER",
    "TRUNCATE", "CREATE", "REPLACE", "MERGE", "CALL", "EXEC"
  ]
}

Versioning and Deprecation

Every tool row carries a version field. The state machine for tool lifecycle is:
active  ──────────────►  deprecated  ──────────────►  disabled
  │                           │
  │  (incompatible change)     │  (migration window ends)
  │  creates new row            │  invocations respond:
  │  with new version           │  code=TOOL_NOT_ALLOWED

active (new version)
  • A tool in state = 'active' is fully operational.
  • A tool in state = 'deprecated' continues to serve clients that already have authorization, but the admin panel flags it as requiring migration. New clients are directed to the replacement row.
  • A tool in state = 'disabled' is rejected before reaching the MCP server. The harness returns code = TOOL_NOT_ALLOWED immediately.
An incompatible change to a tool creates a new row with a new version. The old row transitions to deprecated. The two rows coexist during the migration window.

Declarative Restrictions

Tool restrictions are declared in the registry row and enforced by the runtime — not by the MCP server itself.
FieldApplies toEnforcement
network_restrictions.egress_allowlistAll tool typesRuntime blocks all outbound traffic not explicitly listed. Default: block all.
network_restrictions.ingress_allowlistmcp_http, http_apiRuntime restricts inbound connections to declared sources.
filesystem_restrictions.read_allowlistcliCLI Tool Runner validates paths against the allowlist before invoking the binary.
filesystem_restrictions.write_allowlistcliCLI Tool Runner validates write targets before invoking the binary.
execution_restrictions.cpucliTranslated to container resource limits on the execution container.
execution_restrictions.memorycliTranslated to container resource limits on the execution container.
execution_restrictions.no_networkcliContainer is started with --network none when set to true.
execution_restrictions.sandboxcliAdditional sandboxing flags applied to the execution container.
required_env_varsAll tool typesRuntime fails fast at startup if any declared variable is absent from the environment.

Audit Record

Every significant change to a tool_registry row is recorded in audit_auth_events. The minimum audit fields per action are:
ActionAudited fields
Create toolcreated_by, version, risk_level, required_scopes, state
Change statebefore.state, after.state, actor_id, reason
Change risk_levelbefore.risk_level, after.risk_level, actor_id, justification
Change required_scopesbefore.required_scopes, after.required_scopes, actor_id
Deprecateversion, migrated_to (new technical_name if applicable), sunset_at

Persistence and Isolation

  • The authoritative tool_registry lives in harness_auth_db. mcp-auth-service is its sole writer for authorization-plane mutations.
  • harness_db may hold a read-only cache or materialized view of registry data for operational queries, but it must never make authorization decisions, modify state, change risk_level, or alter required_scopes. All such changes originate in harness_auth_db.
  • Migrations and seeds for tool_registry are applied via the infra/postgres/auth-migrations directory (alembic_version_auth), never via infra/postgres/migrations (alembic_version).

Anti-Patterns

Hardcoded tool lists in core code — prohibitedAny if technical_name == "mcp-readonly-sql" or equivalent enumeration in harness-core or the Python runtime is an automatic PR rejection. Tool resolution must always go through tool_registry.get(technical_name). The runtime discovers tools from the registry; it does not know about them in advance.
Runtime-only registration — prohibitedA tool that exists only in memory (registered at startup without a corresponding database row) does not survive a restart. The tool_registry is the single source of truth. Any tool that should be available after a restart must have a persisted row — either via a seed migration or an admin panel operation.
MCPs authenticating without auth-sdk — prohibitedEvery MCP server, CLI wrapper, and HTTP adapter must validate incoming Bearer tokens using /packages/auth-sdk. An MCP that accepts requests without token validation, validates tokens against its own key store, or calls mcp-auth-service endpoints directly (bypassing the SDK) violates this rule. The SDK is the only approved integration point for the auth plane.

Build docs developers (and LLMs) love