Carrick
Servidor MCP alojado que indexa bases de código TypeScript a través de límites de servicios y repositorios. Los agentes buscan funciones por intención en lugar de por nombre, leen los tipos reales en ambos lados de una llamada y ven cada consumidor de una ruta antes de modificarla.
Servidor MCP alojado
npx add-mcp 'https://api.carrick.tools/mcp'Se instala en Claude Code, Codex, Cursor y más
Documentación
Carrick
Carrick es un índice en vivo, consciente de tipos y de intenciones, que abarca múltiples repositorios de todos los servicios TypeScript en tu organización de GitHub, expuesto a agentes de IA de codificación a través del Model Context Protocol.
Carrick escanea proyectos TypeScript que usan npm, pnpm, Yarn, Bun o Deno. Los proyectos Deno requieren Deno 2.9.4 o superior; la Action proporciona el runtime y lee los manifiestos
deno.jsonodeno.jsoncexistentes. La preparación de dependencias desactiva los scripts de ciclo de vida (detalles). Las funciones entre repositorios necesitan al menos dos servicios indexados en el mismo proyecto Carrick; una instalación de un solo servicio aún obtiene validación dentro del mismo repositorio.
Comienza: regístrate en app.carrick.tools · documentación completa en docs.carrick.tools
Qué puede preguntar un agente
Conecta Claude Code, Cursor, Windsurf o Codex al endpoint MCP de Carrick. Carrick responde preguntas semánticas sobre tu organización que un agente normalmente tendría que buscar con grep entre repositorios, y de forma deficiente:
- "¿Qué funciones manejan la firma de webhooks en nuestros servicios?"
- "¿Dónde deduplicamos usuarios por correo electrónico?"
- "¿Qué llama a
/api/usersy qué forma de respuesta esperan?" - "Muéstrame cada función que reintenta en errores de límite de tasa."
Esto funciona porque el índice combina hechos estructurales, tipos resueltos y una descripción por función de lo que el código realmente hace.
Qué hay en el índice
Para cada función escaneada en cada repositorio de tu organización, Carrick almacena tres capas:
- Estructural. Endpoints declarados, llamadas salientes realizadas, montajes, rutas normalizadas.
- Consciente de tipos. Tipos de solicitud y respuesta resueltos a través del compilador de TypeScript, de modo que la compatibilidad de tipos entre repositorios sea verificable.
- Consciente de intenciones. Una descripción de una o dos frases de lo que hace cada función, generada en el momento del escaneo y almacenada junto con los datos estructurales y de tipos.
La capa de intenciones es la diferencia. Es lo que permite a un agente responder "¿dónde deduplicamos usuarios por correo electrónico?" en lugar de "¿qué funciones se llaman dedupeUser?"
Conecta tu agente
El endpoint MCP está en https://api.carrick.tools/mcp.
claude mcp add --scope user --transport http carrick https://api.carrick.tools/mcp
La autenticación recomendada es iniciar sesión con Carrick: tu agente abre un navegador, haces clic en Aprobar una vez, y ninguna clave API cambia de manos. Pegar una clave manualmente está disponible como alternativa. Para comenzar, regístrate en app.carrick.tools — la guía completa de configuración está en docs.carrick.tools.
Poblar el índice
El índice se puebla ejecutando la GitHub Action de Carrick en cada repositorio TypeScript que quieras indexar. En la rama principal, la acción actualiza la contribución de ese repositorio al índice. En pull requests, la Carrick App publica un comentario de deriva para ti (sin pasos adicionales en el workflow).
name: Carrick
on:
push:
branches: [main]
pull_request:
branches: [main]
# Lets Carrick re-trigger this repo's main scan when a sibling repo in the
# project changes. Optional today and dormant unless enabled server-side —
# included here so it's already wired if you ever turn it on.
repository_dispatch:
types: [carrick-sibling-updated]
permissions:
id-token: write
contents: read
jobs:
carrick:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
# Full git history lets Carrick diff against the last scan and run incrementally.
with:
fetch-depth: 0
- uses: carrick-tools/carrick@v1
No se requieren secretos. El permiso id-token: write permite que la acción acuñe un token OIDC de GitHub Actions de corta duración, que Carrick usa para verificar la identidad del repositorio y autorizar la carga. En pull requests, la Carrick App publica el comentario de deriva por sí misma, por lo que el workflow no necesita permisos adicionales ni un paso de publicación de comentarios. Solo asegúrate de que la Carrick GitHub App esté instalada en la organización y que el repositorio esté conectado a un proyecto en el panel.
Los pull requests abiertos desde forks se omiten con elegancia: GitHub retiene las credenciales OIDC de las ejecuciones de forks, por lo que la acción imprime un aviso y sale con éxito en lugar de fallar la verificación. El escaneo se ejecuta cuando un mantenedor envía la rama al propio repositorio.
Dependencias
Los tipos de paquetes necesitan sus dependencias en disco. Los proyectos Node usan node_modules instalado, y los proyectos Deno usan la caché de dependencias de Deno. La Action prepara esas dependencias antes del análisis:
- Para proyectos Node, la instalación se ejecuta para cada servicio que visitará el escaneo, en el lockfile más cercano de ese servicio. Un monorepo cuyos paquetes tienen sus propios lockfiles se instala paquete por paquete; un workspace que eleva a un solo lockfile en su raíz se instala una vez. El lockfile elige el gestor:
package-lock.jsonejecutanpm ci,pnpm-lock.yamlejecutapnpm install --frozen-lockfile,yarn.lockejecutayarn install,bun.lock/bun.lockbejecutabun install. - Los scripts de ciclo de vida están desactivados en todos los casos, por lo que nada en tu repositorio se ejecuta durante un escaneo.
- Cada comando de instalación tiene un tiempo de espera de cinco minutos. Una instalación fallida o agotada imprime una advertencia, y el escaneo posterior rechaza cualquier servicio cuyas dependencias aún falten (ver más abajo).
- Un
node_modulesexistente omite la instalación Node de ese servicio. Un manifiesto Deno aún activa la preparación de la caché de Deno, que la caché de Deno separada no obtiene de una instalación Node. - Las cachés de descarga de los gestores de paquetes se restauran entre ejecuciones, con clave en el hash de cada lockfile del que la ejecución instala.
Para raíces Deno, la Action ejecuta deno install --frozen --node-modules-dir=none.
Esto prepara la caché de dependencias de Deno y evita que los scripts de ciclo de vida de npm se ejecuten incluso cuando el autor del proyecto los autoriza a través de allowScripts. Una raíz con configuración tanto de Node como de Deno prepara ambos almacenes de dependencias. Antes de la indexación local, instala Deno 2.9.4 o superior y ejecuta el mismo comando de preparación desde la raíz del workspace de Deno. Los proyectos que importan declaraciones generadas deben generar esas declaraciones a través de su compilación normal antes de la indexación.
Un checkout no preparado es rechazado
Carrick se niega a escanear un checkout que no puede tipar, en lugar de cobrar por un índice cuyos tipos son any y no decir nada sobre por qué. La verificación se ejecuta antes de que comience el escaneo, por servicio, sobre lo que es alcanzable desde el propio directorio de ese servicio — una raíz de monorepo que está instalada no dice nada sobre un workspace anidado que no lo está. Dos cosas son rechazadas:
- Dependencias que un lockfile declara y que el árbol no ha instalado. El rechazo nombra el servicio y el comando exacto, porque el lockfile nombra el gestor de paquetes. Un árbol sin lockfile por encima del servicio no declara ninguna instalación y se escanea tal como está. Los servicios Deno solo se preguntan cuando su configuración establece
nodeModulesDir, ya que Deno de otro modo almacena en caché fuera del árbol. - Un mapeo de configuración cuyo directorio de destino no está en el checkout, que el servicio importa a través de él. Una entrada de
pathsde TypeScript, una clave depackage.jsonimportso una entrada de mapa de importación de Deno que apunta, por ejemplo, a un cliente generado cuyo generador no se ha ejecutado. El rechazo nombra el mapeo y el directorio faltante y se detiene ahí: nada en la configuración dice qué llena un directorio generado. Un mapeo dejado por un paquete eliminado, que nada importa, se registra y se escanea más allá — ningún tipo puede seranya través de un mapeo que ninguna importación usa.
Ambos son proxies, por lo que siempre hay una forma de pasar: --allow-unprepared en el comando, CARRICK_ALLOW_UNPREPARED=1 en el entorno, o allow-unprepared: true en la Action (que install-dependencies: false ya implica). Una canalización que escanea un checkout desnudo deliberadamente sigue funcionando; solo lo dice.
Los servicios Deno normalmente omiten tsconfig y usan su manifiesto Deno más cercano. Una configuración TypeScript ordinaria explícita selecciona la ruta de TypeScript. Una configuración Deno explícita debe nombrar ese manifiesto más cercano; deno.json tiene precedencia sobre deno.jsonc cuando ambos existen. Los mapas de importación deben ser archivos locales.
Desactívalo con:
- uses: carrick-tools/carrick@v1
with:
install-dependencies: false
Los registros privados usan tus propias credenciales. Carrick no añade autenticación propia: pon el token que tu .npmrc lee en el entorno del job y el paso de instalación lo hereda.
- uses: carrick-tools/carrick@v1
env:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
Re-analizando todo
Un escaneo lee el modelo una vez por archivo cambiado y reutiliza lo que ya tiene para el resto, lo que hace que un escaneo rutinario sea barato. Ocasionalmente las respuestas mismas necesitan rehacerse en lugar de los archivos: Carrick comienza a extraer algo que no extraía antes, y la caché contiene respuestas de antes de que pudiera. full-scan re-analiza cada archivo para una ejecución.
Pídelo, en lugar de dejarlo activado. Conéctalo a la entrada workflow_dispatch del workflow, que es lo que carrick init genera:
on:
workflow_dispatch:
inputs:
full-scan:
type: boolean
default: false
jobs:
carrick:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: carrick-tools/carrick@v1
with:
full-scan: ${{ inputs.full-scan }}
Luego ejecútalo desde la pestaña Actions, o:
gh workflow run carrick.yml -f full-scan=true
En cualquier otro disparador, la expresión está vacía y el escaneo incremental se ejecuta exactamente como antes.
Un escaneo ordinario indexa un commit una vez por versión del escáner, por lo que una segunda ejecución en un commit sin cambios no almacena nada y lo dice. Un escaneo completo es la excepción: sus respuestas reemplazan lo que el índice tiene para ese commit, porque re-analizar todo es una declaración de que las respuestas almacenadas eran la parte obsoleta.
Herramientas MCP
El endpoint MCP expone el índice como herramientas estructuradas que tu agente puede llamar directamente.
| Herramienta | Propósito |
|---|---|
search_by_intent | Encuentra funciones por lo que hacen — una consulta en inglés sencillo comparada con las descripciones de intención |
list_projects | Los proyectos Carrick en tu workspace y los repositorios conectados de cada proyecto |
list_services | Cada servicio que Carrick ha indexado en tu organización |
list_function_intents | Descripciones de una o dos frases de funciones indexadas, buscables por servicio |
get_api_endpoints | Endpoints declarados por un servicio dado |
get_endpoint_types | Tipos de solicitud y respuesta resueltos para un endpoint específico |
get_type_definition | Tipo TypeScript completamente resuelto por nombre, en toda la organización |
get_service_dependencies | Servicios que llaman a un productor dado |
check_compatibility | Si la llamada del servicio A al servicio B coincide con el contrato del productor |
scaffold | Genera los archivos para incorporar un repositorio: el workflow de GitHub Actions, una guía de agente y un esqueleto de carrick.json |
En pull requests
En pull requests, la Carrick App publica un comentario que resume la deriva detectada contra los servicios indexados: desajustes de tipos entre productores y consumidores, verbos HTTP desajustados, rutas faltantes o huérfanas, y conflictos de versiones de dependencias npm. Actualiza el mismo comentario en su lugar en cada push al PR. Los comentarios de PR están activados por defecto para nuevos proyectos y se pueden alternar por proyecto en el panel; las ejecuciones de PR nunca alteran el índice.
Configuración
Añade un carrick.json a cada servicio indexado para ayudar a clasificar las llamadas salientes.
{
"serviceName": "order-service",
"internalEnvVars": ["USER_SERVICE_URL", "INVENTORY_API"],
"externalEnvVars": ["STRIPE_API", "GITHUB_API"],
"internalDomains": ["https://api.yourcompany.com"],
"externalDomains": ["https://api.stripe.com", "https://api.github.com"]
}
| Campo | Descripción |
|---|---|
serviceName | Nombre amigable para este servicio |
internalEnvVars | Variables de entorno que apuntan a otros servicios en tu organización. Las llamadas se validan contra el índice. |
externalEnvVars | Variables de entorno que apuntan a APIs de terceros. Las llamadas se ignoran. |
internalDomains | Prefijos de URL completos para servicios internos |
externalDomains | Prefijos de URL completos para APIs de terceros a ignorar |
Cuando Carrick ve una llamada como fetch(process.env.ORDER_SERVICE_URL + '/orders'), necesita saber si ORDER_SERVICE_URL apunta interna o externamente. Las variables de entorno no clasificadas aparecen como una sugerencia de configuración en el comentario del PR.
Monorepos
carrick.json es opcional. Sin él, Carrick deriva servicios de los manifiestos de workspace de npm o pnpm; un repositorio simple es un servicio. Una configuración explícita tiene precedencia sobre la derivación. Para establecer límites de servicios e inclusiones de fuentes compartidas, declara un array de services. Cada entrada se escanea independientemente y se indexa como su propio servicio:
{
"includes": {
"lambdas/_shared": {
"externalEnvVars": ["GITHUB_API_BASE"],
"externalDomains": ["https://api.github.com"]
}
},
"services": [
{
"serviceName": "check-or-upload",
"directory": "lambdas/check-or-upload",
"include": ["lambdas/_shared"],
"internalEnvVars": ["CARRICK_API_ENDPOINT"]
},
{
"serviceName": "dashboard",
"directory": "app",
"tsconfig": "tsconfig.json"
}
]
}
| Campo | Descripción |
|---|---|
serviceName | Nombre del servicio, y la clave bajo la que se escribe el índice: contra lo que se comparan las llamadas de un repositorio hermano, y lo que carrick status y las filas alojadas nombran. name se acepta como alias dentro de una entrada de services |
directory | Raíz del servicio, relativa a carrick.json. Los archivos fuera de cada directorio declarado se ignoran |
include | Raíces de fuentes adicionales para incorporar en la resolución de tipos/funciones (por ejemplo, bibliotecas compartidas copiadas en tiempo de compilación), relativas a carrick.json |
tsconfig | Ruta de configuración TypeScript opcional, relativa a directory. Los servicios Deno normalmente omiten este campo y usan su manifiesto Deno más cercano |
graphqlSchemas | Archivos SDL de GraphQL impresos que definen las operaciones que este servicio sirve, relativos a carrick.json; se permiten globs. Ver Esquemas GraphQL code-first |
Junto a services, el mapa opcional de nivel superior includes declara la clasificación para una raíz de origen compartida una sola vez. Consulta Declarar una raíz compartida una sola vez. |
Cada servicio también acepta los campos de clasificación de llamadas (internalEnvVars, externalEnvVars, internalDomains, externalDomains). Cuando services está presente, cualquier campo plano hermano de nivel superior se ignora. La deriva entre servicios, los conflictos de dependencias y los intentos duplicados se detectan entre los servicios declarados igual que entre repositorios.
Declarar una raíz compartida una sola vez
Cuando varios servicios acceden a una API de terceros a través del mismo directorio compartido, las llamadas pertenecen a ese directorio, no a cada servicio que lo incorpora. El mapa opcional de nivel superior includes declara la clasificación por raíz de origen compartida:
{
"includes": {
"lambdas/_shared": {
"externalEnvVars": ["GITHUB_API_BASE"],
"externalDomains": ["https://api.github.com"]
}
}
}
Cada clave es una raíz de origen escrita tal como un servicio la nombra en su include (un ./ inicial y un / final se ignoran al hacer coincidir). Cada valor toma los mismos cuatro campos de clasificación que toma un servicio.
Cada servicio cuyo include lista esa raíz hereda esas declaraciones, unidas con las suyas propias. Un servicio conserva todo lo que declara por sí mismo, y un nombre declarado en ambos lugares aparece una sola vez. Un servicio que no incluye la raíz no hereda nada.
Una clave que ningún servicio lista en su include falla el escaneo en lugar de no hacer nada, de modo que una raíz mal escrita se informa en lugar de dejar las llamadas sin clasificar.
Esquemas GraphQL code-first
Carrick lee las operaciones de un servidor GraphQL desde SDL: archivos .graphql/.gql bajo el directorio propio del servicio y literales de plantilla gql. Un esquema construido en código (Pothos, TypeGraphQL, Nexus) no tiene SDL en el código fuente, por lo que sus consultas y mutaciones no se indexan hasta que el servicio nombra el esquema impreso:
{
"services": [
{
"serviceName": "api",
"directory": "apps/api",
"graphqlSchemas": ["apps/api/dist/schema.graphql"]
}
]
}
Cada entrada es una ruta relativa a carrick.json, o un glob como packages/schema/generated/*.graphql. El archivo puede estar en cualquier lugar del repositorio, incluida una carpeta de compilación como dist/ o el directorio de otra aplicación, siempre que esté confirmado. Cada campo Query, Mutation y Subscription que define se indexa como una operación que este servicio sirve. Un carrick.json plano de un solo servicio acepta el campo en el nivel superior.
Una entrada que no coincide con ningún archivo, o un archivo que no define ningún campo raíz, se informa como advertencia en la salida del escaneo. Cuando un servicio depende de una biblioteca GraphQL y sirve rutas HTTP pero no indexa ningún campo de esquema GraphQL, la salida del escaneo sugiere esta configuración.
Una vez que se conocen los campos del esquema, Carrick también lee los módulos que construyen el esquema: archivos que llaman a través de un valor de constructor creado a partir de una biblioteca que el escaneo detecta, incluidos módulos de campo que no exportan nada y solo importan el constructor. Cada uno de esos archivos se analiza con la lista de campos del esquema, y un campo cuyo resolvedor se encuentra allí se indexa en la línea del resolvedor en lugar de en el esquema impreso. Un campo sin resolvedor localizado permanece en su línea de esquema.
Documentos GraphQL para la API de otro equipo
Los documentos GraphQL de un cliente se indexan como llamadas solo cuando están escritos contra un esquema que este repositorio sirve. Carrick atribuye cada documento (un archivo .graphql/.gql, o una plantilla gql) al archivo de esquema confirmado que contiene sus campos raíz. Un archivo de esquema cuenta como servido cuando un servicio lo nombra en graphqlSchemas, o cuando está bajo el directorio de un servicio y ese servicio muestra que sirve un esquema: sirve rutas HTTP, o un resolvedor en su código está vinculado a uno de los campos del esquema. Cualquier otro archivo de esquema confirmado marca sus documentos como llamadas a una API externa, y no se indexan como llamadas. Eso incluye un esquema de proveedor que un paso de codegen descargó en dist/, o una copia confirmada dentro del propio src/ de una aplicación cliente. Los campos de una copia que está bajo un servicio sin tal evidencia no se indexan como operaciones de ese servicio, y la salida del escaneo nombra el archivo. La salida del escaneo nombra el archivo de esquema y cuenta las operaciones, de modo que un esquema que se sirve aquí pero no se declara puede agregarse a graphqlSchemas.
Algunos documentos también se omiten:
- un documento cuyos campos ningún esquema único contiene;
- un documento cuyos campos están tanto en un esquema servido como en uno externo, cuando las lecturas de entorno del archivo no lo resuelven. Un archivo que solo lee variables de
internalEnvVarscuenta como interno, y uno que solo leeexternalEnvVarscuenta como externo.
Un documento cuyos campos no aparecen en ningún esquema que el repositorio contenga sigue siendo una llamada, porque su servidor puede ser otro repositorio en el proyecto.
Dónde se indexa una llamada GraphQL
Un documento escrito en un archivo .graphql/.gql y compilado en una declaración tipada (OrdersDocument) se envía desde el código que pasa esa declaración a un cliente, como useQuery(OrdersDocument). Carrick indexa los campos de la operación en cada una de esas llamadas, resolviendo la importación a través de rutas relativas, alias de ruta tsconfig y paquetes de espacio de trabajo. Una operación que ninguna llamada ejecuta permanece indexada en su línea en el archivo de documento. Una plantilla gql escrita en el código fuente permanece indexada donde está escrita.
Cómo funciona
- SWC analiza cada archivo TypeScript en un AST.
- Un pase de análisis estático extrae exportaciones de funciones, enrutadores montados, llamadas HTTP con coincidencia de patrones, esquemas y operaciones GraphQL, y contratos de eventos WebSocket.
- Un agente LLM maneja los casos que la coincidencia de patrones no puede alcanzar: URLs dinámicas, funciones de fábrica, enrutamiento específico del framework.
- Un sidecar TypeScript resuelve los tipos de solicitud y respuesta contra el compilador TypeScript real.
- Un segundo pase LLM escribe la descripción de intención por función.
- El índice de la organización vive en DynamoDB y S3 y se actualiza cada vez que la rama principal de un servicio se ejecuta.
Licencia
Licencia Elastic 2.0. Copyright (c) 2026 Far Harbour B.V.
Desarrollo
Consulta AGENTS.md para conocer las convenciones de compilación, prueba y contribución.
cargo test
cargo fmt
cargo clippy
Instala el hook de pre-commit opcional para ejecutar el formateo y las pruebas antes de cada confirmación:
./scripts/install-hooks.sh