Phase 3 introduces the first real institutional agent in the harness: a natural-language SQL interface over pre-approved database views. The key architectural insight of this phase is thatDocumentation 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.
mcp-readonly-sql is not a special-cased component hard-wired into harness-core — it is a seed row in the tool_registry, consumed by the sql_readonly_agent exactly like any other registered tool. The core never enumerates MCPs in code; it resolves them by name from the registry at runtime. This phase is therefore the proof-of-concept for the entire tool-registry model that all subsequent phases reuse.
Objective
Allow an institutional user to ask questions in natural language about pre-approved SQL views, have the agent generate valid SQL, execute it safely, and respond with an explanation — without ever permitting writes, exposing raw tables, or hard-coding the MCP list into the core codebase.Dependencies
- Phase 0 (Base executable local) — PostgreSQL,
harness_db,harness_auth_db,executions, andaudit_eventsmust be running. - Phase 0b (Platform bootstrap) — the
tool_registrytable andmcp-auth-servicemust be operational. - Phase 1 (External identity) —
mcp-identity-connectormust be available to attribute every query to a real institutional user. - Phase 2 (Model Gateway) — the
POST /v1/chatendpoint is used by the agent to generate SQL from natural language.
mcp-readonly-sql: Service Specification
Allowed Connection Profiles and Query Constraints
Allowlisted Views
Thesql_readonly_agent may only generate SQL against views in the approved catalog. The initial seed includes:
| View name | Description |
|---|---|
vw_expedientes_resumen | Summary of active institutional files (expedientes) |
vw_tramites_estado | Current status of administrative procedures (trámites) |
tool_registry seed — no code change is required.
Readonly Database User
mcp-readonly-sql connects to harness_db using a PostgreSQL user that holds only SELECT privilege on the allowlisted views. This user has no access to raw tables, system catalogs, or any other schema object. The connection string is loaded from the service’s own environment — it is never passed from the agent or from user input.
Authentication
All HTTP requests tomcp-readonly-sql must carry an Authorization: Bearer <access_token> header. The service validates tokens using auth-sdk against mcp-auth-service. Tokens without the required scope are rejected with 401 WWW-Authenticate.
| Requirement | Detail |
|---|---|
| Required scope | sql:read (for executing queries) |
| Additional scopes | sql:list_views, sql:describe_view |
| Token location | Authorization header only — never query string |
| Invalid / expired token | 401 with WWW-Authenticate and resource metadata |
tool_registry Seed Row
mcp-readonly-sql enters the harness as a registry entry, not as a hard-coded component. The seed row is applied at Phase 3 startup:
harness-core never references mcp-readonly-sql by name in application code. The sql_readonly_agent resolves the tool by its technical_name from the registry, receives a descriptor and an access_token with scope=sql:read, and delegates to the authenticated HTTP gateway.
Agent Flow
Acceptance Criteria
mcp-readonly-sql active in tool_registry — no hardcoded enumeration
state=active and the three required scopes. Confirm with a grep that harness-core source code contains no string literal "mcp-readonly-sql" in application logic (seed scripts and tests are exempt).All HTTP requests carry Authorization Bearer — never query string
mcp-readonly-sql. Confirm every incoming request has an Authorization: Bearer header and that no token appears in request.url query parameters.Invalid tokens return 401 with WWW-Authenticate
WWW-Authenticate header identifying the protected resource and the required scope.User asks in natural language and agent responds
vw_expedientes_resumen.Agent generates SQL only against allowlisted views
audit_events entries created during the chat call above. The sql field in each mcp.call event must reference only views present in the allowlist. No raw table name (expedientes, tramites, etc.) may appear in generated SQL.Blocklisted statements rejected before execution
error: STATEMENT_BLOCKED. The audit record must show decision=POLICY_DENIED.Query enforces max_rows, timeout, and response size
truncated: true indicator. Confirm that a slow query is terminated at 5000 ms with a timeout error stored in audit_events.Response includes explanation — not just raw data
sql_readonly_agent must return a human-readable explanation alongside any query result. A response that returns only a raw JSON array without explanation fails this criterion.Write attempts blocked and recorded as POLICY_DENIED
POLICY_DENIED in audit_events.Agent never delivers connection strings — credentials resolved by the MCP
sql_readonly_agent and mcp-readonly-sql. The payload must never contain a connection string, username, password, or database host. The MCP resolves its own credentials from environment or the secret manager; the auth-sdk is used exclusively to validate Bearer token authorization.Operational Notes
- The
tool_registryis the runtime source of truth. The contract matrix incontracts/04-mcp-contracts.mdis a historical reference; never override registry entries with hardcoded values. - The
mcp-readonly-sqlseed scripts andmcp-auth-servicedescriptor scripts must be executed during Phase 3 startup, before any agent call is made. - The chat UI introduced here is the first conversational UI in the harness. It serves as the foundation for the
document_rag_agentUI in Phase 5. - Multi-statement SQL (
SELECT 1; SELECT 2) is rejected by the parser regardless of content.reject_multi_statement: trueis not a heuristic — it is a structural block applied before any statement is evaluated.