mcp-x

Proporciona al modelo la API v2 de X (Twitter) como 42 herramientas en publicaciones, búsqueda, usuarios, listas y carga de medios. Go, contexto de usuario OAuth 1.0a, stdio y HTTP transmisible. Construido en torno a la facturación de pago por uso de X: las lecturas se cobran por recurso devuelto, por lo que las herramientas solicitan la página más pequeña que responda a la pregunta.

Documentación

mcp-x

Un servidor MCP que le da a un LLM la API de X (Twitter): leer y buscar publicaciones, gestionar usuarios y listas, subir medios y publicar — como la cuenta cuyas claves utiliza.

License MIT Go MCP Transport X API v2 OAuth 1.0a Paid API

Sitio web · Costos · Credenciales · Herramientas · Inicio rápido · Configuración · Arquitectura · Contribuciones


Qué es

mcp-x es un servidor de Model Context Protocol escrito en Go. Expone la API de X v2 a cualquier cliente compatible con MCP (Claude Desktop, agentes de IDE, aplicaciones LLM personalizadas) como 42 herramientas que cubren publicaciones, usuarios, listas y medios.

Se autentica con contexto de usuario OAuth 1.0a, lo que significa que cada llamada actúa como una cuenta real de X — la que posee las cuatro claves. x_post_create publica públicamente. x_user_follow realmente sigue. x_post_delete es irreversible. Esto no es un entorno de pruebas y no es gratuito: consulta La API de X cuesta dinero antes de conectarla a un agente.

Ambos transportes que soporta el SDK de MCP están disponibles y exponen el mismo conjunto de herramientas:

  • stdio — el cliente lanza el binario y se comunica por stdin/stdout (el predeterminado, ideal para clientes de escritorio).
  • http — un servidor HTTP de transmisión de larga duración (útil para despliegues remotos/compartidos).

La API de X cuesta dinero

[!WARNING] Ya no existe un nivel gratuito. X retiró los niveles de suscripción Free/Basic/Pro para nuevos desarrolladores y pasó a créditos de pago por uso: compras créditos por adelantado en la Developer Console y cada solicitud descuenta del saldo en tiempo real. Las suscripciones heredadas Basic ($200/mes) y Pro ($5,000/mes) sobreviven solo para cuentas que ya las tenían; Enterprise comienza alrededor de $42,000/mes. Una cuenta de desarrollador nueva hoy obtiene pago por uso y nada más.

Tarifas al momento de escribir esto (precios oficiales — siempre vuelve a consultar la Console, han cambiado varias veces en 2026):

OperaciónPrecio
Lectura de publicación$0.005 por publicación devuelta
Lectura propia (tus publicaciones, marcadores, seguidores, me gusta, listas)$0.001 por recurso
Lectura de usuario$0.010 por usuario devuelto
Lectura de me gusta / silenciados / bloqueados$0.001 por recurso
Lectura de seguidores / siguiendo$0.010 por recurso
Publicar una publicación$0.015 por solicitud
Publicar una publicación que contiene una URL$0.200 por solicitud
Me gusta / republicar y otras interacciones$0.015 por solicitud
Escrituras de listas y marcadores$0.005–$0.010 por solicitud

De esto se derivan dos cosas, y ambas están integradas en el servidor:

  • Las lecturas se cobran por recurso devuelto, no por solicitud. max_results: 100 en x_posts_search cuesta veinte veces lo que max_results: 5 cuesta por la misma consulta. La descripción de cada herramienta de lectura le indica al modelo que pida el max_results más pequeño que responda la pregunta, y las herramientas de agrupación (x_posts_lookup, x_users_lookup) le indican que agrupe en lugar de hacer bucles.
  • x_posts_count no consume el presupuesto de lectura de publicaciones. Devuelve recuentos de coincidencias agrupados por minuto/hora/día para la misma sintaxis de consulta. Dimensiona un tema con x_posts_count primero, luego paga por x_posts_search.

X también deduplica: el mismo recurso obtenido dos veces dentro de una ventana UTC de 24 horas se factura una sola vez. Y el pago por uso está limitado a 3 millones de lecturas de publicaciones por ciclo de facturación — más allá de eso, solo Enterprise.

Cuando el dinero se agota, la API responde con un error distinto, y el servidor lo mapea a un mensaje que le dice explícitamente al modelo que no reintente — consulta Errores.


Credenciales

El servidor necesita cuatro valores de OAuth 1.0a, todos de una sola app de X:

