qURL
qURL es el portal hacia el internet invisible: URLs con caducidad y alcance limitado que permiten a los agentes de IA acceder a servicios que nadie más puede ver.
Documentación
@layervai/qurl-mcp
⚠️ Renombrado desde
@layerv/qurl-mcpen v0.4.0. El paquete antiguo está obsoleto y no recibirá más actualizaciones. Si estás usando@layerv/qurl-mcp@0.3.x, cambia el ámbito en la configuración de tu cliente MCP: mismo binario, misma clave API, sin otros cambios.
Un servidor MCP de qURL que admite tanto el modo local
stdiocomo el modo remotoHTTPpara crear, gestionar, resolver y compartir enlaces de acceso seguros.
Descripción general
qURL MCP expone las capacidades de qURL a clientes MCP, GPTs, ChatGPT y otras integraciones remotas.
Actualmente admite:
- crear, leer, actualizar y eliminar qURLs
- resolver tokens de acceso
- gestionar tokens y sesiones de qURL
- subir contenido de texto o archivos y generar qURLs
- servir opcionalmente las páginas legales públicas de LayerV
- servir una página de reproducción de video MP4 configurable
Inicio rápido: sin necesidad de cuenta
Comparte una aplicación que se ejecuta en tu máquina, o una URL, de forma privada desde Claude Desktop, Claude Code, Cursor, Codex o cualquier cliente MCP local. No necesitas cuenta ni clave API: sin QURL_API_KEY configurado, el servidor utiliza la CLI de qURL instalada en tu máquina, y la primera publicación crea la identidad de esta máquina por sí sola.
1. Instala la CLI de qURL (macOS y Linux; se necesita Node.js 22.12 o posterior para el servidor MCP):
(umask 022 && mkdir -p "$HOME/.local/bin" "$HOME/.local/state") && chmod go-w "$HOME/.local" "$HOME/.local/bin" "$HOME/.local/state" && curl -fsSL --proto '=https' https://raw.githubusercontent.com/layervai/qurl-integrations/main/scripts/install.sh | INSTALL_DIR="$HOME/.local/bin" sh
En Windows, descarga qurl.exe desde el último lanzamiento y coloca su carpeta en PATH. El servidor encuentra qurl en PATH, y también en ~/.local/bin, /opt/homebrew/bin y /usr/local/bin, que muchos clientes MCP omiten del PATH que proporcionan a los servidores. Si la CLI falta, cada herramienta responde con este comando de instalación en lugar de fallar.
2. Agrega el servidor a tu cliente MCP, sin bloque env.
Claude Desktop (claude_desktop_config.json) y Cursor (~/.cursor/mcp.json):
{
"mcpServers": {
"qurl": {
"command": "npx",
"args": ["-y", "@layervai/qurl-mcp"]
}
}
}
Codex (~/.codex/config.toml):
[mcp_servers.qurl]
command = "npx"
args = ["-y", "@layervai/qurl-mcp"]
Claude Code:
claude mcp add qurl -- npx -y @layervai/qurl-mcp
3. Pide lo que quieras, por ejemplo "comparte la aplicación en localhost:3000 con mi cliente". El agente publica la aplicación (permanece en tu máquina; el servicio en segundo plano de la CLI mantiene el uso compartido activo) y te da un enlace de corta duración para una persona, o activa las solicitudes de acceso para que las personas puedan pedir acceso.
Herramientas sin cuenta
| Herramienta | Qué hace | Comando CLI |
|---|---|---|
publish_crid | Publicar una aplicación local o URL de forma privada y devolver su CRID | qurl publish <url> [--allow-requests] [--access <mode>] |
share_by_crid | Crear un enlace de acceso de corta duración para un CRID publicado por esta máquina | qurl share <CRID> [--ttl] [--session-duration] |
list_crids | Listar los recursos publicados de esta máquina (solo lectura) | qurl list --status active |
manage_crid_requests | Listar, aprobar por código o denegar solicitudes de acceso; listar o eliminar personas aprobadas; activar o desactivar solicitudes | qurl requests, qurl approve, qurl deny, qurl grants |
manage_crid_access | Política de acceso privado, invitaciones y concesiones de consumidores | qurl access <action> <CRID> |
set_crid_sharing | Detener, iniciar o reiniciar el uso compartido de una aplicación local | qurl stop, qurl start, qurl restart |
delete_crid | Eliminar un recurso permanentemente | qurl delete <CRID> --yes |
Las herramientas siguen las mismas reglas que la habilidad de agente qURL (https://layerv.ai/skills/qurl/SKILL.md):
- Todo se publica de forma privada. Estas herramientas no tienen opción de publicación pública.
- Una solicitud de acceso pendiente se aprueba solo con el código de seis dígitos que la persona te proporciona. Ninguna lista muestra un código, y ninguna herramienta aprueba por nombre: cualquiera puede escribir cualquier nombre, y los nombres vuelven como datos no verificados.
- Un enlace de acceso es un secreto de portador para un destinatario. Un CRID no otorga nada por sí mismo y es seguro compartirlo.
El servidor ejecuta la CLI con una matriz de argumentos (nunca un shell), con salida JSON, stdin cerrado, un tiempo de espera y salida limitada, y no registra lo que imprime la CLI. Las herramientas que necesitan una clave de cuenta (las herramientas de enlace, token, sesión y carga de qURL, y los recursos y avisos qurl://) no se listan en este modo.
Con una clave de cuenta
Configura QURL_API_KEY con una clave API de LayerV y el servidor usa la API de qURL directamente, con el conjunto completo de herramientas descrito en el mapa de funciones:
{
"mcpServers": {
"qurl": {
"command": "npx",
"args": ["-y", "@layervai/qurl-mcp"],
"env": { "QURL_API_KEY": "lv_live_xxx" }
}
}
}
Servidor HTTP remoto
El transporte HTTP (qurl-mcp-http) sirve a entornos de ejecución de agentes remotos. Está basado en cuentas: cada solicitud lleva la clave API de qURL del llamante como token de portador, y nunca ejecuta una CLI local. Consulta Autenticación HTTP y la guía MCP de LayerV.
Acceso privado de consumidores
Se requiere Node.js 22.12 o posterior para el SDK compartido de qURL.
publish_crid crea un recurso URL privado con acceso por claves, solo invitación, captura de correo electrónico, revisión o contrato. manage_crid_access cambia la política con su versión actual, envía invitaciones, lista concesiones, aprueba o deniega solicitudes y elimina accesos. Las herramientas devuelven recibos con ámbito de recurso y omiten secretos de invitación y enlaces de portador. Las aplicaciones locales usan la CLI de qURL en la máquina del publicador.
Por ejemplo, un asistente puede convertir "comparte esto con ana@example.com y no permitas solicitudes" en un recurso de solo invitación y una invitación autorizada. Para la captura de correo electrónico, el consumidor debe saber que el publicador recibe su correo electrónico verificado. Un correo electrónico verificado demuestra control del buzón, no identidad legal. La aprobación de contrato requiere un recibo de derecho de backend de publicador confiable; estas herramientas no verifican pagos. No informes la eliminación de la conexión como completa mientras session_cleanup esté pendiente.
Esta función requiere el servicio correspondiente y lectores de admisión. Mantenla deshabilitada hasta que los recorridos de publicador y consumidor pasen. El paquete fija el SDK 3.0.0 publicado y admite instalación limpia con scripts de dependencia bloqueados. Esta versión requiere Node.js 22.12 o posterior; Node.js 20 ya no es compatible.
Modos de ejecución
| Modo | Propósito | Comando de inicio | Caso de uso típico |
|---|---|---|---|
stdio | Servidor MCP de subproceso local | npm run start | Claude Desktop, Cursor, Codex y otros clientes MCP locales |
http | Servidor MCP remoto autenticado | npm run start:http | Entornos de ejecución de agentes remotos detrás de HTTPS |
Mapa de funciones
Herramientas de gestión de qURL
| Herramienta | Descripción |
|---|---|
create_qurl | Crear un nuevo qURL |
resolve_qurl | Resolver un token de acceso en una URL de destino protegida |
list_qurls | Listar recursos qURL |
get_qurl | Obtener detalles de un solo qURL |
delete_qurl | Eliminar un qURL |
extend_qurl | Extender la expiración de un enlace (necesita qurl:read también) |
update_qurl | Actualizar metadatos o expiración de qURL |
mint_link | Acuñar un nuevo enlace de acceso para un recurso existente |
share_by_crid | Acuñar un enlace de acceso temporal desde un CRID de recurso |
batch_create_qurls | Crear múltiples qURLs en una sola solicitud |
revoke_qurl_token | Revocar un token específico |
update_qurl_token | Actualizar un token específico |
list_qurl_sessions | Listar sesiones de acceso activas |
terminate_qurl_sessions | Terminar una o todas las sesiones activas |
share_by_crid reconoce un valor $<CRID> independiente como una solicitud explícita para acuñar un enlace temporal para ese CRID. El $ es un marcador orientado al usuario y se elimina antes de que el CRID se envíe a la API de qURL. Un CRID simple sigue siendo compatible. Esta herramienta requiere qurl:resolve. Su ttl opcional de duración Go debe resolverse a un número entero positivo de segundos. El servicio limita la vida útil solicitada. El uso compartido realiza una solicitud con un tiempo de espera de 30 segundos y no reintenta automáticamente. Repetir la llamada a la herramienta puede acuñar otro enlace.
Los filtros de estado de recursos aceptan active, revoked o active,revoked. Un recurso permanece activo o revocado incluso cuando su expires_at está en el pasado; los tokens de acceso individuales pueden expirar. expired no es aceptado por list_qurls, aunque el análisis de respuestas tolera valores heredados. La instantánea conserva la prosa de expiración obsoleta del proveedor; su enumeración de estado activo/revocado es autoritativa.
El SDK de qURL reutiliza el mismo Idempotency-Key y cuerpo de solicitud al reintentar solicitudes POST/PATCH después de una falla de red o HTTP 429. Las respuestas HTTP 5xx mutantes no se reintentan automáticamente. Cada nueva invocación de herramienta MCP es una nueva operación con una nueva clave, por lo que repetir create_qurl, batch_create_qurls o mint_link puede acuñar tokens adicionales. MCP no expone una clave proporcionada por el llamante.
Herramientas de carga
| Herramienta | Modo | Descripción |
|---|---|---|
upload_file_qurl | stdio | Subir un archivo local y acuñar un qURL |
upload_file_data_qurl | stdio/HTTP | Subir contenido de archivo en base64 y acuñar un qURL |
upload_text_qurl | stdio/HTTP | Subir contenido de texto y acuñar un qURL |
upload_file_qurl es intencionalmente solo stdio. Puede leer cualquier PDF/imagen compatible al que el usuario del proceso MCP local pueda acceder, por lo que los agentes deben invocarlo únicamente para una ruta que el usuario haya seleccionado explícitamente para compartir. No lo exponga a prompts no confiables ni a agentes autónomos: la inyección de prompts podría seleccionar otro PDF/imagen legible en el host. Ejecute stdio bajo una cuenta del sistema operativo cuyo acceso al sistema de archivos esté limitado al contenido compartible previsto. El modo HTTP nunca registra esta herramienta de archivos del host. | ||
| Las herramientas de bytes/texto también están disponibles en stdio para que los clientes locales puedan compartir archivos adjuntos en el chat sin materializarlos primero en una ruta conocida del host. | ||
La carga del conector y la acuñación de enlaces son operaciones separadas. El enlace proviene del POST /api/mint_link/:resource_id del conector (en modo túnel, una vista con marca de agua por destinatario), no de qURL mint_link, por lo que los archivos cargados no se pueden volver a enlazar con mint_link; ejecute la herramienta de carga nuevamente. Los enlaces de carga admiten expires_in (1m-30d, convertido a una caducidad absoluta en el reloj del host MCP, por lo que un reloj del host desviado acorta o alarga el enlace; un enlace cuya caducidad devuelta ya ha pasado según el reloj de este host se devuelve con expires_at_already_past), one_time_use y session_duration (segundos completos, hasta 24h); access_policy y max_sessions deben omitirse o ser nulos; los valores no nulos se rechazan antes de la carga. Si el conector alguna vez acuña más enlaces de los solicitados, el resultado informa cuántos adicionales en unexpected_extra_link_count y cualquier ID que devolvió en unexpected_extra_qurl_ids (como máximo 10); pueden estar activos incluso cuando el reloj de este host sugiere caducidad, así que informe al usuario. Si la acuñación falla después de la carga, el conector actualmente no tiene un endpoint de eliminación; el servidor registra el resource_id huérfano para la limpieza del operador y devuelve el fallo de acuñación. Los enlaces de carga aún no se pueden revocar desde este servidor: delete_qurl en el resource_id de la carga no detiene un enlace acuñado por el conector (la revocación necesita el /api/revoke_links del conector), así que prefiera caducidades cortas para archivos sensibles. | ||
Las entradas de duración aceptan días o semanas completos (7d, 1w), o secuencias de unidades Go sin signo (1h30m, 1.5h). Las secuencias mixtas de días/semanas (1d12h), signos iniciales, fracciones con punto inicial y mu griega (μs) se rechazan; el signo micro (µs) se acepta. | ||
| Los intentos de carga HTTP permanecen limitados por los límites de tasa MCP por IP y por credencial; los operadores de stdio deben restringir por separado los bucles de reintento autónomos. | ||
| La validación de carga vincula el tipo de medio declarado con el nombre de archivo más los marcadores de inicio/fin de formato; no es un escáner de malware ni un decodificador completo de PDF/imagen. | ||
Para resistencia a poliglotas, el marcador final %%EOF de un PDF debe ir seguido solo de espacios en blanco ASCII; la salida del productor con otros bytes finales se rechaza incluso si un lector de PDF permisivo lo aceptaría. La validación JPEG verifica el encuadre y los marcadores terminales en lugar de decodificar segmentos de imagen. El conector autenticado debe decodificar de forma independiente o validar completamente el contenido antes del almacenamiento cuando la validez semántica del medio sea importante. También debe preservar el tipo de medio seguro declarado y servir descargas con X-Content-Type-Options: nosniff en lugar de inferir un tipo ejecutable. | ||
Intencionalmente no hay una lista blanca de rutas a nivel de aplicación: los enlaces simbólicos y las carreras de tiempo de verificación/tiempo de uso hacen que una verificación de prefijo léxico sea un límite de seguridad engañoso. Use una cuenta de sistema operativo dedicada, un contenedor o un montaje de solo lectura cuyos archivos legibles ya estén limitados al directorio de intercambio previsto. El componente de ruta final se abre con O_NOFOLLOW; los enlaces simbólicos de directorios intermedios conservan el comportamiento normal del sistema de archivos bajo este límite de usuario local confiable. |
Recursos MCP
| URI | Descripción |
|---|---|
qurl://links | Lista qURL actual |
qurl://usage | Información actual de cuota y uso |
Prompts MCP
| Prompt | Descripción |
|---|---|
secure_a_service | Prompt de integración de servicio seguro |
audit_links | Prompt de auditoría de enlaces |
rotate_access | Prompt de rotación de acceso |
Inicio rápido
1. Instalar dependencias
npm install
Para una instalación local de solo stdio desde el código fuente, use npm install --omit=optional; esto omite el SDK de AWS. Las implementaciones HTTP que usan la cuota de credenciales de DynamoDB deben usar la instalación ordinaria para que el SDK opcional esté empaquetado.
2. Compilar
npm run build
3. Iniciar
Modo local stdio:
npm run start
Modo remoto HTTP:
npm run start:http
Ejemplo de cliente MCP
Si desea usar este servidor en modo stdio con un cliente MCP local, use la configuración de Inicio rápido. Omita env para trabajar sin una cuenta a través de la CLI local de qURL, o agregue "env": { "QURL_API_KEY": "lv_live_xxx" } para usar la API de qURL con una clave de cuenta.
Archivos de configuración
Copie los ejemplos rastreados para crear archivos de configuración locales:
cp qurl-mcp.config.example.json qurl-mcp.config.json
cp qurl-mcp.http.example.json qurl-mcp.http.json
Los archivos locales están en gitignore para que las credenciales y las rutas específicas de la máquina no se confirmen.
Sus responsabilidades son:
| Archivo | Propósito |
|---|---|
qurl-mcp.config.json | Configuración de tiempo de ejecución compartida utilizada por ambos modos stdio y http |
qurl-mcp.http.json | Configuración de acceso público y listener del servidor solo HTTP |
Referencia de qurl-mcp.config.json
Configuración central compartida
| Campo | Propósito |
|---|---|
maxUploadFileDataBytes | Limita las cargas de archivos decodificados y locales (predeterminado 10mb) |
defaultQurlApiUrl | URL base de la API backend de qURL |
defaultQurlConnectorUrl | URL base del conector de carga |
La configuración compartida tiene estas anulaciones de entorno. Los valores de entorno tienen prioridad sobre el archivo de configuración compartida. El proceso almacena en caché la configuración compartida resuelta, pero invalida automáticamente esa caché cuando cambian los metadatos del archivo o cualquier valor de entorno relevante.
| Variable de entorno | Campo de configuración |
|---|---|
MCP_MAX_UPLOAD_FILE_DATA_BYTES | maxUploadFileDataBytes |
QURL_API_URL | defaultQurlApiUrl |
QURL_CONNECTOR_URL | defaultQurlConnectorUrl |
QURL_SMTP_HOST | smtp.host |
QURL_SMTP_PORT | smtp.port |
QURL_SMTP_SECURE | smtp.secure |
QURL_SMTP_USERNAME | smtp.username |
QURL_SMTP_PASSWORD | smtp.password |
QURL_SMTP_FROM_EMAIL | smtp.fromEmail |
QURL_SMTP_FROM_NAME | smtp.fromName |
QURL_SMTP_ALLOWED_RECIPIENTS | smtp.allowedRecipients |
QURL_SMTP_ALLOWED_RECIPIENT_DOMAINS | smtp.allowedRecipientDomains |
QURL_SMTP_MAX_RECIPIENTS_PER_MESSAGE | smtp.maxRecipientsPerMessage |
QURL_SMTP_MAX_RECIPIENTS_PER_HOUR | smtp.maxRecipientsPerHour |
QURL_PUBLIC_VIDEO_FILE_PATH | publicVideo.filePath |
QURL_PUBLIC_VIDEO_TITLE | publicVideo.title |
QURL_PUBLIC_VIDEO_PAGE_PATH | publicVideo.pagePath |
QURL_API_KEY es intencionalmente solo de entorno y no tiene un campo de archivo de configuración. Prefiera QURL_SMTP_PASSWORD también para el secreto SMTP. Si smtp.password se almacena en el archivo de configuración en un host POSIX, restrinja ese archivo a permisos de solo propietario (por ejemplo, chmod 600); el inicio advierte cuando hay bits de lectura de grupo/otros presentes. Esta verificación es intencionalmente consultiva para que las implementaciones existentes no fallen después de una actualización, y se omite en Windows porque los bits de modo POSIX no están disponibles allí.
La entrega de correo electrónico en sí es de cierre ante fallos a menos que al menos una entrada exacta de smtp.allowedRecipients o una entrada de smtp.allowedRecipientDomains esté configurada; el inicio advierte cuando las credenciales SMTP completas carecen de esa política.
Elevar maxUploadFileDataBytes también eleva el límite de memoria por solicitud del analizador JSON HTTP a aproximadamente 1.5 veces ese valor (hasta unos 150 MB en el máximo de 100 MB), antes de que la decodificación base64 aplique el límite de bytes exacto. Hasta que una sesión haya completado una llamada exitosa a la API de qURL posterior (la acuñación de enlaces de una carga completada cuenta), su límite de analizador permanece en la configuración de carga predeterminada más pequeña de 10 MB; los clientes configurados para una primera carga más grande deben validar la sesión con una llamada pequeña a la API de qURL o una carga más pequeña primero.
Dimensione el máximo configurado y el límite de concurrencia del proxy inverso juntos.
Establezca QURL_API_KEY en el entorno para el modo stdio para usar la API de qURL. Sin él, el modo stdio se ejecuta a través de la CLI local de qURL y no necesita una cuenta (consulte Inicio rápido). En modo HTTP, cada solicitud de cliente proporciona su propia clave de API de qURL como token de portador.
defaultQurlApiUrl y QURL_API_URL requieren HTTPS para hosts que no sean de bucle local porque las claves de API y los datos de qURL se envían como portador a ese destino. HTTP simple se acepta solo para endpoints de desarrollo de bucle local literales. Las URL del conector de carga siguen la misma regla de HTTPS excepto bucle local.
Bucle local significa 127.0.0.0/8 o ::1; las direcciones de enlace comodín como 0.0.0.0 y :: no se aceptan intencionalmente como destinos HTTP salientes.
Los destinos del conector son configuración de operador confiable en lugar de entrada del llamador; por lo tanto, se permiten direcciones privadas y resolución DNS. Fije el nombre de host del conector en el DNS de implementación y no lo apunte a servicios de metadatos.
La credencial de portador qURL del llamador se reenvía a este host, así que trate el control de URL y DNS del conector como parte del límite de confianza de credenciales.
Configure la URL base del servicio del conector, no una ruta de carga: qurl-mcp agrega /api/upload a las rutas base ordinarias, acepta ese sufijo de endpoint exacto y rechaza rutas ambiguas similares a carga como /upload o /api/upload/v2.
El servidor MCP realiza verificaciones de encuadre de archivos limitadas, no análisis de medios completos; el conector debe revalidar de forma independiente el contenido cargado antes del almacenamiento o servicio, y la entrega debe conservar el comportamiento de nosniff como límite de tipo autoritativo.
Las URL base de API y conector que contienen credenciales incrustadas, una cadena de consulta o un fragmento ahora se rechazan durante el inicio. Las implementaciones que anteriormente usaban una de esas formas de URL inusuales deben mover las credenciales a QURL_API_KEY y mantener la URL del servicio configurada en su origen y prefijo de ruta opcional.
Configuración SMTP
| Campo | Propósito |
|---|---|
smtp.host | Nombre de host del servidor SMTP |
smtp.port | Puerto del servidor SMTP |
smtp.secure | true para TLS implícito; false para STARTTLS obligatorio |
smtp.username | Nombre de usuario de inicio de sesión SMTP |
smtp.password | Contraseña de inicio de sesión SMTP o código específico de la aplicación |
smtp.fromEmail | Dirección de correo electrónico del remitente |
smtp.fromName | Nombre para mostrar del remitente |
smtp.allowedRecipients | Lista de permitidos opcional de direcciones exactas |
smtp.allowedRecipientDomains | Lista de permitidos opcional de dominios exactos (los subdominios no están incluidos) |
smtp.maxRecipientsPerMessage | Límite de destinatarios por mensaje (predeterminado 10) |
smtp.maxRecipientsPerHour | Límite de destinatarios intentados por clave qURL por ventana horaria fija (predeterminado 100) |
Estos ajustes se utilizan cuando la entrega de correo electrónico es solicitada por herramientas como:
create_qurlmint_linkupload_text_qurlupload_file_qurlupload_file_data_qurl
Si se configura cualquiera de las listas de permitidos de destinatarios, solo se entrega una dirección o dominio exacto que coincida. Si ambas están vacías, los límites de mensajes y por hora aún se aplican. Las entradas de dominio son exactas: example.com no permite implícitamente mail.example.com; enumere cada subdominio permitido explícitamente. Las direcciones y dominios se normalizan a minúsculas NFC/IDNA en forma ASCII y se elimina un punto raíz DNS final antes de la comparación y entrega. Cada lista de permitidos de destinatarios está limitada a 1,000 entradas configuradas. El límite de destinatarios por mensaje se aplica al fan-out único solicitado completo antes del filtrado de la lista de permitidos, por lo que las direcciones bloqueadas no pueden usarse para enviar un lote de tamaño excesivo. En modo HTTP, cualquier llamador con una clave de API qURL válida puede solicitar una entrega SMTP del lado del servidor. Configure allowedRecipients o allowedRecipientDomains antes de habilitar SMTP en una implementación HTTP expuesta a Internet; las listas de permitidos vacías permiten la entrega a cualquier dirección sintácticamente válida sujeta a las cuotas. El transporte SMTP utiliza tiempos de espera de conexión/socket limitados y se cierra después de cada lote de entrega. Los intentos SMTP fallidos aún consumen cuota, incluso cuando una interrupción transitoria resulta en cero mensajes entregados, por lo que los fallos repetidos no pueden eludir el límite de abuso. Cada solicitud de entrega también tiene un plazo agregado de 60 segundos. Los destinatarios que no se inician antes de ese plazo se informan como omitidos; las colas del lado del proveedor son la ruta admitida para un fan-out más grande o más lento. El cifrado del transporte es obligatorio: smtp.secure: true utiliza TLS implícito, mientras que smtp.secure: false requiere una actualización STARTTLS exitosa. El puerto 465 está reservado para TLS implícito y, por lo tanto, requiere smtp.secure: true. El estado de la cuota por hora se mantiene por proceso del servidor: se restablece al reiniciar y no se comparte entre réplicas. Los operadores que ejecutan múltiples instancias deben imponer un límite agregado correspondiente en el proveedor o puerta de enlace SMTP. La cuota en proceso es, por lo tanto, un respaldo contra abuso, no un límite de seguridad global duradero; el comportamiento de fallo abierto al reiniciar/escalar debe cubrirse con ese límite del lado del proveedor. El seguimiento falla de forma cerrada para nuevos principales después de que se retienen 10,000 principales en un proceso; los principales existentes continúan usando sus depósitos actuales hasta que se eliminan las entradas caducadas. Restrinja la emisión de claves de API qURL y supervise los rechazos de límite de cuota de nuevos principales: rotar muchas claves válidas puede mantener deliberadamente esa tabla compartida a capacidad durante hasta una ventana de cuota. La cuota utiliza una ventana fija de una hora que comienza con el primer intento de entrega después de que expire la ventana anterior. Como con cualquier ventana fija, el tráfico inmediatamente antes y después de un límite puede totalizar casi el doble del valor horario configurado; use un límite deslizante o continuo del lado del proveedor cuando ese estallido de límite deba prevenirse entre réplicas.
La entrega SMTP HTTP está restringida a la credencial del operador configurada en QURL_API_KEY. Una clave qURL válida de un cliente diferente no autoriza el uso de la cuenta SMTP del operador. Sin esa clave de operador explícita, la entrega de correo electrónico HTTP está deshabilitada. Stdio conserva su modelo de confianza de operador local. Las listas de permitidos de destinatarios y las cuotas se aplican en ambos modos.
Los enlaces qURL generados se incluyen en el cuerpo del correo electrónico en texto plano. Restrinja los destinatarios con las listas de permitidos SMTP y configure el cifrado del transporte en el servidor/proveedor SMTP cuando la confidencialidad del enlace sea importante.
Prefiera variables de entorno para las credenciales y políticas SMTP: QURL_SMTP_USERNAME, QURL_SMTP_PASSWORD, QURL_SMTP_FROM_EMAIL, QURL_SMTP_ALLOWED_RECIPIENTS, QURL_SMTP_ALLOWED_RECIPIENT_DOMAINS, QURL_SMTP_MAX_RECIPIENTS_PER_MESSAGE y QURL_SMTP_MAX_RECIPIENTS_PER_HOUR.
Configuración de la Página de Video Pública
| Campo | Propósito |
|---|---|
publicVideo.title | Título mostrado en la página de video pública |
publicVideo.pagePath | Ruta pública de la página de reproducción de video |
publicVideo.filePath | Ruta absoluta del servidor del archivo MP4 |
Cuando se configura, el servidor HTTP además expone:
- una página pública de reproducción de video
- un punto final de transmisión para el archivo MP4
publicVideo.filePath es configuración de operador confiable. El componente final debe ser un archivo regular .mp4 que no sea un enlace simbólico; los enlaces simbólicos de directorios intermedios mantienen la resolución normal del sistema de archivos y, por lo tanto, deben permanecer bajo control del operador. Al inicio, se sondea este activo opcional y se advierte cuando falta, está vacío o no es regular, pero intencionalmente mantiene el servicio MCP y /healthz disponibles. La ruta del archivo de video aún falla de forma cerrada con 404 hasta que el activo se corrija.
Referencia de qurl-mcp.http.json
Use qurl-mcp.http.example.json para desarrollo local y con estado. qurl-mcp.http.stateless.example.json muestra cada campo de almacén y métrica requerido por un servicio sin estado implementado.
| Campo | Propósito |
|---|---|
port | Puerto de escucha MCP HTTP |
host | Dirección de enlace MCP HTTP |
baseUrl | URL base pública del servicio |
allowedHosts | Lista de permitidos de hosts para validación del encabezado Host |
trustProxyHops | Conteo exacto de saltos de proxy inverso confiable (predeterminado 0) |
stateless | Transporte HTTP con ámbito de solicitud sin afinidad de sesión (predeterminado false) |
maxConcurrentRequests | Límite de concurrencia de analizador POST solo sin estado por proceso (predeterminado 20) |
credentialRateLimitStore | Backend de contador de credenciales: memory o dynamodb (predeterminado memory) |
rateLimitDynamoDbTable | Tabla DynamoDB utilizada por el contador de credenciales compartido |
metricsNamespace | Espacio de nombres CloudWatch EMF para métricas de saturación sin estado |
metricsService | Dimensión de Servicio CloudWatch EMF estable |
metricsEnvironment | Dimensión de Entorno CloudWatch EMF estable |
maxSessions | Límite máximo de sesiones MCP activas (predeterminado 1000) |
maxSessionsPerCredential | Límite de sesiones activas e inicializándose por portador (predeterminado 20) |
maxUnvalidatedSessions | Límite de sesiones que no han completado una llamada API qURL posterior (predeterminado 100) |
sessionIdleTtlMs | Ventana de expulsión por inactividad de sesión conectada (predeterminado 15 minutos) |
sessionAbsoluteTtlMs | Vida útil absoluta de la sesión, incluidas solicitudes SSE/herramientas activas (predeterminado 24 horas) |
unvalidatedSessionTtlMs | Plazo de validación absoluto para sesiones de portador nunca validadas (predeterminado 1 minuto) |
mcpRateLimitPerMinute | Límite de solicitudes /mcp por cliente (predeterminado 120) |
publicFileRateLimitPerMinute | Límite de solicitudes de ruta pública por cliente (predeterminado 300) |
Los campos HTTP tienen anulaciones de entorno correspondientes:
| Variable de entorno | Campo de configuración |
|---|---|
MCP_PORT | port |
MCP_HOST | host |
MCP_BASE_URL | baseUrl |
MCP_ALLOWED_HOSTS | allowedHosts |
MCP_TRUST_PROXY_HOPS | trustProxyHops |
MCP_HTTP_STATELESS | stateless |
MCP_MAX_CONCURRENT_REQUESTS | maxConcurrentRequests |
MCP_CREDENTIAL_RATE_LIMIT_STORE | credentialRateLimitStore |
MCP_SERVE_LAYERV_LEGAL_PAGES | serveLayerVLegalPages (predeterminado false) |
MCP_RATE_LIMIT_DYNAMODB_TABLE | rateLimitDynamoDbTable |
MCP_METRICS_NAMESPACE | metricsNamespace |
MCP_METRICS_SERVICE | metricsService |
MCP_METRICS_ENVIRONMENT | metricsEnvironment |
MCP_MAX_SESSIONS | maxSessions |
MCP_MAX_SESSIONS_PER_CREDENTIAL | maxSessionsPerCredential |
MCP_MAX_UNVALIDATED_SESSIONS | maxUnvalidatedSessions |
MCP_SESSION_IDLE_TTL_MS | sessionIdleTtlMs |
MCP_SESSION_ABSOLUTE_TTL_MS | sessionAbsoluteTtlMs |
MCP_UNVALIDATED_SESSION_TTL_MS | unvalidatedSessionTtlMs |
MCP_RATE_LIMIT_PER_MINUTE | mcpRateLimitPerMinute |
MCP_PUBLIC_FILE_RATE_LIMIT_PER_MINUTE | publicFileRateLimitPerMinute |
MCP_MAX_UPLOAD_FILE_DATA_BYTES | maxUploadFileDataBytes (compartido) |
El listener por defecto usa 127.0.0.1. Se rechaza una host que no sea de loopback a menos que | |
allowedHosts esté configurado explícitamente. Establece trustProxyHops (o | |
MCP_TRUST_PROXY_HOPS) al número exacto de saltos de proxy de confianza; déjalo en | |
0 para conexiones directas para que las cabeceras de IP reenviada no puedan suplantar las claves de limitación de tasa. | |
| La lista de permitidos de Host está limitada a 1.000 entradas para que la validación en tiempo de solicitud se mantenga | |
| acotada incluso bajo una configuración operativa patológica. | |
/mcp aplica la asignación de solicitudes configurada de forma independiente tanto a la | |
| IP del cliente como al resumen SHA-256 del portador autenticado. El almacén de memoria | |
| es local al proceso; el almacén DynamoDB usa un contador atómico de ventana fija con clave | |
| por resumen de credencial y minuto UTC. Nunca almacena el portador. Como con cualquier | |
| ventana fija, las solicitudes alrededor de un límite de minuto pueden totalizar casi el doble de la | |
| asignación configurada. El contrato de la tabla es una clave de partición de cadena llamada | |
rate_key; la actualización atómica escribe un contador numérico request_count y una | |
marca de tiempo numérica expires_at de TTL. Habilita el TTL de DynamoDB en expires_at para que | |
| las filas expiradas no se acumulen; el TTL solo programa una limpieza asíncrona, y | |
| el minuto en la clave—no la eliminación física—reinicia la ventana activa. El rol de la tarea | |
requiere dynamodb:DescribeTable para el arranque y dynamodb:UpdateItem en | |
| la ruta de solicitud. Usa capacidad bajo demanda o aprovisiona suficiente capacidad de escritura para | |
la tasa esperada de la flota; la limitación falla de forma cerrada con 503 y nunca recurre | |
| a la memoria. El cliente usa el modo de reintento estándar con como máximo dos intentos, | |
| un tiempo de espera de conexión de un segundo y un tiempo de espera de solicitud de dos segundos que lanza una excepción; | |
| estos límites explícitos acotan cuánto tiempo una solicitud mantiene un permiso de concurrencia | |
| durante una falla parcial del almacén. La dependencia opcional del SDK de AWS está fijada a nivel superior | |
| con versión exacta, mientras que el lockfile de paquetes confirmado fija su grafo transitivo | |
@aws-sdk/* y @smithy/*. Cualquier actualización del SDK debe actualizar el lockfile y | |
mantener verde la prueba de regresión de materialización de tiempo de espera real NodeHttpHandler. | |
| La dependencia se carga solo cuando se selecciona el almacén DynamoDB, por lo que | |
los consumidores solo-stdio pueden instalar con --omit=optional. Las imágenes HTTP desplegadas | |
| deben incluir dependencias opcionales; el arranque falla antes de escuchar si el SDK | |
| está ausente o expone una superficie de runtime incompatible. El cliente usa la | |
cadena estándar de AWS_REGION y proveedor de credenciales; los despliegues ECS | |
| normalmente obtienen ambas del entorno de la tarea y del rol de la tarea. Los despliegues | |
| con proxy inverso deben establecer el recuento de saltos correcto o todos los llamadores detrás del proxy | |
| compartirán el único bucket de IP del proxy. Solo la cuota de credenciales DynamoDB es | |
| a nivel de flota: el limitador de IP es local al proceso, por lo que su asignación efectiva de flota | |
| se multiplica con el número de tareas y debe estar respaldado por un límite de borde compartido. El | |
| despliegue gestionado en qurl-integrations-infra PR #1305 aplica tanto un | |
límite WAF por IP de origen como un tope agregado de flota /mcp más bajo, con | |
| prueba de margen en vivo rastreada en el issue #1306. El bucket de credenciales también evita que una | |
| clave evite la asignación de solicitudes rotando IPs de origen, mientras que | |
maxSessionsPerCredential evita que ocupe todo el pool de sesiones. | |
| Cada valor de portador distinto retiene una entrada de bucket de credenciales para la | |
| ventana actual de un minuto. El limitador de IP se ejecuta primero, por lo que la rotación de tokens desde una fuente | |
no puede crear entradas más rápido que mcpRateLimitPerMinute; el tráfico | |
| distribuido hostil aún requiere el límite de borde compartido documentado. El bucket de IP es el | |
| control principal en proceso contra la rotación arbitraria de portadores porque | |
| cadenas de portador no validadas distintas ocupan necesariamente buckets de credenciales distintos. | |
| En modo con estado, presupuesta la memoria del parser de sesiones pendientes como | |
maxUnvalidatedSessions veces aproximadamente | |
1,5 veces el menor entre maxUploadFileDataBytes y 10 MB (más alrededor de 64 KiB | |
| por solicitud). Con los valores predeterminados, el techo teórico de concurrencia es de aproximadamente | |
1,5 GiB. Reduce maxUnvalidatedSessions y el límite de concurrencia de borde compartido | |
| juntos cuando el despliegue tiene un presupuesto de memoria más pequeño. | |
| Las credenciales del portador se validan de forma concluyente con la primera | |
| llamada exitosa a la API qURL descendente. Hasta entonces, las sesiones usan el tope de sesiones pendientes más pequeño | |
| y el plazo de validación de un minuto, por lo que cadenas de portador no vacías arbitrarias | |
| no pueden ocupar todo el pool de sesiones durante el TTL normal de 15 minutos. Un cliente que | |
| solo realiza introspección MCP permanece pendiente por diseño; después del desalojo por plazo | |
| debe reinicializarse antes de su próxima solicitud. Los topes de sesión y el | |
| plazo de validación son configurables para clientes con intervalos más largos entre | |
| introspección y llamada a herramienta. El plazo es absoluto y se aplica independientemente | |
| de la actividad, incluido un flujo SSE abierto o una primera llamada a herramienta de larga duración. | |
Los clientes validados que se desconectan sin enviar DELETE /mcp retienen su | |
| espacio de sesión acotado durante un período de gracia de reconexión de 30 segundos. Una reconexión | |
| borra ese plazo; de lo contrario, la sesión se elimina sin esperar el | |
TTL de inactividad más largo. Dimensiona maxSessions y el TTL de inactividad para clientes que permanecen conectados | |
| pero no realizan un cierre de sesión explícito. | |
Las sesiones validadas también expiran en sessionAbsoluteTtlMs (24 horas por defecto), | |
| incluso durante un flujo SSE activo o una solicitud de herramienta. Esto evita que los keepalives | |
| fijen un espacio de sesión global o por credencial indefinidamente. | |
| La primera operación qURL descendente debe, por lo tanto, completarse antes de ese | |
| plazo; una primera llamada API inusualmente lenta puede interrumpirse y el cliente | |
| debe reinicializarse. Este comportamiento de fallo cerrado evita que una credencial inválida | |
| extienda su espacio pendiente con una solicitud deliberadamente de larga duración. |
En el tope de sesiones no validadas, una nueva inicialización reemplaza la sesión no validada inactiva más antigua, por lo que los handshakes basura no pueden reservar cada espacio durante todo el TTL de validación. Las sesiones validadas y las solicitudes activas nunca se desalojan; las inicializaciones pendientes y el cierre asíncrono permanecen acotados. El tráfico sostenido aún puede agitar las sesiones no validadas, por lo que los despliegues públicos deberían usar modo sin estado y controles de admisión en el borde.
Aceptar un portador no vacío durante la inicialización MCP es intencional: mantiene
la introspección del protocolo disponible antes de la primera operación qURL, mientras que el
tope global de sesiones, el tope de sesiones por credencial, el tope de sesiones pendientes, el plazo
absoluto y el límite de tasa de solicitudes acotan el uso de espacios por claves inválidas. El
middleware MCP no valida la clave en sí; solo una respuesta exitosa de la API qURL
descendente promueve la sesión.
Los errores descendentes, incluidas respuestas no-2xx que parecen autenticadas, no
la promueven porque un intermediario puede haberlas generado antes de que la API qURL
autenticara el portador.
La promoción asume, por lo tanto, que el endpoint HTTPS de la API qURL configurado y cada
intermediario de confianza no almacenan en caché ni sintetizan respuestas de éxito autenticadas.
Los proxies inversos en esa ruta deben reenviar la autorización y deshabilitar el
almacenamiento en caché de respuestas para el tráfico de la API qURL.
En consecuencia, cualquier llamador con un portador no vacío puede enumerar el catálogo
público de herramientas/recursos/prompts y mantener brevemente un estado de sesión pendiente acotado. En
redes hostiles, coloca los despliegues no-loopback detrás de un proxy consciente de identidad
que preserve la credencial de portador qURL del llamador para la autorización /mcp.
La inicialización y el listado del catálogo devuelven solo metadatos estáticos propiedad del servidor;
no invocan manejadores de herramientas/recursos/prompts, no leen archivos del host, no contactan la
API qURL ni el conector, ni envían correo. Las llamadas a manejadores dependen de la API qURL
configurada para autenticar el portador reenviado antes de devolver datos o aplicar una
operación. El conector configurado es una segunda autoridad de credenciales: debe
autenticar el portador qURL reenviado antes de aceptar o almacenar bytes de carga.
Desplegar un conector no autenticado no es compatible porque permitiría que un
llamador MCP no validado creara estado en el lado del conector.
El modo con estado es el predeterminado de compatibilidad y conserva el registro de sesiones MCP existente,
GET SSE, el comportamiento explícito de DELETE y el cobro de cuota de credenciales local al proceso
para los tres métodos MCP. El modo sin estado crea y cierra un servidor y
transporte para cada POST, ignora mcp-session-id y
devuelve respuestas 405 con forma JSON-RPC para GET y DELETE. Es el modo requerido
detrás de un balanceador de carga o servicio de autoescalado porque ninguna solicitud depende
de la afinidad local al proceso. El permiso de concurrencia se adquiere antes del análisis
JSON y se libera en cada ruta de respuesta/error/desconexión. El modo sin estado
usa el techo del parser maxUploadFileDataBytes configurado directamente porque
el permiso de concurrencia previo al análisis proporciona su límite de amplificación de memoria.
Presupuesta aproximadamente maxConcurrentRequests veces (1,5 veces
maxUploadFileDataBytes más 64 KiB) por proceso; la concurrencia predeterminada en
el techo de carga de 100 MB es de aproximadamente 3 GiB antes del trabajo descendente.
El arranque sin estado rechaza configuraciones cuyo presupuesto de parser conservador
supere los 4 GiB. Reduce aún más cualquiera de los dos ajustes cuando la tarea ECS tiene un
límite de memoria más pequeño. En contraste, las sesiones con estado por encima del techo predeterminado deben
primero completar una llamada exitosa a la API qURL descendente.
En redes hostiles, un límite de tamaño de solicitud en el borde autenticado no mayor que
el techo del parser configurado es un requisito de despliegue: el permiso acota la
memoria agregada, pero un portador no vacío no se valida de forma autoritativa hasta
que la operación analizada llega a la API qURL descendente.
El listener sin estado acota la recepción de cabeceras a 15 segundos y tanto la recepción
completa de la solicitud como la vida útil del socket inactivo a 120 segundos. Un permiso de concurrencia
abarca desde el análisis hasta la finalización de la respuesta, por lo que los clientes atascados no pueden retener
todo el pool de permisos indefinidamente. Una llamada a herramienta que no produce tráfico de socket
durante 120 segundos se aborta intencionalmente; las integraciones que necesitan operaciones silenciosas
más largas deben mover ese trabajo detrás de una API asíncrona en lugar de aumentar
este límite de retención a nivel de flota.
El modo stateless desplegado (no-loopback) requiere el almacén de credenciales DynamoDB
y los tres campos estables de identidad de métricas. Emite un heartbeat EMF de 30 segundos:
McpConcurrencyUtilization es la utilización máxima de permisos observada durante el
intervalo en la admisión de solicitudes y el heartbeat (incluyendo solicitudes que comienzan y
terminan entre heartbeats), mientras que
McpConcurrencyRejected y McpRateLimitStoreErrors son deltas de intervalo
snapshot-and-zero que incluyen ceros explícitos. Los límites de sesión y las cuotas de destinatarios de correo
permanecen en memoria; la cuota de credenciales DynamoDB es a nivel de flota y cuenta
cada POST HTTP autenticado, incluyendo inicialización, descubrimiento y llamadas
de herramientas. Dimensione esa cuota para el patrón completo de solicitudes esperado en lugar de
solo llamadas de herramientas. El permiso también abarca el incremento acotado de DynamoDB: durante
un brownout del almacén, cada solicitud admitida puede retener un permiso durante aproximadamente
cuatro segundos (dos intentos de dos segundos) antes de fallar de forma cerrada, mientras que las solicitudes
excedentes reciben un 503 de concurrencia rápido. El contador de ventana fija se incrementa en cada
intento, incluyendo
intentos que ya están por encima del límite de credenciales; los límites de tasa perimetrales y las alarmas de
escritura/throttle de DynamoDB deben, por lo tanto, acotar la amplificación de escritura abusiva.
Los propietarios del despliegue deben convertir tanto las alarmas como una sonda de amplificación de escritura
por encima del límite en puertas de promoción estrictas, en lugar de tratarlas como observabilidad opcional.
El despliegue gestionado en
qurl-integrations-infra#1305
aprovisiona esas alarmas, con prueba en vivo rastreada en su ledger de despliegue y
el issue #1306 antes de la promoción.
Los integradores directos de createHttpRuntime que inyectan una implementación
de almacén de credenciales deben declarar credentialRateLimitStore: "dynamodb" para
el modo stateless no-loopback. La interfaz de inyección genérica no puede probar que un
backend personalizado se comparte entre réplicas, por lo que la inyección no es deliberadamente una
vía de escape del contrato desplegado.
Los campos de identidad de métricas se rechazan en modo stateful para que el indicador de concurrencia
no pueda informar silenciosamente un cero engañoso.
Cada POST stateless posee un servidor MCP y transporte nuevos, por lo que ninguna solicitud puede
heredar el estado del handler de otra credencial. El teardown de respuesta completada se rastrea
de forma asíncrona. La admisión se detiene cuando ese backlog alcanza
maxConcurrentRequests; las solicitudes ya en vuelo pueden entonces terminar, por lo que el
backlog puede acercarse transitoriamente al doble de esa cantidad, pero permanece acotado. Mientras
la puerta de admisión está cerrada, las nuevas solicitudes fallan con 503 e incrementan
McpConcurrencyRejected en lugar de crecer la memoria de teardown sin límite.
Ese contador representa intencionalmente fallos de admisión por saturación de solicitudes activas
o por backpressure de teardown. El autoscaling debe usar
McpConcurrencyUtilization solo; el contador de rechazos sigue siendo digno de página,
y la baja utilización junto con rechazos identifica retraso de teardown.
Agrupar estos objetos debilitaría el aislamiento de solicitudes y no es deliberadamente una
optimización de rendimiento sin presión de registro medida.
/healthz y el endpoint público de archivos de video usan cada uno su propio
bucket de publicFileRateLimitPerMinute, aislado del tráfico legal/página de video
y entre sí. Mantenga la frecuencia del balanceador de carga, las sondas de liveness y las solicitudes
de rango de video esperadas por debajo de esa asignación por IP de origen (300
solicitudes/minuto por defecto), o auméntela para clientes inusualmente agresivos.
Prioridad de Configuración
Por defecto, la configuración se carga desde los dos archivos JSON locales anteriores. Si un
archivo está ausente, se usan los valores predeterminados integrados y las variables de entorno.
Las rutas de configuración relativas—incluyendo los valores predeterminados—se resuelven desde el
directorio de trabajo del proceso. Establezca las variables de ruta explícitas a continuación cuando un supervisor,
npx, o un host MCP inicie el servidor desde un directorio diferente.
Las siguientes variables de entorno anulan de forma independiente las rutas de los archivos de configuración:
QURL_MCP_CONFIGQURL_MCP_HTTP_CONFIG
QURL_MCP_HTTP_CONFIG nunca reemplaza la ruta de configuración de runtime compartida. Esto mantiene
los ajustes del listener sin ocultar silenciosamente los ajustes SMTP, de conector o de API.
server.json y smithery.yaml describen el transporte stdio publicado, por lo que
incluyen ajustes compartidos de subida/SMTP pero omiten intencionalmente variables de listener
solo HTTP como QURL_MCP_HTTP_CONFIG y MCP_MAX_SESSIONS.
No confíe claves de API, credenciales SMTP o rutas privadas del sistema de archivos.
Rutas HTTP
Después de iniciar en modo http, las rutas comunes son:
| Ruta | Propósito |
|---|---|
/mcp | Endpoint MCP remoto principal |
/healthz | Endpoint de verificación de salud |
/legal/privacy | Página pública de política de privacidad |
/legal/terms | Página pública de términos de servicio |
publicVideo.pagePath | Página pública de reproducción de video |
publicVideo.pagePath + /file | Endpoint de streaming MP4 |
/healthz está intencionalmente sin autenticación y sin validación de Host para cada
llamador, expone solo { "ok": true }, y usa el límite de solicitudes de ruta pública
configurado en un bucket separado para que las sondas de salud no puedan consumir la
asignación de ruta legal/video. Un 429 de esta ruta
significa que la fuente de la sonda excedió publicFileRateLimitPerMinute, no que la
aplicación falló su verificación de liveness; mantenga la frecuencia de sondas por debajo de ese límite.
Se registra antes de la validación de Host porque las sondas de destino ALB usan la IP de la tarea
y el puerto como Host; las rutas públicas MCP y de navegador permanecen validadas por Host.
Autenticación HTTP
El endpoint /mcp requiere Authorization: Bearer <qURL API key> en cada
solicitud. En modo stateful, el token bearer está vinculado a la sesión MCP resultante,
por lo que un ID de sesión no puede reutilizarse con una credencial diferente. En
modo stateless, el bearer permanece con alcance de solicitud y se descarta cuando la
respuesta se cierra.
Límite de autenticación del operador: la inicialización acepta cualquier token bearer
no vacío y permite que el catálogo público de herramientas/recursos/prompts se lea
antes de la validación autoritativa por la primera llamada API qURL posterior.
Ese catálogo se ensambla a partir de esquemas y descripciones estáticas y no
incluye tokens bearer, credenciales SMTP u otra configuración del operador.
Los límites de sesión no validados, un plazo de validación corto y los límites de tasa de solicitudes
acotan ese estado previo a la validación; el token suministrado se reenvía solo a la
API qURL configurada y, para herramientas de subida, al Conector qURL configurado.
Las sesiones solo de introspección permanecen por lo tanto sin validar y se cierran en
unvalidatedSessionTtlMs; los clientes pueden reinicializar si necesitan una sesión de mayor
duración. Una sesión se promueve solo después de una llamada API qURL exitosa o un
enlace de subida entregable acuñado a través del conector configurado (que reenvía el
bearer a la API qURL)—las llamadas rechazadas o limitadas por tasa no prueban que la credencial
sea válida. El conector es por lo tanto parte de este límite de confianza: apunte
QURL_CONNECTOR_URL solo a un conector que autentique el bearer. Su
texto de error de subida (acotado a 1 KiB, caracteres de control aplanados) y los enlaces
que acuña se devuelven al agente, y los enlaces acuñados pueden enviarse por correo. Las sesiones desconectadas
permanecen registradas durante un período de gracia de reconexión SSE de 30 segundos, mientras que
maxSessions y maxSessionsPerCredential acotan esa asignación bajo rotación.
Las solicitudes sin un encabezado Origin se aceptan para clientes MCP que no son de navegador.
Cuando Origin está presente, debe coincidir con el origen de baseUrl; los valores malformados o
de origen cruzado se rechazan en /mcp. Las rutas públicas de salud, legales y de video
configuradas no usan estado de origen de navegador y no están sujetas a esta verificación.
Configure clientes MCP remotos con:
| Configuración | Valor |
|---|---|
| URL del servidor MCP | Su URL HTTPS pública más /mcp |
| Autenticación | Token Bearer |
| Token | La clave API qURL del llamador |
Si un cliente solo admite descubrimiento OAuth, coloque una puerta de enlace compatible con OAuth
delante de este servidor en lugar de exponer /mcp sin autenticación.
Cómo Verificar el Despliegue
Verificaciones a Nivel de Servicio
Comience con:
/healthz/mcp
Los documentos legales de LayerV están deshabilitados por defecto. Solo los servicios operados por LayerV
deben establecer MCP_SERVE_LAYERV_LEGAL_PAGES=true (o serveLayerVLegalPages: true
en la configuración HTTP). Los operadores autoalojados deben publicar sus propias políticas.
Verificaciones de Páginas Públicas
Cuando estén habilitadas, verifique las páginas legales y la página de video configurada:
/legal/privacy/legal/terms- la ruta de página de video público configurada
Verificación de Dominio
Si planea usar OpenAI Platform, asegúrese de que exista la siguiente ruta a nivel raíz:
/.well-known/openai-apps-challenge
Este archivo de verificación debe vivir bajo la ruta raíz del dominio
.well-known, no bajo/mcp.
Docker
El repositorio incluye un Dockerfile para el despliegue en contenedores.
Ejemplo:
docker build -t qurl-mcp .
docker run -i -e QURL_API_KEY=lv_live_xxx qurl-mcp
Si despliega con Docker, asegúrese de que el contenedor aún pueda acceder a los archivos de configuración correctos, o anule las rutas de los archivos de configuración con variables de entorno.
Ejecute el listener HTTP localmente en Docker:
La imagen usa por defecto el punto de entrada stdio y el servidor HTTP usa por defecto
loopback local del contenedor. Los despliegues HTTP deben anular el comando y vincularse
a 0.0.0.0 con una lista de Hosts permitidos explícita y un origen HTTPS público. Este
ejemplo publica solo en loopback del host; coloque un proxy inverso TLS delante de
él para el origen https://mcp.example.com configurado:
docker run --rm -p 127.0.0.1:3000:3000 \
-e MCP_HOST=0.0.0.0 \
-e MCP_BASE_URL=https://mcp.example.com \
-e MCP_ALLOWED_HOSTS=mcp.example.com,127.0.0.1,localhost \
qurl-mcp node dist/http.js
Para un único proxy inverso de producción confiable, establezca
MCP_TRUST_PROXY_HOPS=1, use el origen HTTPS público en MCP_BASE_URL, y
establezca MCP_ALLOWED_HOSTS al nombre de host público. No exponga el listener del contenedor
directamente cuando la confianza del proxy esté habilitada.
Comandos Comunes
| Comando | Propósito |
|---|---|
npm run build | Compilar TypeScript |
npm test | Ejecutar pruebas |
npm run test:coverage | Ejecutar cobertura forzada |
npm run lint | Ejecutar ESLint |
npm run dev | Modo de observación de TypeScript |
npm run format | Formatear código fuente |
npm run format:check | Verificar formato |
npm run start | Iniciar modo stdio |
npm run start:http | Iniciar modo HTTP |
Orden de Despliegue Recomendado
- Copie y actualice los dos archivos de configuración de ejemplo
- Establezca las credenciales a través de variables de entorno
- Ejecute
npm install - Ejecute
npm run build - Ejecute
npm run start:http - Verifique
/healthz - Verifique que las solicitudes
/mcpno autenticadas reciban401 - Configure el proxy inverso HTTPS
- Verifique una inicialización MCP autenticada y las páginas públicas opcionales
Activos de Terceros
La generación de texto a PDF incluye la fuente Noto Sans SC de peso regular como TrueType
(alrededor de 10 MB) para cobertura de glifos multilingüe sin conexión. La fuente estática conserva
todos los caracteres de la fuente variable original; PDFKit incrusta solo los glifos
usados por cada documento. No se necesita descarga ni dependencia de runtime adicional.
Su Licencia de Fuente Abierta SIL y aviso de copyright están incluidos en
assets/fonts/OFL.txt; los detalles de conversión están en assets/fonts/README.md.
Licencia
MIT -- LayerV AI