ACP: Analytics Context Protocol | El MCP para análisis de datos.

ACP: Analytics Context Protocol

Versión: 1.0

Autor: Iñaki Huerta (IKAUE)

Estado: estable

Base: Model Context Protocol (MCP), revisiones 2025-03-26 y posteriores

ACP es un protocolo abierto para que un agente de IA trabaje con datos de herramientas de analítica, marketing y negocio (GA4, Search Console, BigQuery, bases de datos, CRM…) sin que los datos tengan que pasar enteros por el modelo. Es un perfil de MCP: un servidor ACP es un servidor MCP válido, y lo que añade viaja por los puntos de extensión que MCP ya reserva.

Este documento tiene tres partes:

  • La Parte I explica qué problema resuelve ACP y qué gana quien lo usa. No hace falta saber de protocolos para leerla.
  • La Parte II es la especificación técnica: mensajes, campos, reglas y ejemplos. Es la referencia para quien construye un servidor ACP o un host.
  • La Parte III describe ACP Bridge, una forma opcional de usar servidores ACP hoy desde cualquier cliente MCP, sin desarrollar un host.
    _

Índice

Parte I. Por qué existe ACP

  1. El problema y la propuesta

Parte II. Especificación técnica

  1. Relación con MCP
  2. Arquitectura, transportes y ciclo de vida
  3. Handshake y capacidades
  4. Configuración obligatoria: límites y credenciales
  5. Recursos y resource_id
  6. Autocontext
  7. El resultado en tres partes
  8. Control de costes
  9. Seguridad de credenciales
  10. Responsabilidades del host
  11. Errores, versionado y compatibilidad con MCP
  12. Distribución: carpeta del servidor y acp.json
  13. Guía de implementación y checklist de conformidad
  14. Ejemplos completos

Parte III. Uso opcional: ACP Bridge

  1. ACP Bridge: servidores ACP desde cualquier cliente MCP

Apéndices

Convenciones

Las palabras DEBE, NO DEBE, DEBERÍA, NO DEBERÍA y PUEDE se usan con el sentido de RFC 2119 (MUST, MUST NOT, SHOULD, SHOULD NOT, MAY).

Se usan estos términos:

  • Servidor ACP: el proceso que expone una fuente de datos (GA4, Search Console, BigQuery…).
  • Host: la aplicación agéntica que lanza servidores ACP, gestiona credenciales, almacena datos y habla con el modelo. La implementación de referencia es Analytika, la aplicación de escritorio de IKAUE.
  • Cliente: el componente del host que mantiene la conexión con un servidor. Un host tiene un cliente por conexión.
  • Modelo o IA: el LLM que decide qué tools llamar.
  • Recurso: la unidad de datos dentro de una fuente: una propiedad de GA4, un sitio de Search Console, un proyecto, dataset o tabla de BigQuery.
  • Extracción: una llamada a una tool que devuelve una tabla de datos.
  • Dataset: la copia completa de una extracción que guarda el host.

Parte I. Por qué existe ACP

1. El problema y la propuesta

1.1 Qué es ACP

ACP es un protocolo desarrollado por IKAUE para estandarizar cómo accede la IA a los datos de distintas herramientas cuando el objetivo es analizarlos: sin errores de cálculo, sin perder filas y sin gastos que nadie ha autorizado.

ACP no parte de cero. Hereda casi todo de MCP (Model Context Protocol), el estándar abierto que publicó Anthropic para conectar modelos con herramientas. MCP resuelve muy bien que un modelo descubra y llame a tools. ACP añade lo que falta cuando esas tools devuelven tablas. Un servidor ACP sigue siendo un servidor MCP válido: cualquier cliente MCP puede usarlo, aunque sin las ventajas de ACP.

1.2 Qué falla cuando la IA analiza datos con MCP

MCP está pensado para que el resultado de una tool llegue al modelo como texto. Con datos de analítica, eso provoca problemas concretos.

Todo pasa por la IA. El resultado de una tool tiene un único destino posible: el contexto del modelo. Para trabajar con 50.000 filas, esas 50.000 filas tienen que pasar por la inferencia, lo que es caro, lento y propenso a errores. La alternativa es truncarlas y perder el resto.

El dato puro no queda en ningún sitio. Lo extraído sólo existe dentro de la conversación. No hay una copia completa que se pueda volver a consultar, auditar, compartir o repetir con las mismas condiciones.

El análisis lo infiere el modelo. Sumar, filtrar, agrupar o cruzar lo hace la IA leyendo texto. Un error de suma o un filtro mal aplicado no es un fallo del sistema: es un fallo de inferencia, y no se detecta fácilmente.

El guardado también depende de la IA. Si hay que conservar un resultado, el modelo tiene que llamar a otra tool y reescribir los datos, con el mismo riesgo de errores y el mismo coste en tokens.

Las credenciales están poco cuidadas. Lo habitual es fijar API keys o cuentas de servicio en variables de entorno o en el fichero de configuración del cliente al lanzar el servidor. Las comparte todo lo que use esa instalación y, a veces, acaban en argumentos que el modelo puede leer.

Una instalación, una cuenta. Como el secreto se fija al arrancar, trabajar con dos cuentas de la misma herramienta obliga a duplicar el servidor.

No hay control de costes. MCP no incorpora ningún tope estable. Una consulta de BigQuery puede escanear terabytes, y el modelo puede pedir más datos de los que necesita. Se puede mitigar con instrucciones, pero no es parte del estándar.

La IA consulta sin conocer la fuente. Nada garantiza que el modelo conozca el esquema antes de preguntar. En la práctica inventa nombres de dimensiones, desconoce los eventos clave configurados o pregunta por tablas que no existen, y pierde varias vueltas corrigiéndose.

1.3 La propuesta: dos entregas y un canal de sistema

ACP cambia una sola idea de fondo: cada extracción se entrega dos veces.

  • Una entrega limitada para la IA (model_response): unas pocas filas, el aviso de cuántas hay en total y el contexto necesario para razonar.
  • Una entrega completa para el sistema (system_data): la tabla entera, con columnas tipadas, que el host guarda en un almacén común a la IA y al sistema de análisis (una base de datos, ficheros CSV…).

Después, la IA trabaja sobre esa copia completa de forma determinista, con SQL, tablas dinámicas o gráficos, sin volver a leer los datos fila a fila.

La segunda entrega, igual que las credenciales y los límites, viaja por un canal de sistema que el modelo no ve: el campo _meta que MCP reserva para metadatos.

flowchart LR
  Model["Modelo"] -->|"tools/call: solo argumentos"| Host
  Host -->|"argumentos + _meta.acp: credenciales y límites"| Server["Servidor ACP"]
  Server --> Api["API de la fuente"]
  Server -->|"model_response: muestra"| Host
  Server -->|"system_data: tabla completa"| Host
  Host -->|"muestra + id del dataset"| Model
  Host --> Store["Almacén del host"]
  Store -->|"SQL, pivotes, gráficos"| Host

1.4 MCP frente a ACP, punto por punto

Aspecto Con MCP Con ACP
Destino de los datos Todo va al contexto del modelo, o se trunca y se pierde. El modelo recibe una muestra; el host recibe la tabla completa.
Rastro del dato Sólo existe dentro de la conversación. Cada extracción queda como dataset, con columnas tipadas, cómo se obtuvo (description), sus fechas (dates) y los argumentos para repetirla.
Análisis Lo infiere el modelo leyendo texto. Lo ejecuta el host sobre la copia completa (SQL, pivotes); el modelo pide la operación y lee el resultado.
Guardado Lo gestiona el modelo con otra tool. Es obligación del host. Una extracción con varias tablas deja un dataset por tabla.
Reutilizar una tabla El modelo pega las filas en los argumentos de la siguiente tool. La IA pasa el id del dataset (table_ref) y el servidor pide las filas al host (acp/read_table).
Varias cuentas Un proceso por cuenta, con el secreto fijado al arrancar. Un mismo proceso sirve a varias cuentas: las credenciales viajan en cada llamada.
Credenciales En variables de entorno o en la configuración del cliente. Las guarda el host cifradas y las inyecta en cada llamada por el canal de sistema. El servidor no las persiste y el modelo no las ve ni puede cambiarlas.
Coste Sin tope estándar. Límite de filas para la IA y límite de coste en dólares validado antes de ejecutar.
Conocer la fuente El modelo prueba hasta acertar. Dos tools obligatorias de autocontexto entregan esquema, eventos y valores habituales antes de consultar.
Escritura Cada servidor decide a su manera. Solo lectura por defecto. Escribir exige un modo de acceso explícito, con excepciones por tabla.
Errores Un error genérico que el modelo intenta arreglar. Errores tipados: los de credenciales y coste van a la persona; el resto, a la IA con instrucciones para corregir.
Instalación Cada cliente declara el servidor en su fichero, con comando y secretos. Una carpeta con acp.json. El host pinta el formulario de alta que publica el propio servidor, sin código específico.
Ficheros y enlaces Capturas o PDF entran enteros al modelo; los enlaces privados se descargan sin sesión. Las capturas se adjuntan como imagen, los PDF se copian al proyecto y cada enlace se dirige a la tool que sabe abrirlo con sesión.

1.5 Qué se gana

  • Exactitud. Los números salen de consultas deterministas sobre la tabla completa, no de la lectura del modelo.
  • Trazabilidad. Cada dato tiene origen, fechas, argumentos y una copia que se puede repetir y auditar.
  • Coste acotado. Menos tokens, porque el modelo sólo ve una muestra, y ninguna consulta de pago por encima del límite que fijó la persona.
  • Menos iteraciones. Con el autocontexto, la IA acierta antes con los nombres de campos y tablas. En el uso de la implementación de referencia, el tiempo de respuesta y los tokens se reducen al menos a la mitad.
  • Seguridad. Los secretos no pasan por el prompt ni por los argumentos, y la escritura está cerrada salvo que se abra a propósito.
  • Escala. Varias cuentas e instancias de una misma fuente en un solo proceso, sin duplicar configuraciones.
  • Portabilidad. Un servidor ACP funciona en cualquier host ACP sin adaptar nada, y en cualquier cliente MCP con las funciones básicas.

1.6 Qué no es ACP

  • No sustituye a MCP. Lo extiende. Mensajes, transportes y ciclo de vida son los de MCP.
  • No impone un almacén. El host decide dónde guarda los datasets: una base de datos interna, ficheros CSV o lo que prefiera.
  • No es una aplicación. Es un esquema sobre el que se construyen conectores y hosts. Sin un host que lo implemente (o sin el puente de la Parte III), no hace nada por sí solo.

1.7 El protocolo en una frase por pieza

  • Cada ACP define por defecto tres variables obligatorias: el límite de filas, el límite de coste y las credenciales. Las tres se pueden cambiar en cada llamada.
  • El host las ajusta con una llamada de sistema (acp/configure, o el campo _meta.acp de cada llamada) que nunca se entrega a la IA.
  • autocontext sin recurso devuelve los 25 recursos que más probablemente usará la persona autenticada. Con un recurso, devuelve los bloques de contexto pedidos (todos, si no se piden unos concretos).
  • autocontext_data lista qué bloques existen.
  • El autocontexto llega a la IA por uno de tres medios: inyectado por el host, sugerido en la descripción de las tools o adjuntado por el servidor en la primera extracción.
  • Toda respuesta de tool tiene tres partes: model_response (para la IA), system_info (para el sistema; incluye mensajes del tipo «Showing 25 of 254 extracted rows.») y system_data (la tabla completa con headers y table).
  • Una tool puede leer una tabla que ya está en el host (dataset, CSV importado, report completo) pasando sólo su id. Las filas no viajan en los argumentos ni por el modelo.
  • Una extracción declara su ventana de fechas para que el host pueda recargarla cambiando solo ese rango.
  • La carpeta del servidor puede traer skills (skills/<nombre>/SKILL.md) que el host publica en el catálogo de la IA.
  • ACP funciona sobre los dos transportes de MCP, stdio y HTTP.

Parte II. Especificación técnica

2. Relación con MCP

ACP es un perfil de MCP: no cambia el formato de los mensajes, sólo añade campos en los puntos de extensión que MCP reserva para ello, más métodos que la IA no ve. La tabla resume qué se hereda y qué se añade.

Elemento MCP ACP
Framing JSON-RPC 2.0 Igual
Transportes stdio, Streamable HTTP Igual
initialize Sí Igual; se añade capabilities.experimental.acp
tools/list Sí Igual; siempre incluye autocontext y autocontext_data
tools/call Sí Igual; se añade params._meta.acp y result._meta.acp
acp/configure — Método nuevo, sólo para el host
acp/read_table — El servidor pide una tabla local al host durante un tools/call. La IA no lo ve.
Errores de tool isError: true Igual; se añade un error ACP tipado en _meta.acp.error
Resources, prompts, sampling Opcionales Opcionales; ACP no los usa. Los procedimientos de la IA viajan como skills de la carpeta (13.4), no como prompts MCP.

2.1 Por qué experimental y _meta

  • capabilities.experimental es el sitio que MCP define para las capacidades no estándar. Los SDK de MCP conservan su contenido y descartan las claves desconocidas del primer nivel. Poner acp ahí garantiza que la capacidad sobrevive a cualquier SDK.
  • _meta es el campo que MCP reserva, en peticiones y resultados, para metadatos que no forman parte de los argumentos ni del contenido. Los hosts MCP no lo muestran al modelo. ACP usa _meta.acp como canal de sistema: límites y credenciales al ir, system_info y system_data al volver.

2.2 Qué ve un cliente MCP puro

Un cliente MCP que no conoce ACP puede conectarse a un servidor ACP sin cambios:

  • Ve todas las tools, incluidas autocontext y autocontext_data, y puede llamarlas.
  • Recibe model_response como texto en content[0].text, que es exactamente lo que debe ver un modelo.
  • No envía _meta.acp, así que el servidor aplica sus valores por defecto. Si la fuente necesita credenciales, la tool responde con el error ACP_CREDENTIALS_MISSING.
  • Ignora result._meta.acp, así que pierde la tabla completa, pero la respuesta sigue siendo útil.

3. Arquitectura, transportes y ciclo de vida

3.1 Participantes

┌──────────────────────────── Host ────────────────────────────┐
│                                                              │
│   Modelo ◄──── model_response ────┐                          │
│     │                             │                          │
│     │ tools/call (argumentos)     │                          │
│     ▼                             │                          │
│   Cliente ACP ── _meta.acp ──►  Servidor ACP ──► API externa │
│     │         (límites,           │           (GA4, GSC, BQ) │
│     │          credenciales)      │                          │
│     │ ◄── system_info + system_data                          │
│     ▼                                                        │
│   Almacén (base de datos, CSV…) ◄── tabla completa           │
│                                                              │
│   Llavero ── credenciales ──► Cliente ACP                    │
└──────────────────────────────────────────────────────────────┘
  • El modelo sólo ve la lista de tools, sus argumentos y model_response.
  • El cliente añade _meta.acp a cada llamada y recoge result._meta.acp.
  • El servidor no guarda credenciales de forma persistente. Recibe las credenciales en cada llamada y devuelve las dos entregas.
  • El almacén es del host. ACP no impone su forma.

3.2 Transportes

ACP usa los transportes de MCP sin modificarlos.

stdio

  • El host lanza el servidor como subproceso y habla por stdin y stdout con mensajes JSON-RPC delimitados por saltos de línea.
  • El servidor NO DEBE escribir en stdout nada que no sea un mensaje del protocolo. Los logs van a stderr.
  • Una conexión stdio es una sesión. El estado de acp/configure vive mientras vive el proceso.

Streamable HTTP

  • El servidor expone un endpoint único. Por convención es /mcp.
  • El cliente envía POST con JSON-RPC. El servidor responde con JSON o con un stream SSE, y asigna una sesión mediante la cabecera Mcp-Session-Id en la respuesta a initialize. El cliente la reenvía en cada petición.
  • El estado de acp/configure se guarda por sesión HTTP. Dos sesiones no comparten configuración ni credenciales.
  • Un servidor HTTP accesible fuera de localhost DEBE exigir autenticación de transporte, por ejemplo Authorization: Bearer <token>. Esa autenticación protege el servidor y es independiente de las credenciales de la fuente, que viajan en _meta.acp.
  • Un servidor HTTP remoto DEBE servirse sobre TLS.

3.3 Ciclo de vida

  1. Inicialización. El cliente envía initialize declarando capabilities.experimental.acp. El servidor responde con su capacidad ACP. El cliente envía notifications/initialized.
  2. Configuración (opcional). El cliente envía acp/configure con credenciales, límites, configuración propia de la fuente y recurso por defecto.
  3. Descubrimiento. El cliente llama a tools/list. Normalmente llama también a autocontext sin argumentos para listar recursos y a autocontext_data para listar bloques.
  4. Operación. El modelo llama a tools. El cliente añade _meta.acp a cada tools/call y almacena system_data.
  5. Cierre. Según el transporte: en stdio, el cliente cierra stdin y termina el proceso; en HTTP, envía DELETE al endpoint con la cabecera de sesión.

Un host PUEDE compartir un solo proceso de servidor entre varias cuentas o instancias de la misma fuente. Para eso DEBE enviar las credenciales en _meta.acp de cada llamada en lugar de en acp/configure, porque acp/configure es por sesión y la última escritura manda.


4. Handshake y capacidades

4.1 Capacidad del cliente

El cliente declara que entiende ACP en initialize:

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "initialize",
  "params": {
    "protocolVersion": "2025-06-18",
    "capabilities": {
      "experimental": {
        "acp": {
          "version": "1.0",
          "stores_system_data": true,
          "autocontext_delivery": "inject",
          "multi_table": true,
          "host_tables": true
        }
      }
    },
    "clientInfo": { "name": "Analytika", "version": "0.4.0" }
  }
}
Campo Tipo Req. Descripción
version string Sí Versión ACP del cliente, "MAJOR.MINOR".
stores_system_data boolean Sí true si el host guarda system_data. Con false, el servidor PUEDE omitir system_data para ahorrar ancho de banda.
autocontext_delivery string No Modo de entrega de autocontext preferido por el host: inject, suggest u on_first_call. Es informativo; lo que manda en cada llamada es _meta.acp.
multi_table boolean No true si el host sabe guardar extracciones multitabla (system_data.format: "tables", ver 8.5). Sin él, un servidor multitabla DEBE responder sólo con su tabla principal, como una extracción de una tabla.
host_tables boolean No true si el host resuelve acp/read_table (8.8). Sin él, el servidor NO DEBE pedir tablas al host.

4.2 Capacidad del servidor