X_API_KEY
X_API_KEY_SECRET
X_ACCESS_TOKEN
X_ACCESS_TOKEN_SECRET

El primer par identifica la app; el segundo par identifica la cuenta que actúa a través de ella. mcp-x usa AuthenMethodOAuth1UserContext, no autenticación de portador solo para apps, porque cada endpoint de escritura y cada endpoint "yo" (x_users_me, x_posts_home, x_bookmarks_list, menciones) requiere un contexto de usuario. No existe un modo de token de portador.

Cómo obtenerlas — y el paso que todos hacen mal

  1. Ve a developer.x.com → tu proyecto → tu app.
  2. Abre User authentication settings y establece App permissions en Read and Write. Haz esto primero.
  3. Solo entonces ve a Keys and tokens y genera el Access Token and Secret.

[!IMPORTANT] Un token de acceso conserva permanentemente los permisos que la app tenía en el momento en que se generó. Si creaste el token mientras la app era de solo lectura y luego cambiaste la app a Read and Write, el token sigue siendo de solo lectura. Nada en la página de configuración de la app te lo dirá. Cada escritura fallará con el tipo de problema oauth1-permissions de X, para siempre, hasta que vuelvas a Keys and tokens y regeneres el Access Token and Secret.

Este es el fallo de configuración más común con la API de X, por eso el servidor lo verifica al inicio y se niega a arrancar con:

the access token is read-only: set the app permissions to Read and Write
in the X Developer Console, then regenerate the Access Token and Secret

Regenerar la API Key/Secret no es la solución. Regenera el Access Token and Secret.

Verificación al inicio

Antes de registrar una sola herramienta, el servidor llama a GET /2/users/me una vez (client.Bootstrap) con un plazo de 15 segundos. Esto hace tres trabajos:

  • demuestra que las cuatro claves son válidas — las claves malas fallan el proceso, no la primera llamada de herramienta;
  • detecta la trampa del token de solo lectura mencionada arriba;
  • almacena en caché el id numérico de usuario del propietario de las claves, porque cada endpoint de escritura es POST /2/users/:id/... y buscar el id por cada escritura sería otra solicitud facturada.

Un fallo aquí es fatal por diseño. Un servidor que arranca y luego falla en cada llamada es peor que uno que no arranca.

El costo de esa elección vale la pena decirlo claramente: no hay forma de probar este servidor sin una cuenta de desarrollador de X financiada. Sin credenciales no hay arranque, lo que significa que no hay lista de herramientas — /mcp y claude mcp list mostrarán una conexión fallida y nada más. Para confirmar una instalación sin llegar a eso, ejecuta el binario con -version y lee la sección Herramientas para ver qué habría expuesto.

Las claves son secretos

Los cuatro valores son credenciales para una cuenta activa con acceso de escritura. Mantenlos en un archivo que solo tú puedas leer, pásalo con -env y nunca lo subas a un repositorio — .env está en gitignore, .env.example es la plantilla. Cuando se ejecuta bajo un cliente MCP, el bloque env del cliente también funciona; tiene prioridad sobre el archivo .env.


Herramientas

42 herramientas en cuatro grupos. Cada herramienta lleva anotaciones MCP: readOnlyHint en lecturas, destructiveHint en cualquier cosa irreversible (x_post_delete, x_list_delete, quitar me gusta, desrepublicar, dejar de seguir, eliminación de miembros). Cada una devuelve una carga JSON estructurada que coincide con su esquema de salida; el SDK refleja el mismo JSON en el bloque de contenido de texto para clientes que no leen structuredContent.

Cada herramienta acepta un timeout_ms opcional, limitado a la ventana [MIN, MAX] del grupo (consulta Límites).

Publicaciones — lectura

HerramientaDescripción
x_posts_searchBusca publicaciones recientes para varias consultas en paralelo. La búsqueda reciente alcanza solo 7 días atrás.
x_posts_countCuenta coincidencias por consulta agrupadas por minuto/hora/día. No gasta lecturas de publicaciones — úsala para dimensionar un tema antes de buscar.
x_posts_lookupObtiene hasta 100 publicaciones por id en una sola llamada.
x_posts_by_userPublicaciones recientes para varios nombres de usuario, obtenidas en paralelo.
x_posts_mentionsPublicaciones que mencionan al propietario de las claves.
x_posts_homeLa línea de tiempo principal del propietario de las claves.
x_posts_quotesPublicaciones que citan una publicación dada.
x_posts_likedPublicaciones a las que el propietario de las claves dio me gusta.
x_bookmarks_listLos marcadores del propietario de las claves.
x_post_liked_byUsuarios que dieron me gusta a una publicación dada.
x_post_reposted_byUsuarios que republicaron una publicación dada.