El servidor responde:

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "protocolVersion": "2025-06-18",
    "serverInfo": { "name": "ga4", "title": "Google Analytics 4", "version": "1.0.0" },
    "capabilities": {
      "tools": { "listChanged": false },
      "experimental": {
        "acp": {
          "version": "1.0",
          "defaults": { "row_limit": 25, "cost_limit_usd": 0 },
          "credentials_schema": {
            "oneOf": [
              {
                "title": "Service account",
                "type": "object",
                "properties": {
                  "type": { "type": "string", "const": "service_account" },
                  "service_account": {
                    "type": "string",
                    "title": "Service account JSON",
                    "description": "Paste the full JSON key file (includes client_email and private_key).",
                    "contentMediaType": "application/json",
                    "writeOnly": true
                  }
                },
                "required": ["type", "service_account"]
              },
              {
                "title": "OAuth",
                "type": "object",
                "properties": {
                  "type": { "type": "string", "const": "oauth" },
                  "access_token": { "type": "string", "title": "Access token", "writeOnly": true },
                  "refresh_token": { "type": "string", "title": "Refresh token", "writeOnly": true }
                },
                "required": ["type", "access_token"]
              }
            ]
          },
          "resource_id": {
            "title": "GA4 property",
            "description": "Numeric GA4 property id",
            "format": "<propertyId>",
            "examples": ["123456789"]
          },
          "autocontext": {
            "cache_ttl_s": 21600,
            "delivery": ["inject", "suggest", "on_first_call"]
          },
          "cost": {
            "currency": "USD",
            "model": "free",
            "description": "The GA4 Data API has no monetary cost; it consumes property quota tokens (see system_info.source.property_quota)."
          },
          "extraction_tools": ["run_report", "run_report_by_regex_cluster", "compare_conversion_models"]
        }
      }
    },
    "instructions": "Google Analytics 4 ACP. Start with autocontext for the resource_id (numeric property id) to learn key events, custom dimensions and channels before requesting reports."
  }
}
Campo Tipo Req. Descripción
version string Sí Versión ACP del servidor.
defaults.row_limit integer Sí Filas que se entregan a la IA si nadie indica otra cosa. Entre 0 y 5000.
defaults.cost_limit_usd number Sí Coste máximo por llamada en USD. Siempre definido: vale 0 si la fuente no tiene coste.
defaults.field_limit integer No Campos por fila que ve la IA en las tablas con vista por campos (model_view). Entre 1 y 200 (ACP_MAX_FIELD_LIMIT); si falta, 20 (ACP_DEFAULT_FIELD_LIMIT). Declararlo indica al host que el ACP tiene tablas de eventos y que tiene sentido ofrecer el ajuste.
defaults.tables string[] No Sólo con table_catalog. Tablas del catálogo que se extraen cuando nadie pide otras. Ausente: todas. []: ninguna, así la IA tiene que pedir las que necesita. Ver 8.7.
table_catalog object[] No Catálogo de tablas que pueden producir las tools multitabla. Opcional: sólo lo declaran los ACP que generan muchos datasets de golpe. Cada entrada: name, title, description?, group? (agrupación en la interfaz), required? (si es true, entra siempre en la selección y la interfaz no permite desmarcarla) y tools? (tools que la producen; ausente: todas las de extracción). Ver 8.7.
credentials_schema Esquema de alta Sí Forma del objeto credentials y formulario con el que el host las pide. Admite variantes con oneOf. Si la fuente no necesita credenciales, {"type":"object","properties":{}}. Ver 5.5.
config_schema Esquema de alta No Configuración propia de la fuente, no secreta (por ejemplo location o billing_project en BigQuery). También es el formulario de configuración. Ver 5.5.
resource_id.title string Recomendado Nombre corto del recurso para la interfaz («GA4 property», «Project, dataset or table»). Si falta, el host muestra resource_id.
resource_id.description string Sí Qué identifica un resource_id, en lenguaje natural.
resource_id.format string Sí Plantilla legible, por ejemplo <project>.<dataset>.<table>.
resource_id.separators string[] No Separadores admitidos en ids compuestos, por ejemplo [".", "/"].
resource_id.examples string[] Sí Uno o más ejemplos válidos.
autocontext.cache_ttl_s integer Sí TTL por defecto de la caché de bloques, en segundos.
autocontext.delivery string[] Sí Modos de entrega que soporta el servidor. on_first_call implica que el servidor sabe adjuntar autocontext.
cost.currency "USD" Sí Siempre USD.
cost.model string Sí free, per_query, per_byte o per_row.
cost.description string Sí Explicación legible de cómo se calcula el coste.
extraction_tools string[] Sí Nombres de las tools que devuelven tablas (kind: "extraction"). El primero es la tool por defecto: la que usa el host cuando una petición guardada no indica tool.

La capacidad y tools/list son la única fuente de verdad sobre un ACP. Todo lo que el host necesita para listarlo, darlo de alta, configurarlo y editar sus consultas sale de aquí; acp.json sólo dice cómo arrancarlo y, si se declara, qué icono mostrar (ver 13.2).

4.3 Negociación

  • Un cliente ACP DEBE comprobar que el servidor expone capabilities.experimental.acp. Si no lo hace, DEBE tratarlo como un servidor MCP normal.
  • La versión se negocia por el número mayor: si el mayor del servidor y el del cliente difieren, el cliente NO DEBE usar las extensiones ACP. Las diferencias de menor son compatibles: los campos nuevos se ignoran si no se conocen.
  • Un servidor DEBE funcionar aunque el cliente no declare la capacidad ACP. Ver 2.2.

5. Configuración obligatoria: límites y credenciales

Todo servidor ACP define valores por defecto para tres variables:

  1. row_limit: el número máximo de filas que se entregan a la IA en model_response. Si la respuesta tiene más, se trunca (o se muestrea) antes de entregarla. La tabla completa sigue en system_data.
  2. cost_limit_usd: el coste máximo por llamada, en dólares. Vale 0 cuando la herramienta no tiene coste, pero siempre está definido.
  3. credentials: un objeto distinto en cada ACP, descrito por credentials_schema, que se usa para autenticarse en la fuente.

Las tres son personalizables en cada llamada, por dos vías que la IA no ve: acp/configure y _meta.acp.

5.1 acp/configure

Es un método JSON-RPC propio de ACP. No es una tool: no aparece en tools/list y el modelo no puede llamarlo.

Petición

{
  "jsonrpc": "2.0",
  "id": 2,
  "method": "acp/configure",
  "params": {
    "row_limit": 50,
    "cost_limit_usd": 2,
    "credentials": { "type": "service_account", "service_account": "{\"type\":\"service_account\",\"client_email\":\"…\",\"private_key\":\"…\"}" },
    "config": { "location": "EU" },
    "default_resource_id": "mi-proyecto.analytics_123"
  }
}
Campo Tipo Descripción
row_limit integer Entre 0 y 5000 (ACP_MAX_ROW_LIMIT).
cost_limit_usd number Mayor o igual que 0.
field_limit integer Entre 1 y 200 (ACP_MAX_FIELD_LIMIT). Campos por fila en las tablas con model_view.
tables string[] Sólo con table_catalog. Selección por defecto de tablas (la que el usuario configuró en la fuente). [] no extrae ninguna. Un nombre fuera del catálogo da ACP_INVALID_ARGUMENTS.
credentials object | null Según credentials_schema. null borra las credenciales de la sesión.
config object Según config_schema. Se fusiona con la configuración anterior clave a clave.
default_resource_id string | null Recurso que se usa cuando una tool no recibe resource_id.

Todos los campos son opcionales. Cada llamada fusiona sus campos con los de la llamada anterior de la misma sesión: un campo ausente conserva su valor.

Respuesta

{
  "jsonrpc": "2.0",
  "id": 2,
  "result": {
    "ok": true,
    "effective": {
      "row_limit": 50,
      "cost_limit_usd": 2,
      "field_limit": 20,
      "has_credentials": true,
      "default_resource_id": "mi-proyecto.analytics_123"
    }
  }
}

La respuesta NO DEBE devolver las credenciales; sólo has_credentials.

Si un límite no es válido, el servidor responde con un error JSON-RPC -32016 (ACP_INVALID_LIMIT) y no modifica la sesión.

5.2 _meta.acp en cada tools/call

El cliente puede sobrescribir cualquier valor en una sola llamada con params._meta.acp:

{
  "jsonrpc": "2.0",
  "id": 7,
  "method": "tools/call",
  "params": {
    "name": "run_report",
    "arguments": { "resource_id": "123456789", "metrics": ["sessions"], "dimensions": ["date"] },
    "_meta": {
      "acp": {
        "credentials": { "type": "service_account", "service_account": "…" },
        "row_limit": 25,
        "cost_limit_usd": 0,
        "config": {},
        "default_resource_id": "123456789",
        "field_limit": 8,
        "tables": ["ga4_hits", "datalayer"],
        "session_id": "conv-8f2c",
        "conversation_id": "conv-8f2c",
        "autocontext_delivered": true,
        "autocontext_blocks": ["key_events", "custom_dimensions"],
        "sampling": "head",
        "inline_max_bytes": 5242880
      }
    }
  }
}

Además de los campos de acp/configure, _meta.acp admite:

Campo Tipo Descripción
session_id string Identificador de la conversación en el host. Lo usa el modo on_first_call para saber si ya se entregó el autocontext. No tiene relación con la sesión de transporte.
conversation_id string Conversación del host, enviada siempre que exista (aunque no haya session_id). Es sólo informativo y no afecta al autocontext: sirve para que el servidor mantenga estado propio por conversación, por ejemplo una pestaña de navegador abierta entre llamadas. Si falta, el servidor PUEDE usar session_id.
autocontext_delivered boolean Indica si el host ya ha entregado el autocontext a la IA. Ver 7.5.
autocontext_blocks string[] Bloques que se adjuntan en el modo on_first_call. Vacío o ausente significa todos los aplicables.
sampling "head" | "spread" Cómo se eligen las filas para la IA cuando hay truncado. head toma las primeras N (en el orden de la consulta). spread las reparte uniformemente por toda la tabla, incluyendo la primera y la última. Por defecto head.
inline_max_bytes integer Tamaño máximo de system_data.table en línea. Por encima se usa table_uri. Por defecto 5 MiB (ACP_INLINE_MAX_BYTES).
call_id string Id de esta llamada, lo pone el host. El servidor lo devuelve en acp/read_table para que dos llamadas a la vez no compartan resolutor.

5.3 Precedencia

Para cada variable, el valor efectivo sale del primer sitio donde esté definida, en este orden:

  1. params._meta.acp de la llamada.
  2. Lo acumulado por acp/configure en la sesión.
  3. capabilities.experimental.acp.defaults del servidor (para row_limit, cost_limit_usd, field_limit y tables).

tables tiene además un nivel por encima de todos: el argumento tables que escribe la IA en la llamada (ver 8.7). No es un límite, es qué datos pide, y por eso sí va en arguments.

config es la excepción: se fusiona por claves en ese mismo orden de prioridad. El valor efectivo aparece en system_info.row_limit, system_info.cost_limit_usd y, en extracciones multitabla, system_info.field_limit de cada respuesta.

Si el valor efectivo de un límite no es válido, la tool responde con el error ACP_INVALID_LIMIT.

5.4 El modelo nunca ve la configuración

  • El host NO DEBE exponer acp/configure, _meta.acp ni las credenciales al modelo, ni como tool ni como texto del prompt.
  • El servidor NO DEBE copiar credenciales en model_response, system_info, system_data ni en mensajes de error.
  • El servidor NO DEBE aceptar límites ni credenciales dentro de arguments. Los argumentos los escribe el modelo; los límites los fija el sistema.

5.5 Esquemas de alta: cómo pide un ACP sus datos

Un host ACP no sabe nada de la fuente que hay detrás de cada servidor: no distingue un GA4 de un BigQuery. Todo lo que tiene que pedir al usuario para dar de alta una fuente lo declara el propio ACP, y el host lo pinta sin código específico. Así, copiar un ACP nuevo en el directorio de sistema basta para que aparezca en la interfaz y pueda configurarse.

El ACP declara dos formularios en su capacidad:

Esquema Se envía como Se guarda Para qué
credentials_schema credentials en acp/configure y _meta.acp Cifrado en el almacén de secretos del host Autenticarse en la fuente.
config_schema config en acp/configure y _meta.acp En claro, con la fuente Ajustes no secretos (región, proyecto de facturación, precio…).

Los límites (row_limit, cost_limit_usd) no van en estos esquemas: son comunes a todo ACP, tienen sus valores por defecto en defaults y el host los pide siempre (sección 5). Tampoco van los resource_id: el host los pide con los metadatos de resource_id (sección 6).

5.5.0 Idioma

Como en MCP, todo el texto que publica un ACP va en inglés: title, description y examples de los esquemas de alta, resource_id, cost.description, títulos y descripciones de tools, argumentos y bloques de autocontext, instructions, avisos (notice, messages, warnings) y mensajes de error. Lo lee cualquier host y cualquier modelo, y el inglés es el idioma común.

El host NO DEBE traducir ni reescribir esos textos: los muestra tal cual. Sólo traduce su propia interfaz (rótulos como «Credenciales», «Límites» o los botones), igual que un cliente MCP muestra su interfaz en el idioma del usuario y las tools del servidor en el suyo.

5.5.1 Base: requestedSchema de MCP elicitation

Un esquema de alta es el mismo subconjunto de JSON Schema que MCP define para elicitation/create (requestedSchema): un objeto plano cuyas propiedades son primitivos. Cualquier host que ya sepa pintar un formulario de elicitation sabe pintar un alta ACP. ACP añade tres anotaciones estándar de JSON Schema y la posibilidad de declarar variantes.

{
  "type": "object",
  "properties": {
    "location": {
      "type": "string",
      "title": "Location",
      "description": "Job location. Empty: the dataset location.",
      "examples": ["EU", "US", "europe-west1"]
    },
    "billing_project": { "type": "string", "title": "Billing project" },
    "price_per_tib_usd": { "type": "number", "title": "Price per TiB (USD)", "default": 6.25, "minimum": 0 }
  },
  "required": []
}

Palabras clave admitidas en cada propiedad:

Palabra clave Tipos Significado para el host
type todas string, number, integer o boolean. Nada de objetos ni arrays anidados.
title todas Etiqueta del campo. Si falta, se usa la clave.
description todas Texto de ayuda bajo el campo.
default todas Valor inicial al dar de alta.
examples todas El primero se usa como placeholder.
enum / enumNames string Selector cerrado; enumNames da las etiquetas.
format string email, uri, date o date-time, como en MCP.
minLength / maxLength string Validación de longitud.
minimum / maximum number, integer Validación de rango.
writeOnly: true string Secreto. Se pinta como contraseña, nunca se muestra el valor guardado y, al editar, dejarlo vacío conserva el anterior.
contentMediaType string Texto largo: se pinta como área de texto. Con application/json el host DEBE comprobar que el texto es JSON válido antes de enviarlo.
const string, number, boolean Valor fijo: el host no lo pinta y siempre lo envía. Sirve de discriminador de variantes.

Y a nivel de objeto:

Palabra clave Significado
properties Campos, en el orden en que se pintan. El ACP decide el orden; el host no reordena.
required Campos obligatorios. El host no deja guardar sin ellos.
oneOf Sólo en credentials_schema. Lista de variantes (cada una, un objeto plano con title y, opcionalmente, description). El host muestra un selector de variante y los campos de la elegida.

5.5.2 Variantes de credenciales

Cuando una fuente admite varias formas de autenticarse, credentials_schema es un oneOf de objetos. Cada variante DEBE llevar una propiedad con const (por convención type) que la identifique; el servidor usa ese valor para saber qué variante le llega, y el host para saber cuál está guardada. El ejemplo de 4.2 declara dos variantes para GA4: cuenta de servicio (un JSON pegado) y OAuth (dos tokens).

Un servidor que sólo tenga una forma de autenticarse declara un objeto plano, sin oneOf.

5.5.3 Valores enviados

  • El host envía los valores tipados: number e integer como números, boolean como booleanos, el resto como cadenas. Un JSON pegado en un campo con contentMediaType: "application/json" viaja como cadena; lo interpreta el servidor.
  • Sólo se envían las claves de la variante elegida, más sus const. Los campos vacíos no obligatorios se omiten.
  • El servidor DEBE validar lo recibido y responder ACP_CREDENTIALS_INVALID si fallan las credenciales, o ACP_INVALID_ARGUMENTS si falla la configuración, con un mensaje que no incluya valores secretos.

5.5.4 Reglas de pintado del host

  • El host DEBE generar los formularios de credenciales y configuración a partir de estos esquemas. NO DEBE tener formularios, validaciones ni textos específicos de un ACP.
  • Un campo writeOnly guardado se muestra vacío con un aviso tipo «Guardado; deja vacío para conservarlo». El host sólo reemplaza las credenciales si el usuario cambia de variante o escribe algo.
  • Si el esquema usa una palabra clave que el host no conoce, el host DEBE ignorarla y seguir pintando el campo como texto.
  • El mismo mecanismo sirve para editar las consultas guardadas: el host pinta los arguments de una tool a partir de su inputSchema. En esos esquemas, contentMediaType (por ejemplo application/sql en query de BigQuery) marca los campos que deben editarse como texto largo; los arrays de primitivos se editan como listas separadas por comas.

5.5.5 Inicio de sesión

Una variante de credentials_schema puede pedir al host que abra un login en lugar de que la persona pegue tokens. La anotación va en el objeto de la variante, no en cada campo:

{
  "type": "object",
  "title": "Your Analytika account",
  "required": ["type", "email", "scopes"],
  "x-acp-login": {
    "provider": "analytika",
    "scopes": ["https://www.googleapis.com/auth/analytics.readonly"]
  },
  "properties": {
    "type": { "const": "portal_oauth" },
    "email": { "type": "string", "title": "Account", "x-acp-filled": true },
    "scopes": { "type": "string", "title": "Authorization", "writeOnly": true, "x-acp-filled": true }
  }
}
Anotación Dónde Significado
x-acp-login.provider Objeto de la variante "google" o "analytika". Otro valor se ignora.
x-acp-login.scopes Objeto de la variante Scopes que el host pide en ese login. Lista de strings.
x-acp-filled: true Propiedad El login rellena el campo. El host NO DEBE pintarlo como input.

Proveedores:

  • google. El host abre su propio OAuth de Google con esos scopes y rellena los campos marcados (client_id, client_secret, refresh_token, access_token, expiry_date, email, según los publique la variante). Lo que se guarda es ese objeto.
  • analytika. El host usa la sesión ya iniciada en el portal (en la referencia, apps.ikaue.com). Lo que se guarda es una referencia, no un token de Google: type: "portal_oauth", el email y los scopes. En cada llamada el host cambia esa referencia por un access token de corta duración y envía al servidor type: "oauth" con access_token, expiry_date y email. El servidor nunca ve la sesión del portal. Si no hay sesión, el host no llama a la tool y pide iniciar sesión.

Un host que no implementa x-acp-login la ignora, como el resto de palabras clave desconocidas (5.5.4). Un host que da de alta fuentes con esta anotación DEBE implementar los dos proveedores que anuncia aceptar. Los textos de la variante siguen en inglés (5.5.0).


6. Recursos y resource_id

6.1 Qué es un resource_id

Un resource_id es una cadena que identifica un recurso dentro de la fuente. Puede ser simple o compuesto. Los componentes de un id compuesto se separan con . o /, según lo que necesite el servicio.

Fuente Formato Ejemplos
GA4 <propertyId> 123456789
Search Console URL con prefijo o sc-domain: https://www.ikaue.com/, sc-domain:ikaue.com
BigQuery <project>[.<dataset>[.<table>]] mi-proyecto, mi-proyecto.analytics_123, mi-proyecto.analytics_123.events_*

Reglas:

  • El servidor DEBE aceptar los ejemplos que publica en resource_id.examples.
  • El servidor DEBERÍA normalizar las variantes razonables. Por ejemplo, el GA4 de referencia acepta properties/123456789, y el de BigQuery acepta mi-proyecto/dataset, backticks y proyectos con dominio (dominio.com:proyecto).
  • El resource_id es un argumento de las tools, no un campo de _meta: la IA lo elige. El host fija el recurso por defecto con default_resource_id.
  • Si una tool necesita recurso y no lo recibe, se usa default_resource_id. Si tampoco hay, la tool responde con ACP_RESOURCE_REQUIRED.
  • Un servidor con recursos jerárquicos PUEDE declarar niveles (por ejemplo, project, dataset y table en BigQuery). Algunos bloques y tools sólo aplican a ciertos niveles.
  • Una tool que sólo trabaja con una tabla local (8.8) no usa resource_id. El recurso es el id que la IA pasa en el argumento marcado x-acp-input: "table_ref". El resource_id de la capacidad puede describirse como no usado (format: "none", examples: []) cuando ninguna tool de ese servidor lo necesita. El alta NO DEBE obligar a elegir un recurso remoto en ese caso.

6.2 Inyección en los esquemas de las tools

Cada tool que necesita recurso DEBE publicar en su inputSchema una propiedad resource_id de tipo string, no obligatoria, cuya descripción repita resource_id.description. Si el argumento falta en la llamada, se usa el recurso por defecto. Las tools que no necesitan recurso NO DEBEN publicarla. En el SDK de referencia, el núcleo añade resource_id a toda tool salvo que la definición marque que no lo necesita (requiresResource: false).

6.3 Sugerencia de los 25 recursos

Llamar a autocontext sin resource_id devuelve los recursos que más probablemente usará el usuario autenticado, como máximo 25 (ACP_RESOURCE_SUGGESTIONS):

{
  "notice": "Showing 25 of 142 available resources, most recently used first. Pass resource_id to get its autocontext.",
  "total_available": 142,
  "resources": [
    { "resource_id": "123456789", "name": "ikaue.com - GA4", "group": "IKAUE", "recent": true },
    { "resource_id": "987654321", "name": "Store - GA4", "group": "Client X", "description": "Currency EUR, time zone Europe/Madrid" }
  ]
}
Campo Tipo Descripción
notice string Frase para la IA.
total_available integer Total de recursos accesibles con esas credenciales.
resources[].resource_id string Id listo para usar como argumento.
resources[].name string Nombre legible.
resources[].description string Opcional.
resources[].group string Opcional. Cuenta, proyecto u organización a la que pertenece.
resources[].recent boolean Opcional. true si el usuario lo ha usado recientemente.

El orden DEBE reflejar la probabilidad de uso. El orden recomendado, que sigue la implementación de referencia, es:

  1. El default_resource_id efectivo.
  2. Los recursos usados recientemente con esas mismas credenciales, ordenados por una puntuación de frecuencia con decaimiento exponencial (semivida de 14 días).
  3. El resto, en el orden en que los devuelve la API de la fuente.

El listado completo DEBERÍA cachearse por identidad de credenciales. La referencia lo cachea 600 segundos.

Esa página no es la cuenta entera. notice DEBE decirlo cuando resources tiene menos elementos que total_available: un nombre que no aparece ahí no prueba que el recurso no exista. autocontext PUEDE aceptar query (string opcional). Con query, la búsqueda recorre todos los recursos (nombre, grupo e id) y la página de 25 sale de las coincidencias, no del recorte inicial. Si hay default_resource_id, notice lo nombra y ese recurso entra en la página aunque no coincida con query. Si el id configurado no está en el listado de la cuenta, la página lo incluye igual, marcado como configurado, para que la IA no diga que no existe.

La respuesta tiene kind: "autocontext" y system_info.resource_id: null.


7. Autocontext

7.1 Bloques

El autocontext se entrega por bloques (capítulos): unidades de contexto con un id estable. Cada servidor define los suyos. Por ejemplo, en GA4:

[
  { "id": "key_events", "description": "Property key events (conversions) with their API metric (keyEvents:<event>)." },
  { "id": "custom_dimensions", "description": "Custom dimensions with their scope, description and API name (customEvent:/customUser:/customItem:)." }
]

Reglas sobre los ids de bloque:

  • DEBEN ser estables entre versiones menores del servidor y cumplir ^[a-z][a-z0-9_]*$.
  • La descripción DEBE decir qué contiene el bloque, no cómo se obtiene.
  • Un bloque PUEDE declarar applies_to: los niveles de recurso a los que aplica.

7.2 autocontext_data

Lista los bloques disponibles. Es una tool reservada y obligatoria.

inputSchema

{
  "type": "object",
  "properties": {
    "resource_id": { "type": "string", "description": "…" }
  }
}

model_response: un array de descriptores. Con resource_id, sólo los que aplican a su nivel.

[
  { "id": "datasets", "description": "Project datasets with their location.", "applies_to": ["project"] },
  { "id": "tables", "description": "Dataset tables and views. Date-sharded tables (name_YYYYMMDD) are grouped as name_* with the shard count and the latest shard.", "applies_to": ["dataset"] },
  { "id": "table_schemas", "description": "Schema (columns, type, mode and description) of the table or of the first 15 tables in the dataset. For sharded tables, the latest shard is used.", "applies_to": ["dataset", "table"] }
]

La respuesta tiene kind: "autocontext_data". No necesita credenciales: el servidor DEBE poder responder sin ellas.

7.3 autocontext

Es una tool reservada y obligatoria.

inputSchema

{
  "type": "object",
  "properties": {
    "resource_id": { "type": "string", "description": "…" },
    "request_data": {
      "type": "array",
      "items": { "type": "string", "enum": ["<ids de bloque>"] },
      "description": "Autocontext blocks to return (see autocontext_data). Empty or omitted = all."
    }
  }
}

La tool se comporta así:

  • Sin resource_id: devuelve la lista de recursos. Ver 6.3.
  • Con resource_id y request_data vacío o ausente: devuelve todos los bloques que aplican al recurso.
  • Con resource_id y request_data: devuelve esos bloques, en ese orden.

model_response (con recurso):

[
  {
    "id": "key_events",
    "description": "Property key events (conversions) with their API metric (keyEvents:<event>).",
    "data": [
      { "event_name": "purchase", "counting_method": "ONCE_PER_EVENT", "metric": "keyEvents:purchase" },
      { "event_name": "generate_lead", "counting_method": "ONCE_PER_SESSION", "metric": "keyEvents:generate_lead" }
    ]
  },
  {
    "id": "custom_dimensions",
    "description": "Custom dimensions with their scope, description and API name (customEvent:/customUser:/customItem:).",
    "error": "No permission on this resource (403): The caller does not have permission"
  }
]
Campo Tipo Descripción
id string Id del bloque.
description string La misma que en autocontext_data.
data JSON El contenido. Suele ser un array de objetos, pero puede ser cualquier JSON.
error string Presente en lugar de data si el bloque falló o no aplica al recurso.

Reglas:

  • Si un bloque falla, la llamada NO DEBE fallar: ese bloque lleva error y los demás se entregan. system_info.warnings indica cuántos fallaron.
  • Si request_data contiene un id que no existe, la llamada falla con ACP_UNKNOWN_BLOCK y el mensaje lista los ids válidos.
  • Si se pide explícitamente un bloque que no aplica al nivel del recurso, ese bloque lleva error con los niveles a los que sí aplica.
  • La respuesta tiene kind: "autocontext".
  • El contenido de un bloque DEBERÍA ser compacto: lo que la IA necesita para escribir bien la siguiente consulta, no un volcado.

7.4 Caché

Es muy recomendable cachear el autocontext de cada recurso dentro del ACP, para no pedir la información estable cada vez.

  • La clave de caché DEBE incluir una identidad de las credenciales (por ejemplo, un hash del client_email o del token) además del recurso y del bloque. Dos usuarios con permisos distintos NO DEBEN compartir entradas de caché.
  • Cada bloque PUEDE tener su propio TTL. Si no lo declara, se usa autocontext.cache_ttl_s. Como referencia: la configuración de una propiedad cambia poco (24 h), el esquema de tablas algo más (6 h) y las estadísticas de volumen bastante (1 h).
  • El servidor PUEDE persistir la caché en disco. La implementación de referencia usa el directorio de la variable de entorno ACP_CACHE_DIR.
  • La caché NO DEBE contener credenciales.

7.5 Modos de entrega

El autocontext llega a la IA por uno de tres medios, que se presentan en orden de preferencia. El host decide cuál usa, por fuente o por recurso.

1. inject: inyectado de forma determinista

Cuando el host sabe que se va a trabajar con una fuente, llama él mismo a autocontext y mete el resultado en el prompt, igual que haría con una skill. Es el modo más eficiente: la IA acierta a la primera y no gasta una iteración.

  • El host envía autocontext_delivered: true en las extracciones.
  • El host DEBERÍA limitar el tamaño inyectado. La referencia usa como tope 16.000 caracteres y 3 recursos por fuente.

2. suggest: sugerido en la descripción de la tool

El host no inyecta, pero la descripción de las tools de extracción indica que lo primero es llamar a autocontext para ese resource_id. La IA hace esa llamada.

  • La descripción de autocontext que genera el servidor ya contiene esa instrucción. El host PUEDE reforzarla en la descripción de las tools de extracción.
  • El host envía autocontext_delivered: true o, si quiere la red de seguridad del modo 3, session_id sin autocontext_delivered. Cuando la IA llama a autocontext con un session_id, el servidor marca ese recurso como entregado en esa sesión.

3. on_first_call: devuelto tras la primera extracción

Si no se da ninguno de los dos anteriores, el ACP entiende que en la sesión no hay autocontexto y lo adjunta a la primera llamada a una tool de extracción, como un extra. No mejora esa primera interacción, pero sí las siguientes.

  • El servidor añade model_response.autocontext (un array de bloques, como en 7.3) y pone system_info.autocontext_attached: true.
  • Los bloques que se adjuntan son autocontext_blocks, o todos los aplicables si viene vacío.
  • Si falla la obtención del autocontext, la extracción NO DEBE fallar por ello: simplemente no se adjunta.

Qué decide el servidor en cada extracción

autocontext_delivered session_id ¿Se adjunta?
true cualquiera Nunca.
false ausente Sí, en cada llamada.
ausente ausente No. Sin sesión el servidor no puede saber si es la primera vez.
false o ausente presente Sí, si es la primera vez para ese par (session_id, resource_id); después, no.

El servidor DEBE implementar esta tabla si declara on_first_call en autocontext.delivery. El estado «ya entregado» es sólo de memoria y dura lo que el proceso.

El host PUEDE además desactivar el autocontext para una fuente (off en la referencia). En ese caso envía autocontext_delivered: true y no menciona autocontext en sus prompts.


8. El resultado en tres partes

Toda respuesta de una tool ACP es un CallToolResult de MCP con esta forma:

{
  "content": [{ "type": "text", "text": "<model_response serializado como JSON>" }],
  "_meta": {
    "acp": {
      "version": "1.0",
      "kind": "extraction",
      "system_info": { },
      "system_data": { }
    }
  }
}
  • model_response va en content[0].text, serializado como JSON. Es lo que consume la IA. Ya viene filtrado por filas y adaptado por el ACP. Ponerlo en content garantiza que cualquier host MCP se lo pasa al modelo.
  • system_info va en _meta.acp.system_info. Informa al sistema sobre la petición realizada. No llega a la IA, pero ayuda al host a entender lo que recibe.
  • system_data va en _meta.acp.system_data, sólo en las extracciones. Siempre contiene headers (la definición estándar de los campos y sus tipos) y table (la tabla de datos como array de objetos con los nombres de las cabeceras), o una referencia a ella.

El servidor PUEDE añadir más elementos a content (por ejemplo, una imagen), pero content[0] DEBE ser el texto de model_response.

8.1 kind

Valor Tool system_data
autocontext autocontext No
autocontext_data autocontext_data No
extraction Tools de extraction_tools Sí
info Otras tools (validar SQL, detalle de tabla…) No
error Cualquiera, cuando falla No

8.2 model_response de una extracción

{
  "notice": "Showing 25 of 254 extracted rows.",
  "description": "GA4 property 123456789. Metrics: sessions, keyEvents:purchase. Dimensions: date, sessionDefaultChannelGroup. Filter: dimension_filters [{\"field\":\"sessionDefaultChannelGroup\",\"match_type\":\"EXACT\",\"value\":\"Organic Search\"}].",
  "dates": ["2026-09-01", "2026-09-30"],
  "total_rows": 254,
  "returned_rows": 25,
  "truncated": true,
  "columns": ["date", "sessionDefaultChannelGroup", "sessions", "keyEvents:purchase"],
  "rows": [
    { "date": "2026-09-01", "sessionDefaultChannelGroup": "Organic Search", "sessions": 1830, "keyEvents:purchase": 41 }
  ],
  "totals": { "sessions": 51244, "keyEvents:purchase": 1107 },
  "extra": { "quota": { "tokensPerDay": { "consumed": 14, "remaining": 199986 } } },
  "warnings": ["Metric 'purchases' mapped to 'ecommercePurchases'."],
  "autocontext": [ ]
}
Campo Tipo Req. Descripción
notice string Sí Frase legible con filas vistas y totales.
description string Sí Frase técnica en inglés de cómo se obtuvo el dato: fuente, sujeto (propiedad, sitio, tabla), métricas, dimensiones y filtro si lo hay. No es un resumen del contenido.
dates string[] Sí Fechas clave YYYY-MM-DD, ya resueltas. Con ventana, inicio y fin (y más fechas si la consulta compara periodos o el SQL trae más literales). Sin ventana, un solo elemento: la fecha local de hoy.
total_rows integer Sí Filas totales extraídas, las que hay en system_data.
returned_rows integer Sí Filas en rows. Como mucho row_limit.
truncated boolean Sí returned_rows < total_rows.
columns string[] Sí Nombres de columna en orden. Coinciden con system_data.headers[].name.
rows object[] Sí Filas muestreadas, con exactamente las claves de columns.
totals object No Totales calculados por la fuente sobre todas las filas, no sólo las visibles.
extra JSON No Información propia de la tool (cuota, bytes procesados…).
warnings string[] No Avisos para la IA (alias aplicados, datos incompletos…).
autocontext object[] No Sólo en el modo on_first_call.

Frases de notice normativas (en inglés, como todo texto del servidor; ver 5.5.0):

  • Sin filas: "The query returned no rows."
  • Sin truncado: "Showing all N extracted rows."
  • Con truncado: "Showing R of N extracted rows."

Con row_limit: 0, rows va vacío: la IA sólo recibe el aviso, las columnas y los totales, y trabaja sobre la copia completa.

8.2.1 Extracción multitabla

Una tool de extracción PUEDE devolver varias tablas relacionadas en una sola llamada (por ejemplo, de una sesión de navegador: hits de GA4, pushes de dataLayer, cookies, peticiones de red…). Sólo se usa si el cliente declaró multi_table: true; si no, el servidor responde sólo con la tabla principal, con la forma de 8.2. Si son muchas, el servidor DEBERÍA declarar un catálogo para que sólo se extraigan las pedidas (8.7).

{
  "notice": "Extracted 3 table(s) with data: ga4_hits (14), datalayer (22), cookies (9). You see a reduced view; the host stores every table in full.",
  "description": "Browser capture of shop.example.com in a session started with https://shop.example.com/.",
  "dates": ["2026-10-01"],
  "manifest": { "pages": [{ "page_index": 0, "url": "https://shop.example.com/", "title": "Shop" }] },
  "tables": {
    "ga4_hits": {
      "title": "GA4 hits",
      "description": "GA4 hits of shop.example.com in a session started with https://shop.example.com/.",
      "dates": ["2026-10-01"],
      "notice": "ga4_hits: 14 rows, 8 of 31 fields.",
      "total_rows": 14,
      "returned_rows": 14,
      "fields": ["en", "tid", "dl", "dt", "gcs", "cid", "ep.page_type", "items"],
      "fields_total": 31,
      "rows": [{ "en": "page_view", "tid": "G-ABC123", "dl": "https://shop.example.com/", "dt": "Shop", "gcs": "G111", "cid": "1.2", "ep.page_type": "home", "items": 0 }]
    },
    "cookies": {
      "title": "Cookies",
      "description": "Cookies of shop.example.com in a session started with https://shop.example.com/.",
      "dates": ["2026-10-01"],
      "notice": "cookies: 9 rows, 8 of 15 fields.",
      "total_rows": 9, "returned_rows": 9,
      "fields": ["name", "domain", "change", "first_party", "value_onexit", "expires", "page_index", "session"],
      "fields_total": 15,
      "rows": [{ "name": "_ga", "domain": ".example.com", "change": "created", "first_party": true, "value_onexit": "GA1.1.1…", "page_index": 0, "session": false }]
    }
  },
  "warnings": []
}
Campo Tipo Req. Descripción
description string Sí Frase técnica en inglés de la extracción completa.
dates string[] Sí Fechas clave de la extracción. Misma regla que en 8.2.
notice string Sí Tablas con datos y sus filas. Sin datos: "No data was extracted."
manifest JSON No Información sólo para la IA que acompaña a las tablas (páginas visitadas, pasos, resumen, selectores…). No son datos: el host NO DEBE guardarla como dataset.
tables object Sí Una entrada por tabla, por nombre ([a-z0-9_]), incluidas las vacías.
tables.*.description string Sí Frase técnica de esa tabla. En el navegador nombra el capítulo, el host y la URL de inicio.
tables.*.dates string[] Sí Fechas clave de esa tabla. Si la tabla no tiene ventana propia, las de la extracción.
tables.*.notice string Sí "<name>: no rows.", "<name>: R of N rows, F of T fields." o con all fields / N rows.
tables.*.fields string[] Sí Campos que ve la IA, en orden.
tables.*.fields_total integer Sí Columnas de la tabla completa.
tables.*.rows object[] Sí Filas de la vista.
not_extracted object No Con table_catalog. Tablas del catálogo que tenían datos pero no se pidieron, con sus filas: {"cookies": 9, "network": 120}. Ver 8.7.
extra, warnings, autocontext   No Como en 8.2.

Vista por campos (model_view). Las tablas de eventos (hits, pushes, peticiones) tienen muchas columnas dispersas y poco interés fila a fila para la IA: se limitan por campos, no por filas. Si el servidor declara una vista para la tabla:

  • La IA ve como mucho field_limit campos: los primeros de model_view.fields que existan en la tabla, en ese orden.
  • No se aplica row_limit. Ve como mucho 50 filas si la llamada devuelve una sola tabla (ACP_MODEL_ROWS_SINGLE) y 10 por tabla si devuelve varias (ACP_MODEL_ROWS_MULTI). Con model_view.max_rows: 0 no ve ninguna fila, sólo el aviso y los campos.
  • Cada valor se recorta a model_view.max_value_chars caracteres (por defecto 80, ACP_MODEL_VIEW_MAX_VALUE_CHARS), terminando en …; objetos y arrays se serializan antes a JSON.
  • Las celdas null o "" se omiten de la fila.

Las tablas sin vista siguen la regla general de 8.2: row_limit filas con todas las columnas, según sampling.

8.3 model_response de las demás tools

  • autocontext sin recurso: ver 6.3.
  • autocontext con recurso: ver 7.3.
  • autocontext_data: ver 7.2.
  • info: JSON libre, definido por cada tool. DEBERÍA ser pequeño.
  • error: {"error": {code, name, message, data?}}. Ver 12.1.

8.4 system_info

Siempre presente, en todas las respuestas.