x_posts_search

ParámetroTipoPredeterminadoNotas
queries[]stringRequerido. Se ejecuta en paralelo, limitado a POSTS_MAX_QUERIES (5). ≤ 512 caracteres cada uno.
max_resultsint10Publicaciones por consulta, 1..100. Cada una se factura.
sort_orderstringrecency (más recientes primero) o relevancy (mejor coincidencia).
daysint7Cuánto hacia atrás, 1..7. La API no puede ir más lejos.
include_retweetsboolfalseCuando es false, el servidor agrega -is:retweet a cada consulta.
timeout_msint6415000Tiempo de espera de toda la llamada, limitado a [2000, 60000].

Los operadores de consulta van dentro de la cadena de consulta:

OperadorSignificado
espacioY
ORO explícito
-termNO
( )agrupación
from:user / to:userpor autor / por destinatario
@user / #tagmenciones / hashtags
"exact phrase"frase exacta
lang:enidioma
is:retweet is:reply is:quote is:verifiedtipo de publicación
has:media has:images has:videos has:linksadjuntos
url:example.comdominio enlazado
conversation_id:123un hilo

x_posts_count

ParámetroTipoPredeterminadoNotas
queries[]stringRequerido. Mismos operadores que x_posts_search.
granularitystringhourminute, hour o day.
daysint71..7, misma ventana.
timeout_msint6415000Limitado a [2000, 60000].

x_posts_lookup

ParámetroTipoPredeterminadoNotas
ids[]stringRequerido. 1..100 ids de publicaciones. Agrúpalos; no llames una vez por id.

x_posts_by_user

ParámetroTipoPredeterminadoNotas
usernames[]stringRequerido. Sin el @. Limitado a POSTS_MAX_USERNAMES (5), obtenidos en paralelo.
max_resultsint10Publicaciones por usuario, 1..100.
exclude[]stringreplies y/o retweets.
daysint71..7.
timeout_msint6415000Limitado a [2000, 60000].

Herramientas de línea de tiempo

x_posts_mentions, x_posts_home, x_posts_liked y x_bookmarks_list comparten una forma: max_results (1..100), pagination_token, timeout_ms. x_posts_quotes, x_post_liked_by y x_post_reposted_by agregan un id requerido.

La paginación es explícita y manual: una respuesta lleva next_token, y lo pasas de vuelta como pagination_token para la siguiente página. El servidor nunca recorre páginas por su cuenta — cada página se factura, así que esa decisión queda en manos de quien llama.

Publicaciones — escritura

HerramientaAnotaciónDescripción
x_post_createescrituraPublica un post. Público y facturado ($0.015, o $0.20 con un enlace).
x_post_deletedestructivoElimina uno de tus posts. Irreversible.
x_post_like / x_post_unlikeescritura / destructivoEl "me gusta" es público.
x_post_repost / x_post_unrepostescritura / destructivoEl repost es público.

x_post_create

ParámetroTipoNotas
textstring≤ 280 caracteres. Requerido a menos que media_ids esté definido.
reply_to_idstringResponder a este post.
quote_idstringCitar este post.
media_ids[]string1..4 ids de x_media_upload.
pollobjectoptions (2..4) + duration_minutes (5..10080). No se puede combinar con media_ids.
reply_settingsstringfollowing, mentionedUsers, subscribers o verified.

Publicar el mismo texto dos veces seguidas es rechazado por X como duplicado — el servidor lo presenta como un mensaje distinto en lugar de un fallo genérico.

Usuarios

HerramientaAnotaciónDescripción
x_users_lookuplecturaHasta 100 nombres de usuario o 100 ids en una sola llamada — uno u otro, no ambos. Los nombres no resueltos aparecen en not_found.
x_users_melecturaEl perfil detrás de las claves.
x_user_followers / x_user_followinglecturaUna página a la vez; max_results hasta 1000.
x_user_follow / x_user_unfollowescritura / destructivoPúblico.
x_user_mute / x_user_unmuteescrituraUn silencio es silencioso: la otra cuenta no es notificada y aún puede verte.
x_users_muted / x_users_blockedlecturaLas cuentas silenciadas / bloqueadas del propietario de las claves.

Bloquear y desbloquear están deliberadamente no expuestos. Leer la lista de bloqueos sí lo está.

Listas

HerramientaAnotaciónDescripción
x_list_createescrituraNombre ≤ 25 caracteres, descripción ≤ 100. Las listas privadas son solo del propietario.
x_list_updateescrituraEnvía solo los campos que quieras cambiar.
x_list_deletedestructivoIrreversible: la lista, la membresía y los seguidores desaparecen.
x_list_getlecturaUna lista con conteos de miembros y seguidores.
x_lists_ownedlecturaListas propiedad de un nombre de usuario, o del propietario de las claves si se omite.
x_list_postslecturaPosts recientes de los miembros de la lista. Facturado por post.
x_list_memberslecturaCuentas en la lista, paginadas.
x_list_member_add / x_list_member_removeescritura / destructivoPor nombre de usuario.
x_list_follow / x_list_unfollowescritura / destructivoSolo listas públicas.
x_list_pin / x_list_unpin / x_lists_pinnedescritura / escritura / lecturaListas fijadas del propietario de las claves.

Medios

x_media_upload

Sube una imagen, GIF o video con el flujo fragmentado INIT → APPEND → FINALIZE y devuelve un media_id para x_post_create.

ParámetroTipoNotas
pathstringArchivo local. Debe estar dentro de MEDIA_ROOT.
base64stringContenido del archivo en lugar de una ruta; requiere mime.
mimestringp. ej. image/jpeg, video/mp4. Requerido con base64.
categorystringRequerido. tweet_image (≤ 5 MB), tweet_gif (≤ 15 MB), tweet_video (≤ 512 MB).
timeout_msint64Por defecto 120000, limitado a [5000, 600000] — cubre la subida y la transcodificación de X.

Dos cosas a saber:

  • MEDIA_ROOT es un límite estricto. Es configuración requerida sin valor por defecto, y el servidor rechaza cualquier ruta que se resuelva fuera de él, incluidos los enlaces simbólicos. Sin él, un LLM con esta herramienta podría leer cualquier archivo en el host y publicarlo. Elige un directorio dedicado.
  • El video se transcodifica de forma asíncrona por X. El servidor consulta el estado de FINALIZE cada MEDIA_POLL_INTERVAL_MS, pero una codificación lenta aún puede devolver status: "pending". El media_id es válido pero aún no publicable — reintenta x_post_create en breve. Un media_id expira después de un tiempo y está destinado a un solo post.

Resultados parciales

Las herramientas de difusión — x_posts_search, x_posts_count, x_posts_by_user — ejecutan sus entradas en paralelo y devuelven una entrada por consulta/nombre de usuario, cada una con su propio status, de modo que una entrada mala no pierde los resultados que funcionaron. x_posts_lookup hace lo mismo por id (not found se sitúa junto a los posts que se resolvieron), y x_users_lookup recoge los nombres no resueltos en not_found.

Una llamada falla por completo solo cuando la entrada es rechazada antes de que comience cualquier trabajo, o cuando cada elemento en ella falla.

Errores

Los fallos vuelven como resultado de herramienta con isError: true y un mensaje de texto plano, no como un error JSON-RPC — el modelo lee el mensaje y puede corregir la llamada por sí mismo. Los mensajes están escritos para un modelo en lugar de un lector de registros, por lo que los que no deben reintentarse lo dicen explícitamente.

MensajeSignificado
the access token is read-only: …regenerate the Access Token and SecretLa trampa del token de solo lectura.
the monthly usage cap for this project is reached; …do not retryLímite de uso alcanzado. Nada funciona hasta que se restablece.
the project has no X API credits left; …do not retryCréditos agotados. No se restablecen — recarga en la Consola.
rate limit exceeded; the window resets in about 15 minutesRetrocede; no insistas.
X rejected the credentials; check the four keysClaves inválidas o revocadas.
not authorized for this resourceProtegido, eliminado o de otra persona.
X rejected this as a duplicate of a recent postCambia el texto.
user not found / post not found / list not foundAutoevidente.
X failed to process the uploaded media; re-encode the fileLa transcodificación falló.
media exceeds the size limit for its category5 MB / 15 MB / 512 MB.
the path is outside the directory this server is allowed to readViolación de MEDIA_ROOT.
post text exceeds 280 characters
a post needs text unless it carries media
a poll needs 2 to 4 options and a duration between 5 and 10080 minutes
a post carries at most 4 media items
too many ids in one call; the limit is 100
invalid username: 1 to 15 letters, digits or underscoresValidado localmente, antes de gastar una solicitud.
every query failed; the X API may be unreachableTodos los elementos fallaron.
the call timed out; retry with a larger timeout_ms or fewer inputs
the X API is unavailable5xx o fallo ascendente no clasificado.