{
  "acp": "ga4",
  "acp_version": "1.0",
  "tool": "run_report",
  "kind": "extraction",
  "resource_id": "123456789",
  "arguments": { "metrics": ["sessions"], "dimensions": ["date"], "start_date": "28daysAgo", "end_date": "yesterday" },
  "title": "GA4 123456789 · 28daysAgo→yesterday",
  "total_rows": 254,
  "returned_rows": 25,
  "truncated": true,
  "row_limit": 25,
  "cost_limit_usd": 0,
  "cost_estimate_usd": 0,
  "cost_actual_usd": 0,
  "duration_ms": 842,
  "fetched_at": "2026-09-25T14:03:11.402Z",
  "autocontext_attached": false,
  "cached": false,
  "messages": ["Showing 25 of 254 extracted rows."],
  "warnings": [],
  "source": { "property": "properties/123456789", "api": "analyticsdata.v1beta.runReport" }
}
Campo Tipo Req. Descripción
acp string Sí Nombre del servidor (serverInfo.name).
acp_version string Sí Versión ACP.
tool string Sí Tool llamada.
kind string Sí Ver 8.1.
resource_id string | null Sí Recurso efectivo, ya normalizado.
arguments object Sí Argumentos efectivos, ya validados y convertidos. Con ellos el host puede repetir la extracción.
title string No Título legible para nombrar el dataset.
total_rows integer En extracciones Igual que en model_response.
returned_rows integer En extracciones Igual que en model_response.
truncated boolean En extracciones Igual que en model_response.
row_limit integer Sí Límite efectivo.
cost_limit_usd number Sí Límite efectivo.
cost_estimate_usd number Sí Coste estimado antes de ejecutar. 0 si es gratis.
cost_actual_usd number | null Sí Coste real. null si la fuente no lo informa.
duration_ms integer Sí Tiempo total de la llamada en el servidor.
fetched_at string Sí Momento de la respuesta, ISO 8601 UTC.
autocontext_attached boolean Sí true si se adjuntó autocontext (modo 3).
cached boolean No true si los datos salieron de caché.
messages string[] Sí Frases legibles para el sistema. En extracciones incluye el notice.
warnings string[] Sí Avisos no fatales.
source object No Detalles propios de la fuente: API usada, job de BigQuery, bytes procesados…
field_limit integer En multitabla Límite de campos efectivo.
tables object[] En multitabla Por tabla: name, title?, total_rows, returned_rows, fields_total, fields_shown. En multitabla, total_rows y returned_rows de nivel superior son la suma de todas las tablas.
tables_selected string[] Con catálogo Selección efectiva de tablas de la llamada. También se copia en arguments.tables, para que repetir la petición guardada extraiga lo mismo.

8.5 system_data

Sólo en extracciones. Es la entrega completa.

En línea (hasta inline_max_bytes, por defecto 5 MiB serializado en UTF-8):

{
  "format": "inline",
  "row_count": 254,
  "headers": [
    { "name": "date", "type": "date", "role": "dimension" },
    { "name": "sessionDefaultChannelGroup", "type": "string", "role": "dimension", "description": "Session default channel group" },
    { "name": "sessions", "type": "integer", "role": "metric" },
    { "name": "keyEvents:purchase", "type": "number", "role": "metric" }
  ],
  "table": [
    { "date": "2026-09-01", "sessionDefaultChannelGroup": "Organic Search", "sessions": 1830, "keyEvents:purchase": 41 }
  ]
}

Por referencia (cuando la tabla supera el umbral):

{
  "format": "file",
  "row_count": 812345,
  "headers": [ ],
  "table_uri": "file:///C:/Users/me/AppData/Local/Temp/acp-results/bigquery-4c1d….json"
}
Campo Tipo Req. Descripción
format "inline" | "file" Sí Dónde está la tabla.
row_count integer Sí Filas totales. Igual que total_rows.
headers object[] Sí Cabeceras tipadas, en orden.
table object[] Si inline Array de objetos con exactamente las claves de headers.
table_uri string Si file URI del fichero JSON (un array de objetos, con el mismo formato que table).

Reglas de table_uri:

  • Con stdio, el servidor DEBE usar file:// en un directorio temporal que el host pueda leer (la referencia usa ACP_TMP_DIR). El host DEBE leer el fichero al recibir la respuesta y DEBERÍA borrarlo después.
  • Con HTTP remoto, el servidor DEBE usar https:// con una URL de un solo uso o de caducidad corta, protegida por la misma autenticación de transporte. Si no puede ofrecerla, DEBE entregar la tabla en línea.

Multitabla (sólo si el cliente declaró multi_table):

{
  "format": "tables",
  "row_count": 45,
  "headers": [],
  "tables": [
    {
      "name": "ga4_hits",
      "title": "GA4 hits",
      "format": "inline",
      "row_count": 14,
      "headers": [{ "name": "page_index", "type": "integer", "role": "dimension" }, { "name": "en", "type": "string" }],
      "table": [{ "page_index": 0, "en": "page_view" }],
      "model_view": { "fields": ["en", "tid", "dl"], "max_value_chars": 80 }
    },
    { "name": "cookies", "title": "Cookies", "format": "file", "row_count": 9, "headers": [], "table_uri": "file:///…/browser-7a1c….json" }
  ]
}
  • format es "tables", row_count la suma de filas, headers va vacío y cada elemento de tables es un system_data de una tabla (inline o file, con las mismas reglas de table_uri) más name, title? y model_view?.
  • El umbral inline_max_bytes se aplica a cada tabla por separado.
  • El orden de tables es el que da la tool; la primera con primary (o la primera sin más) es la que recibe un cliente sin multi_table.

8.6 Cabeceras y tipos

{ "name": "sessions", "type": "integer", "role": "metric", "description": "Sessions started" }
Campo Tipo Req. Descripción
name string Sí Clave de la columna en las filas. Única.
type string Sí Uno de los tipos de la tabla siguiente.
role string No dimension, metric o attribute.
description string No Significado de la columna.
type Valor JSON Ejemplo
string string "Organic Search"
integer number entero 1830
number number 0.0421
boolean boolean true
date string YYYY-MM-DD "2026-09-01"
datetime string ISO 8601 "2026-09-01T10:15:00.000Z"
json objeto o array {"key":"value"}

Roles:

  • dimension: columna por la que se agrupa (fecha, canal, página).
  • metric: valor numérico agregable (sesiones, clics, ingresos).
  • attribute: dato descriptivo que no se agrega ni agrupa (ids, textos libres).

Normalización de filas:

  • Cualquier celda PUEDE ser null.
  • El servidor DEBE convertir los valores al tipo declarado: números que la API devuelve como texto, fechas YYYYMMDD a YYYY-MM-DD, marcas de tiempo a ISO 8601, BIGINT a number…
  • Si la tool no declara una columna, el servidor DEBE inferir su tipo mirando los valores:
    • Si una columna mezcla integer y number, se trata como number.
    • Si mezcla date y datetime, se trata como datetime.
    • Cualquier otra mezcla se trata como string.
  • El orden de las columnas es el de las cabeceras declaradas y, después, el orden de aparición.

8.7 Catálogo de tablas y selección

Algunos ACP generan muchos datasets de golpe (un navegador saca hits de GA4, dataLayer, cookies, localStorage, red, SEO, rendimiento…). Guardarlos todos en cada llamada llena el proyecto de datos que nadie ha pedido y satura a la IA. Para esos ACP existe un catálogo de tablas opcional: el servidor anuncia qué tablas sabe producir, el usuario elige cuáles se extraen por defecto y la IA pide en cada llamada las que necesita.

Declaración. El servidor añade a su capacidad table_catalog y, si quiere, defaults.tables:

{
  "defaults": { "row_limit": 25, "field_limit": 8, "cost_limit_usd": 0, "tables": [] },
  "table_catalog": [
    { "name": "ga4_hits", "title": "GA4 hits", "group": "GA4", "description": "One row per GA4 event sent to /g/collect." },
    { "name": "datalayer", "title": "dataLayer pushes", "group": "dataLayer" },
    { "name": "cookies", "title": "Cookies", "group": "Storage" },
    { "name": "seo", "title": "SEO", "group": "SEO" }
  ]
}

Los ACP sin catálogo funcionan como siempre: todas sus tablas se extraen.

Argumento tables. En cada tool de extracción cubierta por el catálogo (las de tools de cada entrada o, si falta, todas), el servidor DEBE añadir al inputSchema publicado un argumento opcional:

"tables": {
  "type": "array",
  "items": { "type": "string", "enum": ["ga4_hits", "datalayer", "cookies", "seo"] },
  "uniqueItems": true,
  "description": "Datasets to extract. If omitted, the source defaults are used. Request only what the analysis needs."
}

La descripción DEBERÍA listar cada tabla con su title para que la IA sepa qué pedir.

Selección efectiva. El primero que esté definido:

  1. arguments.tables de la llamada (lo que escribe la IA).
  2. tables de _meta.acp o de acp/configure (los datasets por defecto que el usuario configuró en la fuente).
  3. defaults.tables del servidor.
  4. Todas las tablas del catálogo.

[] es una selección válida: no se extrae ninguna tabla, salvo las marcadas required: true en el catálogo. Esas entran siempre, aunque la selección venga vacía o el usuario no las haya marcado, y la interfaz NO DEBE permitir desmarcarlas. Cualquier nombre fuera del catálogo de esa tool da ACP_INVALID_ARGUMENTS con la lista de nombres válidos.

Respuesta.

  • system_data.tables y model_response.tables sólo contienen las tablas seleccionadas. Las tablas que la tool produce y no están en el catálogo se entregan siempre.
  • model_response.not_extracted lista las tablas del catálogo que tenían filas pero no se pidieron, y el notice lo dice ("Not extracted because they were not requested: cookies (9). Ask for them with tables only if the analysis needs them."). Si la selección es []: "No tables were requested, so no data was extracted.".
  • El manifest se entrega siempre, aunque no se extraiga ninguna tabla: la IA puede seguir navegando o decidir qué pedir.
  • system_info.tables_selected y system_info.arguments.tables llevan la selección efectiva.

Reglas del servidor.

  • Un servidor PUEDE calcular todas las tablas y filtrar al final, o no calcular las que no se piden. En el segundo caso no podrá informar de sus filas en not_extracted.
  • Una tabla no pedida NO DEBE aparecer en system_data: el host guarda un dataset por cada tabla que recibe.

Reglas del host.

  • Si el ACP declara table_catalog, el alta y la edición de la fuente DEBEN ofrecer la lista de tablas (agrupada por group) para elegir los datasets por defecto, partiendo de defaults.tables. Con ninguna marcada, la interfaz DEBE avisar de que no se extraerá nada salvo que la IA lo pida.
  • El host envía esa selección en _meta.acp.tables y DEBERÍA decir a la IA, en la descripción de la tool o de la fuente, cuáles son los datasets por defecto y que puede pedir otros con tables.
  • Si la respuesta trae not_extracted, el host DEBERÍA trasladarlo a la IA junto a los datasets creados.

8.8 Tablas del host

Un servidor puede pedir al host una tabla que la IA ya tiene en local (un dataset fabricado con SQL, un CSV importado, un report) sin que las filas pasen por el modelo. El servidor no sabe si detrás del id hay un dataset, un report o un CSV. Pide el id y recibe cabeceras y filas.

El método es acp/read_table. Lo llama el servidor, durante un tools/call, por el mismo canal JSON-RPC. No es una tool: la IA no lo ve.

Cuándo se puede llamar. Sólo si el cliente declaró host_tables: true. Si no, el servidor NO DEBE enviar el método y la tool que lo necesitaba responde ACP_HOST_TABLES_UNSUPPORTED.

Petición.

{
  "jsonrpc": "2.0",
  "id": 9,
  "method": "acp/read_table",
  "params": { "ref": "ds_sesiones", "call_id": "call-1" }
}
Campo Tipo Req. Descripción
ref string Sí Id opaco. Lo escribió la IA en un argumento de la tool. El protocolo no define el formato: cada host resuelve los suyos.
call_id string No El mismo _meta.acp.call_id de la llamada en curso, si el host lo envió.

Respuesta. Una tabla, con la misma forma que system_data cuando no es multitabla:

{
  "format": "inline",
  "row_count": 2,
  "headers": [
    { "name": "date", "type": "date" },
    { "name": "sessions", "type": "integer" }
  ],
  "table": [
    { "date": "2026-01-01", "sessions": 10 },
    { "date": "2026-01-02", "sessions": 12 }
  ]
}

Si la tabla supera inline_max_bytes, format es "file" y viaja table_uri con las mismas reglas que 8.5. El lector consume el fichero file://.

Argumento de la tool. La IA no pega las filas. El servidor marca el argumento en su inputSchema:

"table_ref": {
  "type": "string",
  "x-acp-input": "table_ref",
  "description": "Id of a host dataset or report. The host resolves the rows."
}

x-acp-input: "table_ref" distingue ese argumento de los literales (destino, modo, nombre de columna), que la IA sigue escribiendo en la llamada. El host no tiene que interpretar el esquema de cada tool: el servidor llama a acp/read_table con el string que recibió.

El id puede nombrar cualquier tabla del registro del host, aunque no haya nacido en un ACP. El servidor pide el id que vino en los argumentos de esta llamada. El host decide qué ids puede ver la conversación.

Errores. Son errores JSON-RPC de la petición acp/read_table (el servidor los convierte en error de tool):

Código Nombre Cuándo
-32020 ACP_HOST_TABLES_UNSUPPORTED El cliente no declaró host_tables.
-32021 ACP_TABLE_NOT_FOUND El host no tiene ese id, o esta conversación no puede verlo. También si el id es una query: la IA materializa un report y pasa ese id.
-32022 ACP_TABLE_TRUNCATED El report sólo guarda una muestra. El mensaje pide el dataset de origen. No se entrega la muestra.

8.8.1 Qué cuenta como referencia local

El ref sigue siendo opaco para el servidor: no lo parsea. El host que guarda resultados tabulares DEBE resolverlo así.

Lo que nombra el id Qué hace el host
Dataset (tabla completa, incluido un CSV ya importado) Entrega todas las filas y, si las conoce, cabeceras tipadas (date, number, boolean; el resto string).
Report que guarda todas las filas Igual que un dataset.
Report que sólo guarda una muestra ACP_TABLE_TRUNCATED. No entrega la muestra. El mensaje nombra el dataset de origen si lo hay.
Query (la receta, no las filas) ACP_TABLE_NOT_FOUND. La IA ejecuta la query, obtiene un report y pasa el id de ese report.
Id desconocido, otro tipo, o una tabla que esta conversación no puede ver ACP_TABLE_NOT_FOUND.

Visibilidad:

  • Una tabla de conversación sólo la resuelve esa conversación (conversation_id de _meta.acp).
  • Una tabla de proyecto o global la resuelve cualquier conversación de ese ámbito.
  • Si el host no envió conversation_id, NO DEBE resolver tablas privadas de una conversación.

La IA escribe el id en el argumento. No pega filas, no pega CSV y no pasa el SQL de una query como si fuera la tabla.

8.8.2 Escribir a partir de una referencia

Una tool de carga o inserción PUEDE tomar las filas con acp/read_table y escribirlas en la fuente cuando la configuración lo permite (10.6). El argumento sigue siendo el id. El servidor no recibe el array de filas en arguments.

Una tool que sólo transforma una tabla local (por ejemplo un pronóstico) no publica resource_id (6.2).

8.9 Ventana de fechas

Una extracción que depende de un rango de fechas DEBE decirlo en el inputSchema, para que el host pueda recargar el dataset cambiando solo ese rango. La anotación es x-acp-date en la propiedad. El host DEBE ignorarla si no la reconoce, y un servidor antiguo sin ella sigue siendo válido.

Hay dos formas, y una tool usa una:

Par de argumentos. Dos strings, inicio y fin inclusivos. Aceptan YYYY-MM-DD, YYYYMMDD, today, yesterday y NdaysAgo. El host los resuelve a YYYY-MM-DD antes de guardarlos.

"start_date": { "type": "string", "x-acp-date": "start", "description": "YYYY-MM-DD, NdaysAgo, yesterday or today." },
"end_date": { "type": "string", "x-acp-date": "end", "description": "YYYY-MM-DD, NdaysAgo, yesterday or today." }

Plantilla SQL. El argumento de la query lleva x-acp-date: "template". Si el texto contiene {{start_date}} y {{end_date}}, el host los sustituye por el rango pedido y esa sustitución es la ventana. Sin esos marcadores la query no tiene rango: repetirla sustituye el dataset entero, con las mismas columnas. El servidor recibe el SQL ya sustituido.

"query": {
  "type": "string",
  "contentMediaType": "application/sql",
  "x-acp-date": "template",
  "description": "SELECT only. Use {{start_date}} and {{end_date}} for the date window."
}

La columna de día del resultado sigue siendo una cabecera type: "date" (o una columna llamada date). Eso le dice al host si puede reescribir días sueltos o solo la tabla entera. No sustituye a x-acp-date: una es el resultado, la otra es la petición.

Al recargar, el host DEBERÍA ofrecer dos modos cuando hay ventana y columna de día: reescribir sólo esos días (days) o sustituir la tabla (all). Una extracción vacía en modo días borra ese rango y deja el resto. Sin columna de día sólo cabe sustituir la tabla. Reescribir NO DEBE cambiar el conjunto de columnas; si cambia, se crea otro dataset.

8.10 Artefactos locales

Algunas tools de tipo info no meten el binario en model_response. Dejan una ruta absoluta en la máquina del servidor y el host decide qué ve la IA. Sólo tiene sentido con un servidor local (stdio, o HTTP en la misma máquina). Un servidor HTTP remoto NO DEBE devolver una ruta file:// que el host no pueda leer.

Campo de model_response Qué es Qué hace el host
screenshot_path Ruta de un JPEG o PNG. DEBE leer el fichero y añadir un bloque de contenido imagen de MCP (base64) junto al texto de model_response, para que el modelo vea la captura. PUEDE omitir ficheros por encima de 4 MiB. El JSON conserva la ruta.
pdf_path Ruta de un PDF exportado (por ejemplo unas diapositivas). NO DEBE analizar el PDF ni enviarlo al modelo. DEBERÍA copiarlo a una carpeta del proyecto y sustituir la ruta absoluta por una relativa (export_path), con bytes y saved.
download_path Ruta de otro fichero descargado. Igual que pdf_path. Si el objeto trae download_as: "markdown", el aviso al modelo dice que el documento quedó en Markdown.

El servidor NO DEBE poner los bytes de la imagen o del PDF dentro de model_response. system_data no interviene: no son tablas.

8.11 URLs que abre una tool

Muchas fuentes tienen enlaces propios (un documento, una hoja, un informe) que piden sesión. Descargarlos por HTTP falla, y el host no tiene por qué saber qué tool los abre. Una tool que acepta un enlace como argumento DEBERÍA declararlo con x-acp-url en esa propiedad: un string o una lista de expresiones regulares (sintaxis de JavaScript, sin delimitadores, sin distinguir mayúsculas).

"file_id": {
  "type": "string",
  "x-acp-url": ["^https?://docs\\.google\\.com/spreadsheets/d/"],
  "description": "Google Sheet id or URL."
}