La clasificación ocurre en adapter/x/apierr, y es deliberadamente defensiva. X responde a los errores con Content-Type: application/problem+json, que el cliente gotwi subyacente no reconoce como JSON — por lo que todo el cuerpo del problema aterriza textualmente en un campo de mensaje en lugar de decodificarse en campos tipados. El mapeador por lo tanto concatena cada fuente que la respuesta puede llevar y compara los tipos de problema (oauth1-permissions, usage-capped, credits-depleted, rate-limit-exceeded, …) contra ese montón, recurriendo a los códigos de estado HTTP cuando nada coincide.

Limitaciones conocidas

  • Solo búsqueda reciente. 7 días hacia atrás, punto. La búsqueda histórica/de archivo es un producto diferente (Enterprise) y no está conectada.
  • El bloqueo no está expuesto. x_users_blocked lee la lista; no hay herramienta de bloquear/desbloquear.
  • Sin endpoints de streaming. El flujo filtrado/muestreado no está implementado.
  • Sin mensajes directos.
  • Una cuenta por proceso. Las claves son a nivel de proceso; el propietario de las claves se resuelve una vez al inicio. Atender varias cuentas significa varios procesos.
  • La paginación es manual. Las herramientas devuelven next_token; nada recorre páginas automáticamente, porque cada página es facturada.

Inicio rápido

Instalación

Elige el que mejor se adapte — todos dan el servidor idéntico.

Contenedor (no se necesita cadena de herramientas Go):

docker pull ghcr.io/role1776/mcp-x:0.1.1     # or :latest to track the newest release

Fija una versión explícita en cualquier cosa de la que dependas. :latest se mueve en cada versión estable, y una versión puede añadir o cambiar el comportamiento de las herramientas; server.json fija la misma versión que anuncia el Registro MCP.

Binario precompilado — toma el archivo para tu plataforma de la última versión, descomprímelo y pon mcp-x en tu PATH.

Paquete MCP — para clientes que instalan archivos .mcpb, descarga mcp-x_<version>_<os>_<arch>.mcpb de la última versión y ábrelo con tu cliente. El paquete lleva el binario compilado, por lo que no necesita ni Docker ni Go, y el cliente solicita los cinco valores requeridos en la instalación. Elige el archivo que coincida con tu sistema operativo y arquitectura de CPU: un paquete contiene un solo binario nativo.

[!NOTA] Los paquetes construidos antes de v0.1.1 no declaraban campos de configuración, por lo que el cliente nunca pedía las credenciales y el servidor salía al inicio cada vez. Usa v0.1.1 o más reciente.

Desde el código fuente (necesita Go 1.26+):

git clone https://github.com/Role1776/mcp-x
cd mcp-x
make build          # -> bin/mcp-x

go install también funciona, con una advertencia que vale la pena saber antes de escribirlo:

go install github.com/Role1776/mcp-x/app/cmd/mcp-x@latest

El módulo Go vive en app/, por lo que las etiquetas de versión (v0.1.0) no lo nombran — @v0.1.0 falla por completo y @latest se resuelve a una pseudo-versión del commit más reciente en main. Una compilación go install también reporta su versión como dev, porque la versión es estampada por el pipeline de lanzamiento y no por el módulo. Para una compilación que coincida exactamente con una versión, usa el binario precompilado, el contenedor, o make build desde una etiqueta verificada.

Configuración

Desde un clon, o desde un archivo de versión descomprimido (ambos incluyen la plantilla):

cp .env.example .env
$EDITOR .env        # fill in the four X_* keys and MEDIA_ROOT

Instalar desde el contenedor o un paquete .mcpb no te da un checkout del que copiar — toma la plantilla de .env.example, o omite el archivo por completo y pasa los cinco valores en el bloque env de tu cliente (abajo).

Se requieren cinco valores; todo lo demás tiene un valor por defecto. MEDIA_ROOT debe ser una ruta absoluta a un directorio que ya exista — el servidor lo verifica al inicio y se niega a arrancar de lo contrario, en lugar de fallar en la primera subida.

Ejecución