El host DEBERÍA usarla así:

  • Si la petición de la persona cita una URL que casa con el patrón de una fuente activa, la IA recibe la tool y el argumento que la abren (acp_<tipo>__<tool> con <argumento> = "<url>").
  • Una tool del host que descarga páginas por HTTP DEBERÍA rechazar esa URL y remitir a la tool ACP.

El servidor recibe la URL tal cual y la resuelve. La anotación no cambia la forma del argumento. Un patrón que no compila se ignora. Si dos tools casan, gana la primera del catálogo. Por eso cada patrón debe ser lo más específico posible.


9. Control de costes

9.1 Modelos de coste

cost.model Significado Ejemplo
free Sin coste monetario por llamada. cost_estimate_usd es siempre 0. GA4 Data API, Search Console
per_query Precio fijo por llamada. APIs de pago por petición
per_byte Precio por volumen procesado. BigQuery on-demand
per_row Precio por fila devuelta. APIs de datos de terceros

9.2 Preflight obligatorio

Si la fuente tiene coste, el servidor DEBE estimarlo antes de ejecutar:

  1. Calcula estimate_usd sin coste. En BigQuery se hace con un dry run: jobs.insert con dryRun: true devuelve totalBytesProcessed.
  2. Si estimate_usd > cost_limit_usd, NO DEBE ejecutar la consulta y responde con ACP_COST_LIMIT_EXCEEDED:
{
  "code": -32010,
  "name": "ACP_COST_LIMIT_EXCEEDED",
  "message": "The query would cost about $3.84 and the limit is $1.00. Narrow the query (fewer columns, shorter date range, filter by partition) or ask the user to raise the limit.",
  "data": { "estimate_usd": 3.836, "limit_usd": 1, "total_bytes_processed": 674810580992, "estimated_bytes_billed": 674810580992, "price_per_tib_usd": 6.25, "statement_type": "SELECT" }
}
  1. Si cabe, ejecuta. Si la fuente lo permite, DEBERÍA además fijar un tope en la propia API como red de seguridad. En BigQuery se usa maximumBytesBilled, calculado a partir de cost_limit_usd.
  2. Informa cost_estimate_usd y cost_actual_usd en system_info.

Con cost_limit_usd: 0, cualquier consulta con coste estimado mayor que 0 se rechaza. Es el valor seguro por defecto para fuentes de pago que el usuario no ha configurado.

El mensaje del error DEBE decir a la IA cómo reducir el coste. Los datos del error son para el host, que puede ofrecer al usuario subir el límite.

9.3 Límite de filas

row_limit controla lo que se entrega a la IA, no lo que se extrae. La tool extrae hasta su propio tope (el argumento limit de la tool, si existe) y el servidor muestrea después. Así la copia completa en system_data no depende de cuánto contexto tenga el modelo.

Una tool PUEDE tener un argumento propio para limitar la extracción. Ese argumento lo controla la IA y es distinto de row_limit.


10. Seguridad de credenciales

10.1 Principios

  1. Las credenciales pertenecen al host. El host las guarda cifradas (en el llavero del sistema operativo, por ejemplo) y las inyecta en cada invocación.
  2. Las credenciales viajan por el canal de sistema. Siempre en acp/configure o en _meta.acp.credentials. Nunca en arguments, nunca en variables de entorno del servidor compartidas entre usuarios, nunca en el prompt.
  3. El servidor no las persiste. Las usa durante la llamada, o durante la sesión si llegaron por acp/configure. Las cachés de tokens derivados (un access token obtenido a partir de una cuenta de servicio) PUEDEN existir en memoria, con la identidad de las credenciales en la clave.
  4. Nada de eco. Ninguna respuesta, log o error DEBE contener credenciales ni fragmentos de ellas.
  5. Aislamiento. Las cachés (autocontext, listado de recursos, recursos recientes) DEBEN particionarse por identidad de credenciales.

10.2 Forma de las credenciales

Cada ACP publica su credentials_schema como esquema de alta (5.5). Si hay varias formas de autenticarse, cada variante lleva un discriminador type con const. Lo que llega al servidor tiene esta forma:

{ "type": "service_account", "service_account": "{\"type\":\"service_account\",\"project_id\":\"…\",\"client_email\":\"…\",\"private_key\":\"…\"}" }
{ "type": "oauth", "access_token": "ya29…", "refresh_token": "1//…" }
{ "type": "api_key", "api_key": "sk-…" }

10.3 Errores

  • Si faltan credenciales y la tool las necesita: ACP_CREDENTIALS_MISSING.
  • Si la fuente rechaza las credenciales (401 o 403 de autenticación): ACP_CREDENTIALS_INVALID.

En ambos casos el mensaje es para el usuario, no para la IA: la IA no puede arreglarlo, y el host DEBERÍA mostrárselo al usuario y ofrecerle reconfigurar.

10.4 Solo lectura

Una tool de consulta (SQL, GAQL…) DEBE rechazar las sentencias que modifican datos o esquema con ACP_READ_ONLY_VIOLATION, y DEBERÍA validarlo con la propia fuente (en BigQuery, con el statementType del dry run) en lugar de con expresiones regulares. Una tool declarada como escritura (cargar o insertar) PUEDE modificar datos cuando la configuración de la fuente lo permite, y PUEDE tomar las filas por table_ref (8.8) en lugar de recibirlas en los argumentos.

10.5 Transporte

  • stdio: el canal es privado entre dos procesos del mismo usuario.
  • HTTP: TLS obligatorio fuera de localhost, autenticación de transporte obligatoria, y NO DEBE registrar cuerpos de petición (contienen credenciales).

10.6 Modos de acceso

No es un campo del protocolo: es el vocabulario que DEBERÍA usar el config_schema de un servidor que puede escribir. El host lo pinta como cualquier otro campo de configuración (5.5).

access_mode Qué permite
onlyRead Sólo lectura. Es el valor por defecto.
readAndOnlyAdd También crear y añadir (tablas nuevas, INSERT, subir un fichero, cargar una table_ref).
full También sustituir y borrar, dentro de lo que la tool publique.

Un servidor PUEDE omitir full si no tiene esa operación (BigQuery de referencia sólo publica onlyRead y readAndOnlyAdd, y la escritura es load_rows desde una table_ref).

Excepciones por nombre: un campo de texto JSON, table_rules o file_rules, con contentMediaType: "application/json". Es una lista de { "pattern": "<regex>", "mode": "<access_mode>" }. La primera expresión que encaja gana, así una instancia de sólo lectura puede abrir unas tablas y una instancia plena puede cerrar otras. Si la operación no cabe en el modo efectivo, la tool responde ACP_READ_ONLY_VIOLATION.


11. Responsabilidades del host

El host es quien convierte el protocolo en una experiencia útil. Un host ACP conforme:

11.1 Instalación y descubrimiento

  • DEBE tener un directorio de sistema donde viven los servidores ACP instalados, uno por carpeta, y DEBERÍA leer también los ACP de la carpeta del proyecto abierto, con precedencia sobre los del sistema. Ver 13.1.
  • DEBERÍA permitir instalar un ACP desde un .zip, en el sistema o en el proyecto, comprobando que arranca antes de sustituir la versión anterior (13.1.1), y desinstalarlo.
  • DEBE leer acp.json para listar las fuentes disponibles sin arrancar el proceso.
  • DEBE obtener todo lo demás (formularios, límites por defecto, metadatos de resource_id, tools, tool por defecto) de initialize y tools/list, y NO DEBE contener código, formularios ni textos específicos de un ACP concreto. Copiar un ACP nuevo en el directorio de sistema DEBE bastar para poder darlo de alta.

11.2 Alta de una fuente

Al dar de alta una fuente, el host:

  1. Pinta los formularios de credentials_schema y config_schema según 5.5, valida lo introducido contra el esquema y guarda las credenciales cifradas.
  2. Llama a autocontext sin argumentos para obtener los recursos disponibles.
  3. DEBE obligar al usuario a elegir al menos un recurso por defecto, salvo que ninguna tool del servidor publique resource_id (6.1).
  4. Llama a autocontext_data para listar los bloques y deja al usuario elegir cuáles quiere, o todos si deja la lista vacía.
  5. Deja configurar row_limit, cost_limit_usd y el modo de entrega de autocontext, con los valores por defecto del servidor como punto de partida.
  6. Si el ACP declara table_catalog, deja elegir los datasets por defecto (ver 8.7).