./bin/mcp-x -env /absolute/path/to/.env
BanderaSignificado
-versionImprime la versión que el binario reporta a los clientes MCP y sale. Respondible sin credenciales, a diferencia del apretón de manos.
-envRuta a un archivo .env. No hay búsqueda implícita — bajo stdio el directorio de trabajo es elegido por el cliente MCP, por lo que un valor relativo por defecto sería impredecible. Si la bandera se omite, o el archivo no existe, el servidor recurre al entorno ambiente y lo dice en stderr; los cinco valores requeridos deben estar definidos en algún lugar o el inicio falla.

Un inicio exitoso registra la cuenta autenticada:

INFO authenticated with the X API op=app.Run user_id=1234567890
INFO MCP server has started op=app.Run transport=stdio

Conectar un cliente MCP (stdio)

Claude Code — un comando, credenciales en línea (-- separa el comando propio del servidor de las banderas anteriores):

claude mcp add mcp-x \
  -e X_API_KEY=... \
  -e X_API_KEY_SECRET=... \
  -e X_ACCESS_TOKEN=... \
  -e X_ACCESS_TOKEN_SECRET=... \
  -e MEDIA_ROOT=/absolute/path/to/media \
  -- /absolute/path/to/mcp-x

O apúntalo a un archivo .env en su lugar: -- /absolute/path/to/mcp-x -env /absolute/path/to/.env. Verifica el resultado con claude mcp list, o /mcp dentro de una sesión.

Claude Desktop y otros clientes que aceptan una configuración JSON — apúntalos al binario compilado:

{
  "mcpServers": {
    "x": {
      "command": "/absolute/path/to/mcp-x",
      "args": ["-env", "/absolute/path/to/.env"]
    }
  }
}

O omite el archivo y pasa las credenciales en el bloque env — las variables ya en el entorno ganan sobre el archivo .env, por lo que el bloque env de un cliente siempre tiene efecto:

{
  "mcpServers": {
    "x": {
      "command": "/absolute/path/to/mcp-x",
      "env": {
        "X_API_KEY": "...",
        "X_API_KEY_SECRET": "...",
        "X_ACCESS_TOKEN": "...",
        "X_ACCESS_TOKEN_SECRET": "...",
        "MEDIA_ROOT": "/absolute/path/to/media"
      }
    }
  }
}

Conectar un cliente MCP (contenedor)

Ejecuta la imagen en stdio. Docker necesita cada variable nombrada en la línea de comandos con -e para que llegue al proceso, y MEDIA_ROOT solo tiene sentido si el directorio está montado:

{
  "mcpServers": {
    "x": {
      "command": "docker",
      "args": [
        "run", "-i", "--rm",
        "-e", "X_API_KEY",
        "-e", "X_API_KEY_SECRET",
        "-e", "X_ACCESS_TOKEN",
        "-e", "X_ACCESS_TOKEN_SECRET",
        "-e", "MEDIA_ROOT",
        "-v", "/host/media:/media:ro",
        "ghcr.io/role1776/mcp-x:0.1.1"
      ],
      "env": {
        "X_API_KEY": "...",
        "X_API_KEY_SECRET": "...",
        "X_ACCESS_TOKEN": "...",
        "X_ACCESS_TOKEN_SECRET": "...",
        "MEDIA_ROOT": "/media"
      }
    }
  }
}

-i es obligatorio — sin él, el contenedor no recibe stdin y el cliente ve morir el servidor de inmediato. Los clientes que instalan desde el propio Registro MCP construyen esta invocación por sí mismos y solicitan las variables declaradas en server.json — eso es una propiedad del cliente, no de todos los clientes que pueden hablar con este servidor; la CLI de Claude Code, por ejemplo, no instala desde el registro, así que usa claude mcp add arriba.

Cuando el cliente dice que la conexión se cerró

Bajo stdio, el stderr del servidor pertenece al cliente, y la mayoría de los clientes lo descartan. Así que un problema de configuración que el servidor explica perfectamente en una línea llega a ti como nada más que:

✘ Failed to connect — -32000: Connection closed

Ejecuta el binario manualmente con el mismo entorno para ver la razón real:

/absolute/path/to/mcp-x -env /absolute/path/to/.env

Imprime exactamente qué está mal — qué variables faltan y dónde obtenerlas, o que las claves fueron rechazadas, o que el token de acceso es de solo lectura — y sale. Casi todas las instalaciones fallidas son una de esas tres.

Ejecución sobre HTTP