11.3 En cada llamada

  • DEBE añadir _meta.acp con las credenciales, los límites y el recurso por defecto de esa fuente.
  • DEBE pasar al modelo sólo content, es decir, model_response.
  • DEBE guardar system_data completo en un almacenamiento común a la IA y al sistema de análisis.
  • DEBE decirle a la IA, por escrito, junto a model_response:
    • El total de filas de la consulta y cuántas se le entregan. Por ejemplo: «Showing 25 of 254 extracted rows.».
    • El identificador del recurso almacenado y con qué tools consultarlo. Por ejemplo: «La extracción completa se encuentra en el dataset ‘12345’, que puede consultarse con las tools query_dataset y pivot_dataset».
  • DEBERÍA guardar system_info.arguments junto al dataset para poder repetir la extracción.
  • DEBERÍA enviar conversation_id en _meta.acp en todas las llamadas hechas dentro de una conversación, y field_limit si el usuario lo ha ajustado en la fuente.
  • Con system_data.format: "tables", el host:
    • DEBE guardar un dataset por tabla con filas. Las vacías sólo se mencionan a la IA («Sin datos: …»).
    • DEBERÍA nombrar cada dataset con el título de la extracción y el de la tabla, y registrar una única petición repetible ligada a todos ellos, recordando qué tabla corresponde a cada dataset.
    • Al repetir la petición en modo reemplazar o añadir, DEBERÍA escribir cada tabla en su dataset hermano.
    • DEBE decirle a la IA qué dataset corresponde a cada tabla y que en model_response sólo ve una vista reducida.
    • NO DEBE guardar manifest como datos: es parte de lo que ve la IA.
    • En un ACP de navegador (u otro con datasets por host), DEBERÍA acumular en el mismo dataset las extracciones sucesivas del mismo resource_id (origen) dentro del mismo ámbito (p. ej. la misma conversación), identificando el destino por origin (acp:<tipo>:<resource_id>#<tabla>), en lugar de crear uno nuevo por cada navegación.
  • Con table_catalog, DEBE enviar en _meta.acp.tables los datasets por defecto de la fuente si el usuario los ha elegido (incluyendo siempre las entradas required), y DEBERÍA trasladar a la IA not_extracted (qué no se extrajo y cómo pedirlo).
  • Si declaró host_tables, DEBE resolver acp/read_table según 8.8.1 y DEBE enviar call_id cuando pueda haber dos llamadas a la vez.
  • DEBE leer x-acp-date para saber qué argumentos forman la ventana, sustituir {{start_date}} y {{end_date}} antes de llamar al servidor cuando el argumento es template, y DEBERÍA poder recargar el dataset por días o entero (8.9).
  • Si model_response trae screenshot_path, pdf_path o download_path, DEBE tratarlos como en 8.10.
  • Si las credenciales guardadas son una referencia portal_oauth, DEBE materializar el access token antes de la llamada (5.5.5). El servidor no recibe la referencia del portal.

11.4 Autocontext

  • DEBE implementar al menos uno de los tres modos de entrega y señalizarlo con autocontext_delivered y session_id según 7.5.
  • DEBERÍA preferir inject cuando sabe qué fuente se va a usar.

11.5 Exposición de tools al modelo

El host PUEDE exponer las tools del ACP con otro nombre o con parámetros adicionales. Por ejemplo, Analytika las expone como acp_<type>__<tool> y añade un parámetro source para elegir entre varias cuentas de la misma fuente. Si lo hace:

  • DEBE quitar sus parámetros propios antes de reenviar arguments al servidor.
  • NO DEBE exponer acp/configure ni ningún parámetro que permita al modelo cambiar límites o credenciales.
  • En la descripción de una tool con x-acp-input: "table_ref", DEBERÍA dejar claro que el argumento es el id de un dataset o de un report, no las filas y no una query.

11.6 Errores

  • ACP_COST_LIMIT_EXCEEDED: el host DEBERÍA mostrar la estimación al usuario y ofrecer subir el límite. NO DEBE subirlo automáticamente.
  • ACP_CREDENTIALS_MISSING o ACP_CREDENTIALS_INVALID: el host DEBERÍA pedir al usuario que reconfigure la fuente.
  • Los demás errores DEBEN llegar a la IA (van en content) para que se corrija.
  • ACP_TABLE_NOT_FOUND y ACP_TABLE_TRUNCATED: el mensaje ya dice qué id usar. El host lo entrega a la IA sin reescribirlo.

11.7 Skills del servidor

Si el host tiene catálogo de skills y el ACP instalado trae skills/ (13.4), DEBE publicarlos mientras ese servidor esté instalado. Son de sólo lectura. Un skill de proyecto o global del usuario con el mismo nombre tapa al del ACP. El ACP de proyecto tapa al global del mismo id, así que también tapa sus skills.


12. Errores, versionado y compatibilidad con MCP

12.1 Errores

ACP distingue dos niveles, igual que MCP.

Errores de protocolo. Son errores JSON-RPC (error en lugar de result): método desconocido, JSON inválido, parámetros de acp/configure no válidos. Usan los códigos estándar de JSON-RPC o los códigos ACP de la tabla.

Errores de tool. Aparecen al ejecutar una tool. Se devuelven como resultado con isError: true, para que el modelo los vea y pueda corregirse:

{
  "content": [{ "type": "text", "text": "{\"error\":{\"code\":-32013,\"name\":\"ACP_RESOURCE_REQUIRED\",\"message\":\"Missing resource_id (<propertyId>). Call autocontext without arguments to list the available resources.\"}}" }],
  "isError": true,
  "_meta": {
    "acp": {
      "version": "1.0",
      "kind": "error",
      "system_info": { "acp": "ga4", "tool": "run_report", "kind": "error", "resource_id": null, "…": "…" },
      "error": {
        "code": -32013,
        "name": "ACP_RESOURCE_REQUIRED",
        "message": "Missing resource_id (<propertyId>). Call autocontext without arguments to list the available resources."
      }
    }
  }
}

Códigos ACP

Código Nombre Cuándo ¿Lo corrige la IA?
-32010 ACP_COST_LIMIT_EXCEEDED La estimación supera cost_limit_usd. data lleva estimate_usd y limit_usd. Sí, acotando la consulta.
-32011 ACP_CREDENTIALS_MISSING La tool necesita credenciales y no hay. No.
-32012 ACP_CREDENTIALS_INVALID La fuente rechaza las credenciales. No.
-32013 ACP_RESOURCE_REQUIRED Falta resource_id y no hay recurso por defecto. Sí.
-32014 ACP_RESOURCE_NOT_FOUND El recurso no existe o las credenciales no tienen acceso. Sí, eligiendo otro.
-32015 ACP_UNKNOWN_BLOCK request_data contiene un bloque inexistente. Sí.
-32016 ACP_INVALID_LIMIT row_limit o cost_limit_usd no válidos. No (lo fija el sistema).
-32017 ACP_INVALID_ARGUMENTS Argumentos que no cumplen el esquema, valor no admitido o tool desconocida. Sí.
-32018 ACP_UPSTREAM_ERROR La API de la fuente falló. Es el error por defecto para excepciones no tipadas. A veces.
-32019 ACP_READ_ONLY_VIOLATION Se intentó modificar datos. Sí.
-32020 ACP_HOST_TABLES_UNSUPPORTED La tool necesita acp/read_table y el cliente no declaró host_tables. No.
-32021 ACP_TABLE_NOT_FOUND El host no resuelve ese ref (no existe, no es visible, o es una query). Sí, con otro id.
-32022 ACP_TABLE_TRUNCATED El report sólo tiene una muestra. Hay que pasar el dataset. Sí.

El rango de -32010 a -32049 queda reservado para ACP. Los servidores PUEDEN usar otros códigos por debajo de -32050 para errores propios.

Reglas:

  • message DEBE estar escrito para que la IA sepa qué hacer a continuación.
  • data es opcional y está pensado para el host.
  • content[0].text de un error es siempre {"error": {code, name, message, data?}}.

12.2 Versionado

  • La versión ACP sigue el formato MAJOR.MINOR. Esta especificación es la 1.0, la primera versión publicada.
  • El servidor anuncia su versión en capabilities.experimental.acp.version, en _meta.acp.version y system_info.acp_version de cada resultado y en acp_version de su acp.json. El cliente la anuncia en su capacidad.
  • Menor: añade campos opcionales, bloques o tools. Los clientes y servidores DEBEN ignorar los campos que no conocen.
  • Mayor: cambia el significado de campos existentes. Ver la negociación en 4.3.
  • La versión de MCP (protocolVersion) se negocia de forma independiente, como en MCP.

12.3 Compatibilidad

  • Un servidor ACP DEBE ser un servidor MCP válido.
  • Un servidor ACP NO DEBE exigir _meta.acp para responder tools/list, autocontext_data ni autocontext sin recurso cuando la fuente permite listar sin credenciales.
  • Un host ACP PUEDE usar servidores MCP normales. Simplemente no tendrá doble entrega, límites ni autocontext.

13. Distribución: carpeta del servidor y acp.json

13.1 Dónde se instalan: sistema y proyecto

Un ACP es una carpeta con su acp.json, el ejecutable y lo que necesite. El host busca carpetas de ACP en dos sitios, igual que Cursor busca skills y reglas globales y de proyecto:

Ámbito Carpeta (Analytika) Disponible en
Sistema (global) <userData>/acp/servers/<id>/ Todos los proyectos del usuario.
Proyecto <proyecto>/.analytika/acp/<id>/ Sólo ese proyecto. Viaja con el proyecto (por ejemplo, en su repositorio).

<userData> es la carpeta de datos de la aplicación instalada. En Windows, %APPDATA%\@ikagent\desktop, que da %APPDATA%\@ikagent\desktop\acp\servers.

<userData>/acp/servers/          <proyecto>/.analytika/acp/
├── ga4/                          ├── mi-crm/
│   ├── acp.json                  │   ├── acp.json
│   ├── server.mjs                │   ├── server.mjs
│   ├── skills/                   │   └── skills/
│   │   └── ga4-reporting/        │       └── mi-crm/
│   │       └── SKILL.md          │           └── SKILL.md
│   └── .bundle-hash              └── sources/        (fuentes del proyecto, no es un ACP)
├── gsc/
└── bigquery/

Reglas:

  • Una carpeta es un ACP si contiene acp.json. Se ignoran las carpetas que empiezan por . y, en el proyecto, sources (el host guarda ahí las fuentes dadas de alta).
  • El nombre de la carpeta DEBERÍA coincidir con id en acp.json. El host identifica el ACP por id.
  • Precedencia: si el proyecto y el sistema tienen un ACP con el mismo id, en ese proyecto se usa el del proyecto. Así un proyecto puede fijar su propia versión.
  • Sin registro: basta con copiar o borrar la carpeta. El host vuelve a leer las carpetas cada vez que lista los ACP; no hace falta reiniciarlo.
  • El host PUEDE incluir servidores de serie y sincronizarlos al arrancar. Analytika los marca con un fichero .bundle-hash y los copia sólo si han cambiado. Un ACP instalado por el usuario con el mismo id (sin .bundle-hash) no se sobrescribe, y los de serie no se pueden desinstalar.
  • Los datos de ejecución del servidor (caché, ficheros temporales) NO DEBEN escribirse en la carpeta del servidor. El host indica dónde, con ACP_CACHE_DIR y ACP_TMP_DIR. Un ACP de proyecto recibe una caché distinta de la del global del mismo id.

13.1.1 Paquete .zip

Para distribuir un ACP basta un .zip de su carpeta:

  • acp.json va en la raíz del zip o dentro de una carpeta (lo habitual al comprimir la carpeta del ACP). Si hay varios, manda el menos profundo; todo lo que esté fuera de esa carpeta se ignora.
  • Se admiten zips normales (stored o deflate). No se admiten zip64 ni ficheros cifrados.
  • acp.json puede venir con BOM UTF-8; el host lo tolera.

Al instalar un zip, el host:

  1. Lee acp.json y valida id (^[a-z][a-z0-9_-]*$) y name.
  2. Descomprime en una carpeta temporal junto a la de destino. Rechaza cualquier entrada cuya ruta salga de esa carpeta (../, rutas absolutas).
  3. Arranca el servidor desde la carpeta temporal y comprueba que anuncia capabilities.experimental.acp y responde a tools/list. Si falla, no instala nada y la versión anterior sigue intacta.
  4. Cierra el proceso, para el que pudiera estar en marcha con ese id y sustituye la carpeta.

Desde ese momento el ACP aparece al crear una fuente, con el formulario que declara su capacidad (5.5).

13.2 acp.json

{
  "acp_version": "1.0",
  "id": "bigquery",
  "name": "BigQuery",
  "version": "1.0.0",
  "publisher": "IKAUE",
  "description": "Read-only SQL over BigQuery with a mandatory dry-run and a cost limit.",
  "icon": "icon.svg",
  "transport": { "type": "stdio", "command": "node", "args": ["server.mjs"] },
  "http": { "args": ["server.mjs", "--http", "--port", "7413"] }
}

acp.json es el equivalente a una entrada de mcpServers en el mcp.json de un cliente MCP: identidad, cómo arrancar el proceso y, si se quiere, un icono. No repite formularios, límites, resource_id ni tools. Esos datos cambian con el código del servidor, y duplicarlos sólo sirve para que se desincronicen. El host los obtiene al arrancar el servidor, de la capacidad (4.2) y de tools/list.

Campo Req. Descripción
acp_version Sí Versión ACP que implementa.
id Sí Identificador estable, ^[a-z][a-z0-9_-]*$.
name Sí Nombre legible.
version Sí Versión semántica del servidor.
publisher No Autor.
description Sí Qué hace, en una o dos frases.
icon No Ruta de un icono, relativa a la carpeta del servidor. Sirve para reconocerlo en la lista. No cambia la versión que anuncia el proceso.
transport Sí Cómo lanzarlo. {"type":"stdio","command","args"} para un proceso local, o {"type":"http","url","headers?"} para un servidor remoto ya en marcha. Las rutas de args son relativas a la carpeta.
http No Argumentos para lanzarlo localmente en modo HTTP.

icon apunta a un fichero dentro de la carpeta. El host lo lee sin arrancar el proceso. Reglas:

  • La ruta es relativa. No vale una ruta absoluta, una URL ni un segmento ... El fichero resuelto tiene que quedar dentro de la carpeta del servidor.
  • La extensión es svg, png o webp. Por encima de 256 KB el host lo ignora.
  • Si falta, la ruta sale de la carpeta o el fichero no vale, el host omite el icono. El ACP sigue siendo válido.

Los hosts DEBEN ignorar los campos desconocidos de acp.json. Si un acp.json antiguo trae campos como credentials, resource_id, defaults, config_fields, tools o default_tool, el host no los usa: manda la capacidad.

13.3 Invocación

node server.mjs                     # stdio
node server.mjs --http --port 7413  # Streamable HTTP en http://127.0.0.1:7413/mcp
Variable de entorno Uso
ACP_CACHE_DIR Directorio para la caché de autocontext y los recursos recientes. Si no existe, la caché es sólo de memoria.
ACP_TMP_DIR Directorio para los ficheros de table_uri.
ACP_PORT Puerto HTTP si no se pasa --port.
ACP_HTTP_TOKEN Si está definida, el modo HTTP exige Authorization: Bearer <token>.

Ninguna variable de entorno contiene credenciales de la fuente.

13.4 Skills que viajan con el servidor

instructions de MCP y el autocontext dicen qué es la fuente. Un skill dice cómo trabajarla en este producto: el orden de las tools, qué id local pasar, qué no hacer a mitad de una captura. Vive en la carpeta del ACP, no en un prompt MCP.

<id>/skills/<nombre>/SKILL.md

<nombre> coincide con ^[a-z0-9]+(?:-[a-z0-9]+)*$ y con el name del frontmatter. SKILL.md es markdown con frontmatter YAML:

---
name: ga4-reporting
description: When to pull a GA4 property and how to pass a local dataset id onward.
icon: icon.svg
---

Cuerpo en markdown. Lo lee la IA cuando la descripción encaja.

Reglas:

  • name y description son obligatorios. description es la pista de cuándo aplica; el cuerpo son los pasos. Los dos van en inglés (5.5.0).
  • icon es opcional. Misma regla que el de acp.json: ruta relativa a la carpeta del skill, extensión svg, png o webp, como mucho 256 KB, sin salir de esa carpeta. Si no vale, el host no muestra icono y el skill sigue publicándose.
  • Puede haber ficheros auxiliares en la misma carpeta. SKILL.md es el único obligatorio.
  • El host DEBE ignorar una subcarpeta sin SKILL.md y un nombre que no cumpla el patrón.
  • Son de sólo lectura. Se actualizan al sustituir la carpeta del ACP, no desde el editor de skills del usuario.
  • Precedencia de servidores: el ACP de proyecto tapa al global del mismo id, y con él sus skills (13.1).
  • Precedencia de nombres: un skill de proyecto o global creado por el usuario con el mismo name tapa al del ACP. El del usuario se puede editar; el del ACP no.
  • Si dos ACP instalados publican el mismo name, el host los ordena por id de servidor. El primero conserva el nombre de carpeta. Cada uno de los siguientes se publica como <id>--<nombre>.
  • El catálogo los marca con un alcance propio del servidor (acp), distinto de sistema, global y proyecto, para que la IA pueda leerlos y la interfaz no ofrezca editarlos.
  • Un host sin catálogo de skills ignora la carpeta. El servidor sigue siendo válido.

14. Guía de implementación y checklist de conformidad

14.1 Cómo construir un ACP paso a paso

  1. Parte de un servidor MCP. Usa el SDK oficial de tu lenguaje y expón tools/list y tools/call.
  2. Declara la capacidad en capabilities.experimental.acp con defaults, credentials_schema, resource_id, autocontext, cost y extraction_tools. Escribe credentials_schema y config_schema como esquemas de alta (5.5): con title, description y examples, en el orden en que quieres que se pidan, y con writeOnly en todo lo secreto. Es tu formulario en cualquier host.
  3. Implementa acp/configure como un manejador de petición JSON-RPC personalizado. Guarda la configuración por conexión o sesión.
  4. Lee params._meta.acp en cada tools/call y calcula la configuración efectiva: _meta > configure > defaults.
  5. Define el resource_id: su formato, cómo se normaliza y, si es jerárquico, sus niveles.
  6. Implementa listResources: todos los recursos accesibles con esas credenciales. El orden de las sugerencias sale de ahí.
  7. Define los bloques de autocontext: id, descripción, niveles, TTL y una función que obtiene los datos. Piensa en lo que la IA necesita saber para no equivocarse en la primera consulta: nombres exactos de campos, valores frecuentes, rangos de fechas disponibles, eventos configurados.
  8. Añade las dos tools reservadas, autocontext y autocontext_data, con los esquemas de 7.2 y 7.3.
  9. Escribe las tools de extracción. Cada una devuelve filas (array de objetos) y, si puede, cabeceras tipadas, totales y avisos. Si leen la fuente, publican resource_id. Si sólo leen una tabla local, marcan el argumento con x-acp-input: "table_ref" y no publican resource_id.
  10. Implementa el preflight de coste si la fuente cobra.
  11. Construye el resultado en tres partes: muestrea según row_limit y sampling, genera notice, rellena system_info, pon la tabla completa en system_data o en table_uri.
  12. Implementa el modo on_first_call según la tabla de 7.5.
  13. Convierte cualquier excepción en un error ACP con isError: true. Las excepciones sin tipo se convierten en ACP_UPSTREAM_ERROR.
  14. Empaqueta la carpeta con acp.json (identidad, transporte y, si se quiere, icon), el ejecutable (con sus dependencias incluidas, por ejemplo un único server.mjs generado con esbuild), un README y, si hace falta, skills/<nombre>/SKILL.md (13.4). Comprímela en un .zip para distribuirla, o cópiala tal cual en la carpeta de sistema o en .analytika/acp/ del proyecto (13.1).

La implementación de referencia en TypeScript (@ikagent/acp-sdk) hace los pasos 2 a 4, 8, 11, 12 y 13, y la mitad del 3. Con ella, un servidor se reduce a declarar la definición:

import { runAcpServer, type AcpServerDefinition } from '@ikagent/acp-sdk'

const definition: AcpServerDefinition = {
  name: 'mi-fuente',
  title: 'My source',
  version: '1.0.0',
  capability: {
    defaults: { row_limit: 25, cost_limit_usd: 0 },
    credentials_schema: {
      type: 'object',
      properties: { api_key: { type: 'string', title: 'API key', writeOnly: true } },
      required: ['api_key']
    },
    config_schema: {
      type: 'object',
      properties: { region: { type: 'string', title: 'Region', enum: ['eu', 'us'], default: 'eu' } }
    },
    resource_id: { title: 'Account', description: 'Account id', format: '<accountId>', examples: ['acc_123'] },
    autocontext: { cache_ttl_s: 3600, delivery: ['inject', 'suggest', 'on_first_call'] },
    cost: { currency: 'USD', model: 'free', description: 'No cost per query.' }
  },
  async listResources(ctx) {
    const { api_key } = ctx.requireCredentials()
    const accounts = await fetchAccounts(String(api_key))
    return accounts.map((a) => ({ resource_id: a.id, name: a.name }))
  },
  blocks: [
    {
      id: 'fields',
      description: 'Available fields with their type',
      fetch: async (resourceId, ctx) => fetchFields(resourceId, ctx.requireCredentials())
    }
  ],
  tools: [
    {
      name: 'run_query',
      kind: 'extraction',
      description: 'Extracts rows from the account',
      inputSchema: { type: 'object', properties: { fields: { type: 'array', items: { type: 'string' } } }, required: ['fields'] },
      async run(args, ctx) {
        const rows = await query(ctx.resourceId!, args['fields'] as string[], ctx.requireCredentials())
        return { rows, title: `Query on ${ctx.resourceId}` }
      }
    }
  ]
}

await runAcpServer(definition)

14.2 Checklist de conformidad del servidor

Handshake

  • Expone capabilities.experimental.acp con version, defaults, credentials_schema, resource_id, autocontext, cost y extraction_tools.
  • defaults.cost_limit_usd está definido (0 si es gratis).
  • credentials_schema y config_schema son esquemas de alta válidos: objetos planos de primitivos (o oneOf de ellos, con discriminador const), con title en cada campo y writeOnly en los secretos.
  • resource_id lleva title, y el primer elemento de extraction_tools es la tool principal.
  • Todos los textos publicados (formularios, tools, bloques, avisos y errores) están en inglés (5.5.0).
  • acp.json sólo contiene identidad, transporte y, como mucho, un icon relativo a la carpeta.
  • Funciona con un cliente MCP que no declara ACP.

Configuración

  • Implementa acp/configure con fusión por sesión y responde con effective, sin credenciales.
  • Lee _meta.acp en cada tools/call y aplica la precedencia _meta > configure > defaults.
  • Rechaza límites no válidos con -32016.
  • No acepta credenciales ni límites en arguments.

Recursos

  • autocontext sin resource_id devuelve como máximo 25 recursos, ordenados por probabilidad de uso, con total_available.
  • Acepta los examples publicados y normaliza variantes razonables.
  • Usa default_resource_id si falta el argumento y responde ACP_RESOURCE_REQUIRED si tampoco hay.

Autocontext

  • autocontext_data responde sin credenciales.
  • autocontext con request_data vacío devuelve todos los bloques aplicables.
  • Un bloque que falla lleva error y no hace fallar la llamada.
  • Un bloque desconocido provoca ACP_UNKNOWN_BLOCK.
  • La caché está particionada por identidad de credenciales.
  • Si declara on_first_call, sigue la tabla de 7.5.

Resultados

  • content[0].text es model_response en JSON.
  • Toda respuesta lleva _meta.acp.system_info completo.
  • Las extracciones llevan system_data con headers tipados y la tabla completa, en línea o con table_uri.
  • Si la extracción depende de un rango de fechas, lo declara con x-acp-date (start y end, o template con {{start_date}} y {{end_date}}).
  • Una tool que lee o escribe una tabla del host marca el argumento con x-acp-input: "table_ref" y llama a acp/read_table sólo si el cliente declaró host_tables.
  • Una tool que no usa la fuente remota no publica resource_id.
  • Si puede escribir, el modo por defecto del config_schema es onlyRead y una operación que no cabe en el modo efectivo responde ACP_READ_ONLY_VIOLATION.
  • Si el paquete trae skills/, cada skill tiene SKILL.md con name y description en inglés, y el nombre de carpeta cumple el patrón.
  • rows tiene como mucho row_limit filas; notice indica las filas vistas y el total.
  • Las filas contienen exactamente las columnas de headers, con valores del tipo declarado.

Coste y seguridad

  • Si tiene coste, estima antes de ejecutar y rechaza con ACP_COST_LIMIT_EXCEEDED si se supera el límite.
  • Informa cost_estimate_usd y cost_actual_usd.
  • Nunca devuelve ni registra credenciales.
  • Una tool de consulta rechaza escrituras con ACP_READ_ONLY_VIOLATION. Una tool de escritura sólo escribe si el modo de acceso lo permite.
  • En HTTP remoto: TLS y autenticación de transporte.

Errores

  • Los errores de tool van como isError: true con _meta.acp.error {code, name, message}.
  • Los mensajes dicen a la IA qué hacer.

Catálogo de tablas (opcional)

  • Si genera muchas tablas de golpe, declara table_catalog y, si conviene, defaults.tables ([] para no extraer nada por defecto).
  • Publica el argumento tables (enum del catálogo) en sus tools de extracción.
  • Aplica la precedencia arguments.tables > _meta/acp/configure > defaults.tables > todas, y rechaza nombres desconocidos.
  • Sólo entrega las tablas seleccionadas y lista las demás con datos en not_extracted.

14.3 Checklist de conformidad del host

  • Instala los ACP en un directorio de sistema y los descubre por acp.json.
  • Descubre también los ACP de la carpeta del proyecto, que tapan a los globales con el mismo id.
  • Instala desde .zip sin permitir rutas fuera de la carpeta destino y sólo si el servidor arranca y anuncia ACP.
  • Guarda las credenciales cifradas y las envía sólo por _meta.acp o acp/configure.
  • No tiene código, formularios ni textos específicos de ningún ACP: un ACP nuevo copiado al directorio de sistema se puede dar de alta sin tocar el host.
  • Genera los formularios de credenciales y configuración desde los esquemas de alta, en su orden, y valida required, tipos, rangos y JSON.
  • Muestra los secretos (writeOnly) como guardados sin revelarlos y los conserva si el usuario no los cambia.
  • Genera el editor de consultas guardadas desde el inputSchema de la tool, y usa extraction_tools[0] cuando la consulta no indica tool.
  • Obliga a elegir al menos un recurso por defecto en el alta, salvo que ninguna tool publique resource_id.
  • Si declara host_tables, resuelve datasets y reports completos, rechaza queries y reports truncados, y no entrega tablas de otra conversación.
  • Si credentials_schema trae x-acp-login, abre ese login y no pinta los campos x-acp-filled. Una referencia portal_oauth se cambia por un access token antes de cada llamada.
  • Adjunta al modelo la imagen de screenshot_path y copia al proyecto los ficheros de pdf_path y download_path sin analizar el binario.
  • Publica los skills de skills/ como sólo lectura mientras el ACP esté instalado, y deja que un skill de usuario con el mismo nombre los tape.
  • Si acp.json o un skill traen icon, lo muestra. Si la ruta sale de la carpeta, el fichero no existe o no es svg, png o webp de hasta 256 KB, lo omite.
  • Deja elegir bloques, límites y modo de autocontext.
  • Si el ACP declara table_catalog, deja elegir los datasets por defecto y los envía en _meta.acp.tables.
  • Pasa al modelo sólo model_response.
  • Guarda system_data completo y le dice a la IA dónde está y con qué tools consultarlo.
  • Implementa al menos un modo de entrega de autocontext y lo señaliza correctamente.
  • No expone acp/configure ni ningún control de límites o credenciales al modelo.
  • No sube cost_limit_usd sin confirmación del usuario.

15. Ejemplos completos

Los ejemplos muestran el tráfico JSON-RPC entre el cliente y el servidor. Por legibilidad, en content[0].text se muestra el JSON sin escapar, y se abrevian con … las credenciales y las partes repetidas. Todos usan la versión 1.0.

15.1 GA4: alta de la fuente e informe con truncado

1. Inicialización (ver 4.1 y 4.2).

2. Listado de recursos para el alta

{ "jsonrpc": "2.0", "id": 3, "method": "tools/call",
  "params": {
    "name": "autocontext",
    "arguments": {},
    "_meta": { "acp": { "credentials": { "type": "service_account", "service_account": "…" } } }
  } }
{ "jsonrpc": "2.0", "id": 3, "result": {
  "content": [{ "type": "text", "text": {
    "notice": "3 resources available. Pass resource_id to get its autocontext.",
    "total_available": 3,
    "resources": [
      { "resource_id": "123456789", "name": "ikaue.com", "group": "IKAUE" },
      { "resource_id": "222333444", "name": "Blog", "group": "IKAUE" },
      { "resource_id": "987654321", "name": "Store", "group": "Client X" }
    ]
  } }],
  "_meta": { "acp": { "version": "1.0", "kind": "autocontext", "system_info": {
    "acp": "ga4", "acp_version": "1.0", "tool": "autocontext", "kind": "autocontext", "resource_id": null,
    "arguments": {}, "row_limit": 25, "cost_limit_usd": 0, "cost_estimate_usd": 0, "cost_actual_usd": 0,
    "duration_ms": 611, "fetched_at": "2026-09-25T14:00:02.118Z", "autocontext_attached": false,
    "messages": [], "warnings": []
  } } }
} }

El usuario elige 123456789 como recurso por defecto.

3. Bloques disponibles

{ "jsonrpc": "2.0", "id": 4, "method": "tools/call", "params": { "name": "autocontext_data", "arguments": {} } }
{ "jsonrpc": "2.0", "id": 4, "result": {
  "content": [{ "type": "text", "text": [
    { "id": "property_settings", "description": "Property settings: name, time zone, currency, industry, attribution model, conversion lookback windows and data retention." },
    { "id": "key_events", "description": "Property key events (conversions) with their API metric (keyEvents:<event>)." },
    { "id": "custom_dimensions", "description": "Custom dimensions with their scope, description and API name (customEvent:/customUser:/customItem:)." },
    { "id": "custom_metrics", "description": "Custom metrics with unit, scope and API name (customEvent:<param>)." },
    { "id": "calculated_metrics", "description": "Calculated metrics with their formula and API name (calcMetric:<id>)." },
    { "id": "channel_groups", "description": "Channel groups (system-defined and custom)." },
    { "id": "sessions_by_channel", "description": "Sessions, users and engagement by default channel group over the last 28 days." },
    { "id": "sessions_by_device", "description": "Sessions and users by device over the last 28 days." },
    { "id": "sessions_by_country", "description": "Top 15 countries by sessions over the last 28 days." },
    { "id": "top_events", "description": "Top 50 events by event count over the last 28 days (exact eventName values)." },
    { "id": "top_landing_pages", "description": "Top 25 landing pages by sessions over the last 28 days." },
    { "id": "content_groups", "description": "Sessions by content group over the last 28 days (empty if the property does not use them)." }
  ] }],
  "_meta": { "acp": { "version": "1.0", "kind": "autocontext_data", "system_info": { "…": "…" } } }
} }

4. Configuración de sesión (el host, antes de la conversación)

{ "jsonrpc": "2.0", "id": 5, "method": "acp/configure",
  "params": { "row_limit": 25, "cost_limit_usd": 0, "default_resource_id": "123456789",
              "credentials": { "type": "service_account", "service_account": "…" } } }
{ "jsonrpc": "2.0", "id": 5, "result": { "ok": true,
  "effective": { "row_limit": 25, "cost_limit_usd": 0, "has_credentials": true, "default_resource_id": "123456789" } } }

5. Inyección (modo inject). El host pide dos bloques y los mete en el prompt.

{ "jsonrpc": "2.0", "id": 6, "method": "tools/call",
  "params": { "name": "autocontext", "arguments": { "resource_id": "123456789", "request_data": ["key_events", "custom_dimensions"] } } }
{ "jsonrpc": "2.0", "id": 6, "result": {
  "content": [{ "type": "text", "text": [
    { "id": "key_events", "description": "Property key events (conversions) with their API metric (keyEvents:<event>).",
      "data": [
        { "event_name": "purchase", "counting_method": "ONCE_PER_EVENT", "metric": "keyEvents:purchase" },
        { "event_name": "generate_lead", "counting_method": "ONCE_PER_SESSION", "metric": "keyEvents:generate_lead" }
      ] },
    { "id": "custom_dimensions", "description": "Custom dimensions with their scope, description and API name (customEvent:/customUser:/customItem:).",
      "data": [
        { "api_name": "customEvent:form_name", "display_name": "Form", "scope": "EVENT" },
        { "api_name": "customUser:plan", "display_name": "Plan", "scope": "USER" }
      ] }
  ] }],
  "_meta": { "acp": { "version": "1.0", "kind": "autocontext", "system_info": { "…": "…", "resource_id": "123456789" } } }
} }

6. Extracción pedida por la IA. El modelo sólo escribe arguments; el host añade _meta.

{ "jsonrpc": "2.0", "id": 7, "method": "tools/call",
  "params": {
    "name": "run_report",
    "arguments": {
      "metrics": ["sessions", "keyEvents:purchase"],
      "dimensions": ["date", "sessionDefaultChannelGroup"],
      "start_date": "2026-06-01", "end_date": "2026-08-31"
    },
    "_meta": { "acp": { "autocontext_delivered": true } }
  } }
{ "jsonrpc": "2.0", "id": 7, "result": {
  "content": [{ "type": "text", "text": {
    "notice": "Showing 25 of 736 extracted rows.",
    "total_rows": 736, "returned_rows": 25, "truncated": true,
    "columns": ["date", "sessionDefaultChannelGroup", "sessions", "keyEvents:purchase"],
    "rows": [
      { "date": "2026-06-01", "sessionDefaultChannelGroup": "Organic Search", "sessions": 1830, "keyEvents:purchase": 41 },
      { "date": "2026-06-01", "sessionDefaultChannelGroup": "Direct", "sessions": 902, "keyEvents:purchase": 22 }
    ],
    "totals": { "sessions": 164210, "keyEvents:purchase": 3321 }
  } }],
  "_meta": { "acp": {
    "version": "1.0", "kind": "extraction",
    "system_info": {
      "acp": "ga4", "acp_version": "1.0", "tool": "run_report", "kind": "extraction", "resource_id": "123456789",
      "arguments": { "metrics": ["sessions", "keyEvents:purchase"], "dimensions": ["date", "sessionDefaultChannelGroup"],
                     "start_date": "2026-06-01", "end_date": "2026-08-31" },
      "title": "GA4 123456789 · 2026-06-01→2026-08-31",
      "total_rows": 736, "returned_rows": 25, "truncated": true,
      "row_limit": 25, "cost_limit_usd": 0, "cost_estimate_usd": 0, "cost_actual_usd": 0,
      "duration_ms": 1204, "fetched_at": "2026-09-25T14:03:11.402Z", "autocontext_attached": false,
      "messages": ["Showing 25 of 736 extracted rows."], "warnings": []
    },
    "system_data": {
      "format": "inline", "row_count": 736,
      "headers": [
        { "name": "date", "type": "date", "role": "dimension" },
        { "name": "sessionDefaultChannelGroup", "type": "string", "role": "dimension" },
        { "name": "sessions", "type": "integer", "role": "metric" },
        { "name": "keyEvents:purchase", "type": "number", "role": "metric" }
      ],
      "table": [ "… 736 rows …" ]
    }
  } }
} }

7. Lo que el host entrega a la IA. El model_response anterior, más una nota escrita por el host:

La extracción completa (736 filas) está en el dataset ds_ga4_7f3a, que puede consultarse con las tools execute_sql y explore_sql.

15.2 Search Console: autocontext en la primera llamada

El host no inyecta. Envía session_id y deja autocontext_delivered sin definir.

{ "jsonrpc": "2.0", "id": 12, "method": "tools/call",
  "params": {
    "name": "search_analytics",
    "arguments": { "resource_id": "sc-domain:ikaue.com", "dimensions": ["query"],
                   "filters": [{ "dimension": "page", "operator": "contains", "expression": "/blog/" }] },
    "_meta": { "acp": {
      "credentials": { "type": "service_account", "service_account": "…" },
      "session_id": "conv-8f2c",
      "autocontext_blocks": ["available_date_range", "position_clusters"]
    } }
  } }
{ "jsonrpc": "2.0", "id": 12, "result": {
  "content": [{ "type": "text", "text": {
    "notice": "Showing 25 of 4812 extracted rows.",
    "total_rows": 4812, "returned_rows": 25, "truncated": true,
    "columns": ["query", "clicks", "impressions", "ctr", "position"],
    "rows": [ { "query": "que es ga4", "clicks": 312, "impressions": 9120, "ctr": 0.0342, "position": 3.1 } ],
    "totals": { "clicks": 20931, "impressions": 1402211, "ctr": 0.0149, "position": 18.4 },
    "autocontext": [
      { "id": "available_date_range", "description": "Queryable date range (16 months) and the latest day with reliable data.",
        "data": { "first_date": "2025-05-25", "last_reliable_date": "2026-09-22",
                  "note": "Search Console keeps 16 months of data and publishes it with a 2-3 day delay." } },
      { "id": "position_clusters", "description": "Queries grouped by position cluster (Top-3, Top-10, Top-30, Top-100, Out) over the last 3 months.",
        "data": [ { "clusterPosition": "Top-3", "clicks": 14022, "impressions": 180344 } ] }
    ]
  } }],
  "_meta": { "acp": { "version": "1.0", "kind": "extraction",
    "system_info": { "…": "…", "resource_id": "sc-domain:ikaue.com",
      "arguments": { "dimensions": ["query"], "filters": [ "…" ], "start_date": "2026-08-22", "end_date": "2026-09-22", "search_type": "web" },
      "autocontext_attached": true },
    "system_data": { "format": "inline", "row_count": 4812, "headers": [
      { "name": "query", "type": "string", "role": "dimension" },
      { "name": "clicks", "type": "integer", "role": "metric" },
      { "name": "impressions", "type": "integer", "role": "metric" },
      { "name": "ctr", "type": "number", "role": "metric", "description": "Fraction 0-1" },
      { "name": "position", "type": "number", "role": "metric", "description": "Average position weighted by impressions" }
    ], "table": [ "…" ] } } }
} }

Fíjate en que system_info.arguments incluye las fechas por defecto que aplicó el servidor. Así el host puede repetir la extracción con exactamente las mismas fechas.

Una segunda llamada con el mismo session_id y el mismo recurso ya no incluye autocontext, y lleva autocontext_attached: false.

15.3 BigQuery: validación, coste excedido y extracción grande

1. La IA valida el SQL (tool info)

{ "jsonrpc": "2.0", "id": 20, "method": "tools/call",
  "params": { "name": "validate_query",
    "arguments": { "resource_id": "mi-proyecto.analytics_123",
                   "query": "SELECT event_name, COUNT(*) n FROM `mi-proyecto.analytics_123.events_*` GROUP BY 1" },
    "_meta": { "acp": { "credentials": { "…": "…" }, "cost_limit_usd": 1 } } } }
{ "jsonrpc": "2.0", "id": 20, "result": {
  "content": [{ "type": "text", "text": {
    "valid": true, "statement_type": "SELECT", "read_only": true,
    "total_bytes_processed": 674810580992, "human_bytes": "628.5 GB", "estimated_cost_usd": 3.836,
    "cost_limit_usd": 1, "within_cost_limit": false,
    "referenced_tables": ["mi-proyecto.analytics_123.events_*"],
    "result_schema": [ { "name": "event_name", "type": "STRING" }, { "name": "n", "type": "INTEGER" } ]
  } }],
  "_meta": { "acp": { "version": "1.0", "kind": "info", "system_info": { "…": "…", "cost_estimate_usd": 0 } } }
} }

2. La IA ejecuta igualmente y el preflight la frena

{ "jsonrpc": "2.0", "id": 21, "method": "tools/call",
  "params": { "name": "execute_query",
    "arguments": { "query": "SELECT event_name, COUNT(*) n FROM `mi-proyecto.analytics_123.events_*` GROUP BY 1" },
    "_meta": { "acp": { "credentials": { "…": "…" }, "cost_limit_usd": 1, "default_resource_id": "mi-proyecto.analytics_123" } } } }
{ "jsonrpc": "2.0", "id": 21, "result": {
  "content": [{ "type": "text", "text": { "error": {
    "code": -32010, "name": "ACP_COST_LIMIT_EXCEEDED",
    "message": "The query would cost about $3.84 and the limit is $1.00. Narrow the query (fewer columns, shorter date range, filter by partition) or ask the user to raise the limit.",
    "data": { "estimate_usd": 3.836, "limit_usd": 1, "total_bytes_processed": 674810580992, "price_per_tib_usd": 6.25 }
  } } }],
  "isError": true,
  "_meta": { "acp": { "version": "1.0", "kind": "error",
    "system_info": { "acp": "bigquery", "tool": "execute_query", "kind": "error", "cost_estimate_usd": 3.836, "…": "…" },
    "error": { "code": -32010, "name": "ACP_COST_LIMIT_EXCEEDED", "message": "…", "data": { "estimate_usd": 3.836, "limit_usd": 1 } } } }
} }

3. La IA acota con _TABLE_SUFFIX y la consulta pasa

{ "jsonrpc": "2.0", "id": 22, "method": "tools/call",
  "params": { "name": "execute_query",
    "arguments": { "query": "SELECT event_date, event_name, user_pseudo_id FROM `mi-proyecto.analytics_123.events_*` WHERE _TABLE_SUFFIX BETWEEN '20260901' AND '20260907'" },
    "_meta": { "acp": { "credentials": { "…": "…" }, "cost_limit_usd": 1, "default_resource_id": "mi-proyecto.analytics_123",
                        "autocontext_delivered": true, "sampling": "spread" } } } }
{ "jsonrpc": "2.0", "id": 22, "result": {
  "content": [{ "type": "text", "text": {
    "notice": "Showing 25 of 181022 extracted rows.",
    "total_rows": 181022, "returned_rows": 25, "truncated": true,
    "columns": ["event_date", "event_name", "user_pseudo_id"],
    "rows": [ { "event_date": "20260901", "event_name": "page_view", "user_pseudo_id": "1823.1799" } ],
    "extra": { "bytes_processed": 9663676416, "cache_hit": false }
  } }],
  "_meta": { "acp": { "version": "1.0", "kind": "extraction",
    "system_info": { "…": "…", "cost_estimate_usd": 0.054932, "cost_actual_usd": 0.054932,
      "source": { "job_id": "job_Qm3…", "location": "EU", "total_bytes_billed": 9663676416 } },
    "system_data": {
      "format": "file", "row_count": 181022,
      "headers": [
        { "name": "event_date", "type": "string" },
        { "name": "event_name", "type": "string" },
        { "name": "user_pseudo_id", "type": "string" }
      ],
      "table_uri": "file:///C:/Users/me/AppData/Local/Temp/acp-results/bigquery-4c1d9e1a.json"
    } } }
} }

La tabla completa supera 5 MiB, así que viaja como table_uri. El host lee el fichero, lo guarda en su almacén y lo borra.

4. Intento de escritura

{ "jsonrpc": "2.0", "id": 23, "method": "tools/call",
  "params": { "name": "execute_query", "arguments": { "query": "DELETE FROM `mi-proyecto.analytics_123.tmp` WHERE true" } } }
{ "jsonrpc": "2.0", "id": 23, "result": {
  "content": [{ "type": "text", "text": { "error": {
    "code": -32019, "name": "ACP_READ_ONLY_VIOLATION",
    "message": "This ACP is read-only: only SELECT statements are executed (the query is DELETE)."
  } } }],
  "isError": true,
  "_meta": { "acp": { "version": "1.0", "kind": "error", "system_info": { "…": "…" },
    "error": { "code": -32019, "name": "ACP_READ_ONLY_VIOLATION", "message": "…" } } }
} }

5. Autocontext de un dataset

{ "jsonrpc": "2.0", "id": 24, "method": "tools/call",
  "params": { "name": "autocontext", "arguments": { "resource_id": "mi-proyecto.analytics_123", "request_data": ["tables", "table_schemas"] } } }
{ "jsonrpc": "2.0", "id": 24, "result": {
  "content": [{ "type": "text", "text": [
    { "id": "tables", "description": "Dataset tables and views. Date-sharded tables (name_YYYYMMDD) are grouped as name_* with the shard count and the latest shard.",
      "data": [ { "table": "mi-proyecto.analytics_123.events_*", "type": "TABLE", "shards": 412, "latest_shard": "events_20260923",
                  "query_hint": "FROM `mi-proyecto.analytics_123.events_*` WHERE _TABLE_SUFFIX BETWEEN 'YYYYMMDD' AND 'YYYYMMDD'", "partitioning": null } ] },
    { "id": "table_schemas", "description": "Schema (columns, type, mode and description) of the table or of the first 15 tables in the dataset. For sharded tables, the latest shard is used.",
      "data": [ { "table": "events_*", "columns": [
        { "name": "event_date", "type": "STRING" },
        { "name": "event_params", "type": "RECORD", "mode": "REPEATED" },
        { "name": "event_params.key", "type": "STRING" }
      ] } ] }
  ] }],
  "_meta": { "acp": { "version": "1.0", "kind": "autocontext", "system_info": { "…": "…" } } }
} }

 


Parte III. Uso opcional: ACP Bridge

16. ACP Bridge: servidores ACP desde cualquier cliente MCP

Esta parte no es normativa. Describe una herramienta de la implementación de referencia (packages/acp-bridge) para usar servidores ACP hoy, con un cliente MCP cualquiera (Cursor, Claude Desktop, VS Code…), sin desarrollar un host.

16.1 Qué es y cuándo usarlo

ACP Bridge es un servidor MCP que hace de host ACP. Por arriba habla MCP con el cliente. Por abajo lanza los servidores ACP que tenga instalados en subcarpetas, les pasa credenciales y límites en _meta.acp y guarda cada extracción completa como CSV en la carpeta acp-data/ del proyecto.

El cliente MCP no sabe nada de ACP y no lo necesita: ve tools normales. Lo que ACP aporta lo pone el bridge:

  • La IA recibe una muestra y el resumen; la tabla entera queda en acp-data/<id>.csv.
  • Los totales, filtros y cruces se calculan con SQL sobre esos CSV (acp_query), no a ojo sobre la muestra.
  • Las credenciales están en un fichero del usuario y nunca llegan a la IA.
  • Varias cuentas del mismo conector conviven como fuentes distintas.
  • Un ACP que acepta table_ref (como prediction) lee un CSV de acp-data sin que la IA copie filas.

Úsalo si quieres probar o adoptar ACP con las herramientas que ya tienes. Si construyes una aplicación propia, implementa el host de la Parte II: tendrás credenciales cifradas, conversaciones y una UI de alta que el bridge no ofrece.

flowchart LR
  C["Cliente MCP<br/>(Cursor, Claude…)"] -- "MCP stdio" --> B["ACP Bridge"]
  B -- "MCP + _meta.acp" --> A1["acps/databases"]
  B -- "MCP + _meta.acp" --> A2["acps/prediction"]
  B -- "CSV + meta.json" --> D[("proyecto/acp-data")]
  S["sources.json<br/>(credenciales)"] --> B

16.2 Instalación

El bridge se distribuye como una carpeta autocontenida. Requiere Node.js 22.13 o posterior (usa node:sqlite, que en Node 22 y 24 emite un aviso de característica experimental por stderr).

acp-bridge/
├── server.mjs              # el servidor MCP
├── sources.json            # tus fuentes (lo creas tú a partir del ejemplo)
├── sources.example.json
└── acps/
    ├── databases/          # cualquier carpeta de servidor ACP (sección 13)
    │   ├── acp.json
    │   └── server.mjs
    └── prediction/
        ├── acp.json
        └── server.mjs

En el repositorio de referencia se genera con:

npm run build:acp                      # construye los servidores ACP
npm run build:acp-bridge               # dist/acp-bridge con databases y prediction
npm run build:acp-bridge -- ga4 gsc    # o con los ACP que elijas

Para añadir un ACP basta con copiar su carpeta construida (la que contiene acp.json) dentro de acps/. El bridge lee los acp.json al arrancar y lanza cada servidor según su transport (13.3).

El cliente MCP registra el bridge como cualquier servidor stdio:

{
  "mcpServers": {
    "acp-bridge": {
      "command": "node",
      "args": ["/ruta/acp-bridge/server.mjs", "--project", "/ruta/de/mi/proyecto"]
    }
  }
}
Argumento Por defecto Uso
--project ACP_BRIDGE_PROJECT o la carpeta actual Proyecto donde se crea acp-data/.
--sources <carpeta del bridge>/sources.json Fichero de fuentes. Permite tener uno por proyecto.
--root La carpeta de server.mjs Carpeta que contiene acps/.

En Cursor, .cursor/mcp.json admite ${workspaceFolder}, de modo que el proyecto abierto es el destino de acp-data. El repositorio de referencia incluye esa configuración apuntando a examples/acp-bridge/sources.json (crea la base de ejemplo con node examples/acp-bridge/seed.mjs).

16.3 sources.json

Cada entrada es una fuente: una instancia de un ACP con su cuenta. Es el equivalente del alta de 11.2, escrita a mano.

{
  "defaults": { "row_limit": 25 },
  "sources": [
    {
      "id": "ventas",
      "acp": "databases",
      "label": "Ventas (SQLite)",
      "default_resource_id": "ventas",
      "credentials": { "type": "sqlite", "path": { "projectPath": "data/ventas.sqlite" } }
    },
    {
      "id": "ga4_tienda",
      "acp": "ga4",
      "label": "GA4 tienda",
      "default_resource_id": "123456789",
      "credentials": { "type": "service_account", "service_account": { "file": "secrets/ga4-sa.json" } }
    },
    { "id": "prevision", "acp": "prediction", "label": "Previsión local" }
  ]
}
Campo Obligatorio Significado
id sí Identificador de la fuente: minúsculas, dígitos, _ o -, hasta 40 caracteres. Prefija sus tools.
acp sí id del ACP instalado en acps/.
label no Nombre visible. Aparece en las descripciones de las tools.
credentials no Objeto que pide el credentials_schema del ACP (5.5).
config no Objeto que pide el config_schema del ACP.
default_resource_id no Recurso por defecto (6).
row_limit, field_limit, cost_limit_usd no Límites de 5.1. row_limit son las filas que ve la IA (0 a 5000); el CSV guarda siempre la tabla completa. defaults los fija para todas las fuentes.

Cualquier valor dentro de credentials y config puede ser una referencia en lugar de un literal. Así el fichero se puede compartir sin secretos:

Referencia Se sustituye por
{ "env": "NOMBRE" } El valor de la variable de entorno NOMBRE del proceso del bridge.
{ "file": "ruta" } El contenido del fichero (sin espacios al inicio y al final). Una ruta relativa parte de la carpeta de sources.json.
{ "projectPath": "ruta" } La ruta absoluta de ruta dentro del proyecto.

Un error en sources.json no impide arrancar: el bridge publica sus propias tools y acp_sources devuelve el error para que la IA se lo explique al usuario. El fichero se lee al arrancar; tras editarlo hay que reiniciar el servidor desde el cliente MCP.

16.4 Tools que publica

Tools propias del bridge

Tool Argumentos Qué hace
acp_sources — Fuentes configuradas, su estado (ok o el error de arranque), si tienen credenciales (sin mostrarlas), límites, tools y los ACP instalados sin fuente.
acp_datasets filter? Datasets de acp-data: id, fichero, filas, columnas tipadas, origen, descripción y fechas clave.
acp_query sql, save_as? Una SELECT (o WITH … SELECT) de SQLite con los ids de dataset como nombres de tabla. El resultado completo se guarda como dataset nuevo y la IA recibe hasta 50 filas.

Tools de cada fuente

Cada tool de cada ACP se publica como <fuente>__<tool>: ventas__execute_sql, ventas__autocontext, prevision__forecast. Dos fuentes del mismo ACP publican cada una su juego de tools, y todas comparten un único proceso del servidor ACP, porque las credenciales viajan en cada llamada (5.2).

El esquema de entrada es el del ACP, con estos ajustes en la descripción que ve la IA:

  • La descripción empieza por [<label> · <nombre del ACP>].
  • Los argumentos table_ref (8.8) piden un id de dataset de acp-data.
  • Una tool con un argumento marcado "x-acp-date": "template" (8.9) recibe además start_date y end_date. El bridge resuelve los dos valores (YYYY-MM-DD, NdaysAgo, yesterday, today), sustituye {{start_date}} y {{end_date}} y no los reenvía al ACP.
  • Las tools de extracción avisan de que la tabla completa queda en acp-data.

16.5 Cómo cumple el papel de host

Responsabilidad del host (sección 11) Cómo la resuelve el bridge
Capacidad del cliente (4.1) Declara stores_system_data, multi_table, host_tables y autocontext_delivery: "on_first_call".
_meta.acp en cada llamada credentials, config, default_resource_id y límites salen de sources.json. session_id y conversation_id valen bridge-<uuid>:<fuente>: uno por fuente y arranque del bridge.
Autocontext Lo entrega el servidor en la primera llamada de cada recurso en la sesión (7.5). La IA también puede llamar a <fuente>__autocontext.
Guardar system_data Cada tabla no vacía se escribe como acp-data/<id>.csv con su <id>.meta.json. Una respuesta multitabla genera un dataset por tabla.
acp/read_table Resuelve el ref contra acp-data: id del dataset, id.csv o acp-data/id.csv. Rechaza rutas fuera de la carpeta.
Artefactos (8.10) screenshot_path se adjunta como imagen (hasta 4 MiB). pdf_path y download_path se copian a acp-data/files/ y la IA recibe la ruta relativa.
Errores (12.1) El error ACP llega tal cual a la IA, como {"error": {code, name, message}} con isError. En credenciales ausentes o inválidas y en coste excedido se añade una nota que remite al usuario a sources.json.

16.6 La carpeta acp-data

Cada dataset son dos ficheros con el mismo id:

  • <id>.csv: CSV RFC 4180 en UTF-8, con cabecera. null se escribe vacío; objetos y arrays, como JSON.
  • <id>.meta.json: los metadatos.

El id de una extracción es <fuente>_<tool>[_<tabla>]_<AAAAMMDD>_<HHMMSS>, en minúsculas y válido como nombre de tabla SQL sin comillas. Cada extracción crea un dataset nuevo, de modo que repetir una llamada no borra la anterior. acp_query con save_as usa ese id exacto y lo sustituye si ya existe.

{
  "id": "ventas_execute_sql_20261012_101630",
  "origin": "acp",
  "source": "ventas",
  "acp": "databases",
  "tool": "execute_sql",
  "resource_id": "ventas",
  "arguments": {
    "sql": "SELECT fecha, SUM(pedidos) AS pedidos, SUM(importe) AS importe FROM ventas WHERE fecha BETWEEN '2026-07-01' AND '2026-09-28' GROUP BY fecha ORDER BY fecha"
  },
  "title": "SQLite · ventas.sqlite",
  "description": "SQLite database ventas.sqlite (ventas). SQL: SELECT …",
  "dates": ["2026-07-01", "2026-09-28"],
  "headers": [
    { "name": "fecha", "type": "date" },
    { "name": "pedidos", "type": "integer" },
    { "name": "importe", "type": "number" }
  ],
  "row_count": 90,
  "created_at": "2026-10-12T08:16:30.120Z"
}

origin vale acp para una extracción, query para un resultado de acp_query (con su sql) y csv para un fichero que el usuario deja en la carpeta a mano. Esos CSV sin meta también son datasets: el bridge infiere el tipo de cada columna (entero, número, booleano, fecha, fecha y hora o texto; un valor con ceros a la izquierda, como un código postal, se queda en texto).

acp_query carga en una base SQLite en memoria sólo los datasets que nombra la consulta, con columnas tipadas según el meta, y la descarta al terminar. Los CSV no se modifican nunca: cualquier sentencia que no sea una SELECT devuelve ACP_READ_ONLY_VIOLATION.

16.7 Una sesión de ejemplo

Con la configuración de 16.2 y la base de ejemplo (90 días de ventas por canal):

  1. La IA llama a ventas__execute_sql con sql: "SELECT fecha, SUM(pedidos) AS pedidos, … WHERE fecha BETWEEN '{{start_date}}' AND '{{end_date}}' GROUP BY fecha", start_date: "2026-07-01" y end_date: "2026-09-28". Recibe 25 de 90 filas, el autocontext de la base y la nota «The full extraction was saved as dataset «ventas_execute_sql_20261012_101630″ in acp-data/ventas_execute_sql_20261012_101630.csv. You saw 25 of 90 rows.»
  2. Llama a prevision__forecast con table_ref: "ventas_execute_sql_20261012_101630", time_column: "fecha", value_column: "pedidos", horizon: 14 y method: "holt_winters". El ACP pide la tabla con acp/read_table, el bridge la lee del CSV y la previsión se guarda como prevision_forecast_20261012_101630.
  3. Llama a acp_query con SELECT strftime('%m', fecha) AS mes, SUM(pedidos) AS pedidos FROM ventas_execute_sql_20261012_101630 GROUP BY mes y obtiene los tres totales mensuales, calculados sobre las 90 filas.

En ningún paso la IA vio las credenciales ni copió filas de una tool a otra.

16.8 Límites

El bridge es una forma de empezar, no un host completo. Conviene saber qué no hace:

  • Secretos. Viven en sources.json, en ficheros que este referencia o en variables de entorno, no en un almacén cifrado del sistema. Protege esos ficheros como protegerías un .env y no los subas al repositorio. Aun así, nunca llegan a la IA: no aparecen en descripciones, respuestas ni en acp_sources.
  • Sin conversaciones. Todos los datasets de acp-data son visibles para cualquier llamada, y el autocontext de cada recurso se entrega una sola vez por arranque del bridge, aunque el cliente abra un chat nuevo. La IA puede pedirlo de nuevo con <fuente>__autocontext.
  • Sin UI de alta. Los formularios de 5.5 se rellenan a mano en sources.json. Los métodos que exigen un login interactivo (x-acp-login) no están disponibles; usa la variante de credenciales que no lo necesite (cuenta de servicio, token, clave de API).
  • Coste. El bridge pasa cost_limit_usd al ACP, que lo aplica como límite duro (9.2). No hay diálogo de confirmación: si una consulta lo supera, la IA recibe ACP_COST_LIMIT_EXCEEDED y debe acotarla o pedir al usuario que suba el límite.
  • Escritura. Las tools que escriben (por ejemplo insert_rows de databases) sólo funcionan si el config de la fuente sube el modo de acceso del ACP (10.6). Los datasets de acp-data son de sólo lectura para la IA.

Apéndice A: esquemas de referencia (TypeScript)

Estos tipos son normativos para el formato de los mensajes. Los de capacidad, llamada y resultado coinciden con packages/acp-sdk/src/protocol.ts. Las anotaciones de alta (x-acp-login, x-acp-filled) las interpreta el host con parseAcpSetupSchema.

export const ACP_PROTOCOL_VERSION = '1.0'
export const ACP_DATE_ANNOTATION = 'x-acp-date'   // "start" | "end" | "template"
export const ACP_URL_ANNOTATION = 'x-acp-url'     // patrones de URL que acepta un argumento
export const ACP_KEY = 'acp'                      // clave en experimental y en _meta
export const ACP_METHOD_CONFIGURE = 'acp/configure'
export const ACP_METHOD_READ_TABLE = 'acp/read_table'
export const ACP_INPUT_TABLE_REF = 'table_ref'    // valor de x-acp-input
export const ACP_TOOL_AUTOCONTEXT = 'autocontext'
export const ACP_TOOL_AUTOCONTEXT_DATA = 'autocontext_data'
export const ACP_RESOURCE_SUGGESTIONS = 25
export const ACP_INLINE_MAX_BYTES = 5 * 1024 * 1024
export const ACP_MAX_ROW_LIMIT = 5000
export const ACP_MAX_FIELD_LIMIT = 200
export const ACP_DEFAULT_FIELD_LIMIT = 20
export const ACP_MODEL_ROWS_SINGLE = 50           // filas de model_view con una sola tabla
export const ACP_MODEL_ROWS_MULTI = 10            // filas de model_view por tabla con varias
export const ACP_MODEL_VIEW_MAX_VALUE_CHARS = 80

export type AcpAutocontextDelivery = 'inject' | 'suggest' | 'on_first_call'
export type AcpCostModel = 'free' | 'per_query' | 'per_byte' | 'per_row'
export type AcpColumnType = 'string' | 'integer' | 'number' | 'boolean' | 'date' | 'datetime' | 'json'
export type AcpColumnRole = 'dimension' | 'metric' | 'attribute'
export type AcpResultKind = 'autocontext' | 'autocontext_data' | 'extraction' | 'info' | 'error'

/** Esquema de alta (5.5): subconjunto del requestedSchema de MCP elicitation. */
export interface AcpSetupSchema {
  type: 'object'
  title?: string
  description?: string
  properties: Record<string, AcpSetupProperty>
  required?: string[]
  /** Solo en una variante de credentials_schema. */
  'x-acp-login'?: AcpSetupLogin
}

export interface AcpSetupProperty {
  type: 'string' | 'number' | 'integer' | 'boolean'
  title?: string
  description?: string
  default?: string | number | boolean
  examples?: Array<string | number | boolean>
  enum?: Array<string | number>
  enumNames?: string[]
  format?: 'email' | 'uri' | 'date' | 'date-time'
  minLength?: number
  maxLength?: number
  minimum?: number
  maximum?: number
  const?: string | number | boolean
  writeOnly?: boolean
  contentMediaType?: string
  /** El login rellena el campo. El host no lo pinta. */
  'x-acp-filled'?: boolean
}

/** Pide al host que abra un login para esa variante de credenciales. */
export interface AcpSetupLogin {
  provider: 'google' | 'analytika'
  scopes: string[]
}

/** acp.json (13.2): identidad, arranque e icono opcional, como una entrada de mcp.json. */
export interface AcpPackageManifest {
  acp_version: string
  id: string
  name: string
  version: string
  publisher?: string
  description: string
  /** Ruta relativa a la carpeta del servidor (`svg`, `png` o `webp`). */
  icon?: string
  transport:
    | { type: 'stdio'; command: string; args?: string[]; env?: Record<string, string> }
    | { type: 'http'; url: string; headers?: Record<string, string> }
  http?: { args: string[] }
}

export interface AcpClientCapability {
  version: string
  stores_system_data: boolean
  autocontext_delivery?: AcpAutocontextDelivery
  multi_table?: boolean
  host_tables?: boolean
}

export interface AcpServerCapability {
  version: string
  defaults: { row_limit: number; cost_limit_usd: number; field_limit?: number; tables?: string[] }
  credentials_schema: AcpSetupSchema | { oneOf: AcpSetupSchema[] }
  config_schema?: AcpSetupSchema
  resource_id: { title?: string; description: string; format: string; separators?: string[]; examples: string[] }
  autocontext: { cache_ttl_s: number; delivery: AcpAutocontextDelivery[] }
  cost: { currency: 'USD'; model: AcpCostModel; description: string }
  extraction_tools: string[]
  table_catalog?: AcpTableCatalogEntry[]
}

/** Tabla que puede producir una tool multitabla. */
export interface AcpTableCatalogEntry {
  name: string
  title: string
  description?: string
  group?: string
  required?: boolean                              // siempre en la seleccion efectiva; la UI no la desmarca
  tools?: string[]                                // ausente: todas las de extraccion
}

export interface AcpConfigureParams {
  row_limit?: number
  cost_limit_usd?: number
  field_limit?: number
  tables?: string[]                               // con table_catalog
  credentials?: JsonObject | null
  config?: JsonObject
  default_resource_id?: string | null
}

export interface AcpConfigureResult {
  ok: true
  effective: {
    row_limit: number
    cost_limit_usd: number
    field_limit: number
    tables?: string[]                             // con table_catalog
    has_credentials: boolean
    default_resource_id: string | null
  }
}

export interface AcpCallMeta extends AcpConfigureParams {
  session_id?: string
  conversation_id?: string
  autocontext_delivered?: boolean
  autocontext_blocks?: string[]
  sampling?: 'head' | 'spread'
  inline_max_bytes?: number
  call_id?: string
}

/** Respuesta de acp/read_table. Una tabla, como system_data sin multitabla. */
export interface AcpHostTable {
  format: 'inline' | 'file'
  row_count: number
  headers: AcpHeader[]
  table?: JsonObject[]
  table_uri?: string
}

export interface AcpResourceSummary {
  resource_id: string
  name: string
  description?: string
  group?: string
  recent?: boolean
}

export interface AcpResourceListResponse {
  notice: string
  total_available: number
  resources: AcpResourceSummary[]
}

export interface AcpBlockDescriptor { id: string; description: string; applies_to?: string[] }
export interface AcpAutocontextBlockResult { id: string; description: string; data?: JsonValue; error?: string }

export interface AcpHeader { name: string; type: AcpColumnType; role?: AcpColumnRole; description?: string }

export interface AcpExtractionModelResponse {
  notice: string
  description: string
  dates: string[]                           // YYYY-MM-DD
  total_rows: number
  returned_rows: number
  truncated: boolean
  columns: string[]
  rows: JsonObject[]
  totals?: JsonObject
  extra?: JsonValue
  warnings?: string[]
  autocontext?: AcpAutocontextBlockResult[]
}

/** Vista por campos de una tabla para la IA. */
export interface AcpModelView { fields: string[]; max_value_chars?: number; max_rows?: number }

/** Una tabla en model_response multitabla. */
export interface AcpModelTable {
  title?: string
  notice: string
  total_rows: number
  returned_rows: number
  fields: string[]
  fields_total: number
  rows: JsonObject[]
  description: string
  dates: string[]
}

/** Model_response de una extraccion multitabla. */
export interface AcpMultiTableModelResponse {
  notice: string
  description: string
  dates: string[]
  manifest?: JsonValue
  tables: Record<string, AcpModelTable>
  not_extracted?: Record<string, number>          // con table_catalog
  extra?: JsonValue
  warnings?: string[]
  autocontext?: AcpAutocontextBlockResult[]
}

/** Resumen de una tabla en system_info. */
export interface AcpTableInfo {
  name: string
  title?: string
  total_rows: number
  returned_rows: number
  fields_total: number
  fields_shown: number
}

export interface AcpSystemInfo {
  acp: string
  acp_version: string
  tool: string
  kind: AcpResultKind
  resource_id: string | null
  arguments: JsonObject
  title?: string
  total_rows?: number
  returned_rows?: number
  truncated?: boolean
  row_limit: number
  cost_limit_usd: number
  cost_estimate_usd: number
  cost_actual_usd: number | null
  duration_ms: number
  fetched_at: string
  autocontext_attached: boolean
  cached?: boolean
  messages: string[]
  warnings: string[]
  source?: JsonObject
  field_limit?: number                            // multitabla
  tables?: AcpTableInfo[]                         // multitabla
  tables_selected?: string[]                      // con table_catalog
}

export interface AcpSystemData {
  format: 'inline' | 'file' | 'tables'
  row_count: number
  headers: AcpHeader[]
  table?: JsonObject[]
  table_uri?: string
  tables?: AcpSystemTable[]
}

/** Una tabla de system_data multitabla. */
export interface AcpSystemTable {
  name: string
  title?: string
  format: 'inline' | 'file'
  row_count: number
  headers: AcpHeader[]
  table?: JsonObject[]
  table_uri?: string
  model_view?: AcpModelView
}

export interface AcpErrorPayload { code: number; name: string; message: string; data?: JsonObject }

export interface AcpResultMeta {
  version: string
  kind: AcpResultKind
  system_info: AcpSystemInfo
  system_data?: AcpSystemData
  error?: AcpErrorPayload
}
Iñaki Huerta
CEO de IKAUE

Director de IKAUE. Analista Digital y SEO hace más de 15 años. Internet Lover, Creador de Hilillos y DataHacker.

Iñaki Huerta Profile Picture

También te puede interesar

¡Suscríbete!

RECIBE NUESTRA NEWSLETTER

Registrar nueva cuenta

IKAUE MARKETING ONLINE, S.L. es la Responsable del Tratamiento de tus datos, con la finalidad de gestionar tu registro y remitirte nuestra Newsletter con las últimas novedades y/o promociones. Tienes derecho de acceso, rectificación, supresión, limitación, oposición al tratamiento y portabilidad. Puedes ejercitar tus derechos [email protected]. Más información en la Política de privacidad.