Establece MCP_TRANSPORT=http y el servidor escucha en SERVER_PORT en MCP_PATH (por defecto http://localhost:8080/mcp).

[!CAUTION] El transporte HTTP no tiene autenticación propia. Cualquiera que pueda alcanzar el endpoint puede publicar, eliminar y seguir como tu cuenta, con tus créditos. Enlázalo a localhost o colócalo detrás de un proxy inverso autenticado — nunca lo expongas a internet abierta.


Configuración

Todo se configura mediante variables de entorno, y cada valor se valida antes del inicio: una clave requerida faltante, o un número no numérico o no positivo, es un error de inicio que nombra la variable que realmente estableciste (X_API_KEY, no APIKey). Las relaciones entre límites no se verifican al inicio — ver Límites. Las variables ya presentes en el entorno ganan sobre un archivo .env.

Consulta .env.example para la lista completa con sus valores predeterminados, lista para copiar a .env.

Requeridas

EnvNotas
X_API_KEYClave de consumidor OAuth 1.0a.
X_API_KEY_SECRETSecreto de consumidor OAuth 1.0a.
X_ACCESS_TOKENToken de acceso de usuario — genéralo después de establecer Lectura y Escritura.
X_ACCESS_TOKEN_SECRETSecreto del token de acceso de usuario.
MEDIA_ROOTRuta absoluta al único directorio del que x_media_upload puede leer. Debe existir y ser un directorio; ambos se verifican al inicio. Sin valor predeterminado, a propósito.

Si falta alguna de ellas, el inicio se aborta con la lista de lo que falta más un puntero a la Consola de Desarrollador.

Servidor MCP

EnvPredeterminadoNotas
MCP_TRANSPORTstdiostdio o http.
MCP_NAMEmcp-xNombre del servidor anunciado a los clientes.
MCP_PATH/mcpRuta HTTP (solo transporte http).

La versión anunciada a los clientes no es configurable: se estampa en el binario en tiempo de compilación desde la etiqueta git.

Servidor HTTP (solo transporte http)

EnvPredeterminado
SERVER_PORT8080
SERVER_READ_TIMEOUT60s
SERVER_WRITE_TIMEOUT60s

Cliente HTTP

EnvPredeterminadoNotas
MAX_IDLE_CONNS_PER_HOST100Agrupación de conexiones hacia la API de X.
X_DEBUGfalseRegistra solicitudes gotwi sin procesar. Verboso; útil cuando un error de X no tiene sentido.

Registro

EnvPredeterminadoNotas
LOG_MODElocallocal → manejador de texto en nivel de depuración; prod → manejador JSON en nivel de información. Los registros van a stderr (deben: stdout lleva el protocolo MCP).

Límites

Cada grupo tiene sus propios límites, por lo que una carga de medios lenta no puede ser limitada por un tiempo de espera de búsqueda.

Publicaciones

EnvPredeterminadoNotas
POSTS_MAX_QUERIES5Consultas por llamada x_posts_search / x_posts_count.
POSTS_MAX_USERNAMES5Nombres de usuario por llamada x_posts_by_user.
POSTS_MAX_IDS100Ids por x_posts_lookup; el techo propio de la API.
POSTS_DEFAULT_RESULTS10
POSTS_MAX_RESULTS100
POSTS_DEFAULT_DAYS7
POSTS_MAX_DAYS7La búsqueda reciente no puede mirar más atrás.
POSTS_DEFAULT_TIMEOUT_MS15000
POSTS_MIN_TIMEOUT_MS2000
POSTS_MAX_TIMEOUT_MS60000

Usuarios

EnvPredeterminado
USERS_MAX_USERNAMES100
USERS_MAX_IDS100
USERS_DEFAULT_RESULTS100
USERS_MAX_RESULTS1000
USERS_DEFAULT_TIMEOUT_MS15000
USERS_MIN_TIMEOUT_MS2000
USERS_MAX_TIMEOUT_MS60000

Listas

EnvPredeterminado
LISTS_DEFAULT_RESULTS25
LISTS_MAX_RESULTS100
LISTS_DEFAULT_TIMEOUT_MS15000
LISTS_MIN_TIMEOUT_MS2000
LISTS_MAX_TIMEOUT_MS60000

Medios

EnvPredeterminadoNotas
MEDIA_CHUNK_BYTES41943044 MB. El endpoint APPEND limita un segmento a 5 MB.
MEDIA_POLL_INTERVAL_MS2000Con qué frecuencia se consulta el estado de FINALIZE mientras X transcodifica.
MEDIA_DEFAULT_TIMEOUT_MS120000
MEDIA_MIN_TIMEOUT_MS5000
MEDIA_MAX_TIMEOUT_MS60000010 minutos, para video grande.

Cada valor se verifica por sí solo — debe ser mayor que cero, y los que la propia API limita (*_MAX_IDS, POSTS_MAX_RESULTS, LISTS_MAX_RESULTS, POSTS_MAX_DAYS) se limitan adicionalmente al techo de la API para que un error tipográfico no pueda producir una solicitud que X rechace. Los tríos DEFAULT_*, MIN_* y MAX_* no se verifican entre sí al inicio. Un conjunto inconsistente no detiene el servidor; se reconcilia por solicitud en su lugar:

  • un valor que el llamador omite, o pasa como cero o negativo, cae al DEFAULT_* correspondiente;
  • el resultado se ajusta luego dentro de [MIN_*, MAX_*], por lo que un DEFAULT_* mayor que su MAX_* simplemente produce MAX_*;
  • si MIN_* excede MAX_*, gana el máximo.

El límite efectivo está por lo tanto siempre dentro del máximo configurado, y una mala configuración degrada a un servidor funcional en lugar de un inicio fallido. La compensación es que degrada silenciosamente: POSTS_MAX_RESULTS=1 en lugar de 10 no produce ninguna advertencia, solo respuestas más pequeñas sin aviso. Vale la pena verificar dos veces estos valores cuando los resultados parecen truncados.


Arquitectura

El proyecto sigue una estructura limpia y en capas. Las dependencias apuntan hacia adentro, hacia el dominio, y cada capa habla con la siguiente a través de interfaces.

app/                       the Go module: sources plus its build files
                           (Dockerfile, .dockerignore, .goreleaser.yaml)

cmd/mcp-x/main.go          entry point: parse flags, load config, run app

internal/
  app/                     wiring + lifecycle (X client, bootstrap, run, graceful shutdown)
  config/                  config loading (.env → env vars → validate → env-name error messages)
  domain/x/                core types and errors
    posts/ users/ lists/ media/   value objects: PostID, UserID, Username, Draft, Query, Upload…
    format.go              shared snowflake-id and username validation
    errors.go              the sentinel error set the whole app maps onto
  dto/x/                   request/response shapes for the MCP tools, with jsonschema + validate tags
  transport/mcp/           MCP layer
    router/                registers every tool group on the MCP server
    x/posts|users|lists|media/   tool definitions, handlers and error → tool-result mapping
  usecase/x/               business logic: validation, parallelism, timeouts, limit resolution
  adapter/x/               X API wiring
    client/                gotwi client + startup Bootstrap (key check, key-owner id)
    apierr/                X problem responses → domain errors
    posts|users|lists|media/     endpoint calls and mappers to DTOs
  pkg/                     reusable building blocks (mcpserver, server, logger, validator, parallel)

Flujo de solicitud para una llamada de herramienta:

MCP client → transport/mcp/x/... (handler) → usecase/x/... → adapter/x/... → gotwi → X API
                    ↑ maps errors                 ↑ validates, resolves limits,
                      to isError text               fans out, applies timeouts

Dos límites merecen ser señalados:

  • La capa de dominio se niega a construir valores inválidos. NewPostID, NewUserID, NewUsername, NewDraft y amigos validan al construir, por lo que un id malformado o una publicación de 300 caracteres se rechaza localmente — antes de que cueste una solicitud. Los ids se verifican contra el formato de copo de nieve y los nombres de usuario contra la regla de 1–15 [A-Za-z0-9_] de X en un único paquete compartido format.
  • Cada capa habla el mismo vocabulario de errores. Los adaptadores convierten las respuestas de problema de X en los centinelas en domain/x/errors.go; la capa de transporte es el único lugar que convierte un centinela en texto humano. Por eso la misma falla se lee de manera idéntica sin importar cuál de las 42 herramientas la produjo.

Desarrollo

Todo lo de Go vive en app/, así que usa el makefile desde la raíz del repositorio o pasa -C app a la cadena de herramientas:

make build          # compile the binary -> bin/mcp-x
make test           # go test -v ./...
make cover          # total coverage percentage
make cover-html     # coverage report in the browser
make version        # the version that would be stamped into the binary

go -C app build ./...
go -C app test ./...
go -C app vet ./...

Los mocks se generan con mockgen a partir de las directivas //go:generate junto a cada interfaz usecase:

go -C app generate ./...

Se espera que el código nuevo venga con pruebas. Consulta CONTRIBUTING.md para las pautas completas de solicitudes de extracción.

Licencia

Publicado bajo la Licencia MIT.