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

npm version

⚠️ Renombrado desde @layerv/qurl-mcp en v0.4.0. El paquete anterior 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 stdio como el modo remoto HTTP para 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 páginas legales públicas
  • servir una página de reproducción de video MP4 configurable

Modos de ejecución

ModoPropósitoComando de inicioCaso de uso típico
stdioServidor MCP local en subprocesonpm run startClaude Desktop, Cursor, Codex y otros clientes MCP locales
httpServidor MCP remoto autenticadonpm run start:httpEntornos de ejecución de agentes remotos detrás de HTTPS

Mapa de funciones

Herramientas de gestión de qURL

HerramientaDescripción
create_qurlCrear un nuevo qURL
resolve_qurlResolver un token de acceso en una URL de destino protegida
list_qurlsListar recursos qURL
get_qurlObtener detalles de un solo qURL
delete_qurlEliminar un qURL
extend_qurlExtender la expiración de qURL
update_qurlActualizar metadatos o expiración de qURL
mint_linkAcuñar un nuevo enlace de acceso para un recurso existente
batch_create_qurlsCrear múltiples qURLs en una sola solicitud
revoke_qurl_tokenRevocar un token específico
update_qurl_tokenActualizar un token específico
list_qurl_sessionsListar sesiones de acceso activas
terminate_qurl_sessionsTerminar una o todas las sesiones activas

Herramientas de subida

HerramientaModoDescripción
upload_file_qurlstdioSubir un archivo local y acuñar un qURL
upload_file_data_qurlstdio/HTTPSubir contenido de archivo en base64 y acuñar un qURL
upload_text_qurlstdio/HTTPSubir 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 solo para una ruta que el usuario haya seleccionado explícitamente para compartir. No lo expongas a prompts no confiables o agentes autónomos: la inyección de prompts podría seleccionar otro PDF/imagen legible en el host. Ejecuta stdio bajo una cuenta de 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 del chat sin materializarlos primero en una ruta conocida del host. La subida del conector y la acuñación de qURL son operaciones separadas. Si la acuñación falla después de la subida, 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 error de acuñación. Los intentos de subida 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 subida 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 la 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 las 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 condiciones de carrera 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. Usa una cuenta de sistema operativo dedicada, contenedor o 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

URIDescripción
qurl://linksLista actual de qURL
qurl://usageInformación actual de cuota y uso

Prompts MCP

PromptDescripción
secure_a_servicePrompt de integración segura de servicios
audit_linksPrompt de auditoría de enlaces
rotate_accessPrompt de rotación de acceso

Inicio rápido

1. Instalar dependencias

npm install

Para una instalación local de fuente solo stdio, usa npm install --omit=optional; esto omite el AWS SDK. Las implementaciones HTTP que usan la cuota de credenciales 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 deseas usar este servidor en modo stdio con un cliente MCP local:

{
  "mcpServers": {
    "qurl": {
      "command": "npx",
      "args": ["@layervai/qurl-mcp"],
      "env": { "QURL_API_KEY": "lv_live_xxx" }
    }
  }
}

Archivos de configuración

Copia 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:

ArchivoPropósito
qurl-mcp.config.jsonConfiguración de ejecución compartida utilizada por ambos modos stdio y http
qurl-mcp.http.jsonConfiguración de escucha del servidor y acceso público solo HTTP

Referencia de qurl-mcp.config.json

Configuración central compartida

CampoPropósito
maxUploadFileDataBytesLimita las subidas decodificadas y de archivos locales (predeterminado 10mb)
defaultQurlApiUrlURL base de la API backend de qURL
defaultQurlConnectorUrlURL base del conector de subida

La configuración compartida tiene estas anulaciones de entorno. Los valores de entorno tienen precedencia sobre el archivo de configuración compartido. El proceso almacena en caché la configuración compartida resuelta pero invalida automáticamente esa caché cuando los metadatos del archivo o cualquier valor de entorno relevante cambian.

Variable de entornoCampo de configuración
MCP_MAX_UPLOAD_FILE_DATA_BYTESmaxUploadFileDataBytes
QURL_API_URLdefaultQurlApiUrl
QURL_CONNECTOR_URLdefaultQurlConnectorUrl
QURL_SMTP_HOSTsmtp.host
QURL_SMTP_PORTsmtp.port
QURL_SMTP_SECUREsmtp.secure
QURL_SMTP_USERNAMEsmtp.username
QURL_SMTP_PASSWORDsmtp.password
QURL_SMTP_FROM_EMAILsmtp.fromEmail
QURL_SMTP_FROM_NAMEsmtp.fromName
QURL_SMTP_ALLOWED_RECIPIENTSsmtp.allowedRecipients
QURL_SMTP_ALLOWED_RECIPIENT_DOMAINSsmtp.allowedRecipientDomains
QURL_SMTP_MAX_RECIPIENTS_PER_MESSAGEsmtp.maxRecipientsPerMessage
QURL_SMTP_MAX_RECIPIENTS_PER_HOURsmtp.maxRecipientsPerHour
QURL_PUBLIC_VIDEO_FILE_PATHpublicVideo.filePath
QURL_PUBLIC_VIDEO_TITLEpublicVideo.title
QURL_PUBLIC_VIDEO_PAGE_PATHpublicVideo.pagePath

QURL_API_KEY es intencionalmente solo de entorno y no tiene campo de archivo de configuración. Prefiere QURL_SMTP_PASSWORD para el secreto SMTP también. Si smtp.password está almacenado en el archivo de configuración en un host POSIX, restringe ese archivo a permisos solo del propietario (por ejemplo, chmod 600); el inicio advierte cuando los bits de lectura de grupo/otros están 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 smtp.allowedRecipients o una entrada 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 qURL posterior, su límite de analizador permanece en la configuración de subida predeterminada más pequeña de 10 MB; los clientes configurados para una primera subida más grande deben validar la sesión con una llamada pequeña a la API qURL primero. Dimensiona el máximo configurado y el límite de concurrencia del proxy inverso juntos.

Establece QURL_API_KEY en el entorno para el modo stdio. En modo HTTP, cada solicitud de cliente proporciona su propia clave API qURL como token de portador.

defaultQurlApiUrl y QURL_API_URL requieren HTTPS para hosts que no sean de bucle local porque las claves 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 subida 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 confiable del operador en lugar de entrada del llamador; por lo tanto, se permiten direcciones privadas y resolución DNS. Fija el nombre de host del conector en el DNS de implementación y no lo apuntes a servicios de metadatos. La credencial de portador qURL del llamador se reenvía a este host, así que trata la URL del conector y el control DNS como parte del límite de confianza de credenciales. Configura la URL base del servicio del conector, no una ruta de subida: qurl-mcp agrega /api/upload a las rutas base ordinarias, acepta ese sufijo de endpoint exacto y rechaza rutas ambiguas similares a subidas como /upload o /api/upload/v2. El servidor MCP realiza verificaciones limitadas de encuadre de archivos, no análisis completo de medios; el conector debe revalidar de forma independiente el contenido subido antes del almacenamiento o servicio, y la entrega debe conservar el comportamiento nosniff como el límite de tipo autoritativo. Las URL base de API y conectores que contienen credenciales incrustadas, una cadena de consulta o un fragmento ahora se rechazan durante el inicio. Los despliegues que anteriormente usaban una de esas formas de URL inusuales deben mover las credenciales a QURL_API_KEY y mantener la URL de servicio configurada en su origen y prefijo de ruta opcional.

Configuración de SMTP

CampoPropósito
smtp.hostNombre de host del servidor SMTP
smtp.portPuerto del servidor SMTP
smtp.securetrue para TLS implícito; false para STARTTLS obligatorio
smtp.usernameNombre de usuario de inicio de sesión SMTP
smtp.passwordContraseña de inicio de sesión SMTP o código específico de aplicación
smtp.fromEmailDirección de correo del remitente
smtp.fromNameNombre para mostrar del remitente
smtp.allowedRecipientsLista de permitidos opcional de direcciones exactas
smtp.allowedRecipientDomainsLista de permitidos opcional de dominios exactos (los subdominios no están incluidos)
smtp.maxRecipientsPerMessageLímite de destinatarios por mensaje (predeterminado: 10)
smtp.maxRecipientsPerHourLímite de destinatarios intentados por clave qURL por ventana horaria fija (predeterminado: 100)

Estos ajustes se utilizan cuando la entrega de correo es solicitada por herramientas como:

  • create_qurl
  • mint_link
  • upload_text_qurl
  • upload_file_qurl
  • upload_file_data_qurl

Si se configura cualquiera de las listas de permitidos de destinatarios, solo se entrega una coincidencia exacta de dirección o dominio. Si ambas están vacías, los límites de mensaje y por hora siguen aplicándose. 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 en forma NFC/IDNA ASCII y se elimina un punto raíz DNS final antes de la comparación y la entrega. Cada lista de permitidos de destinatarios está limitada a 1.000 entradas configuradas. El límite de destinatarios por mensaje se aplica a la expansión única completa solicitada 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 un despliegue HTTP expuesto 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 acotados y se cierra después de cada lote de entrega. Los intentos SMTP fallidos siguen consumiendo 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 no iniciados antes de ese plazo se informan como omitidos; las colas del lado del proveedor son la vía compatible para expansiones más grandes o más lentas. 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 horaria se mantiene por proceso de 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, una red de seguridad contra abusos, no un límite de seguridad global duradero; el comportamiento de fallo abierto ante reinicio/escalado horizontal debe estar cubierto por ese límite del lado del proveedor. El seguimiento falla cerrado 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 plena 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 móvil del lado del proveedor cuando deba prevenirse esa ráfaga en los límites entre réplicas. Los enlaces qURL generados se incluyen en el cuerpo del correo 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 de los enlaces 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 pública de video

CampoPropósito
publicVideo.titleTítulo mostrado en la página pública de video
publicVideo.pagePathRuta pública de la página de reproducción de video
publicVideo.filePathRuta 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 endpoint de streaming para el archivo MP4

publicVideo.filePath es configuración de operador de confianza. 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. El inicio sondea este recurso opcional y advierte cuando falta, está vacío o no es regular, pero mantiene intencionalmente el servicio MCP y /healthz disponibles. La ruta del archivo de video sigue fallando cerrada con 404 hasta que el recurso se corrija.

Referencia de qurl-mcp.http.json

Use qurl-mcp.http.example.json para desarrollo local con estado. qurl-mcp.http.stateless.example.json muestra todos los campos de almacenamiento y métricas requeridos por un servicio sin estado desplegado.

CampoPropósito
portPuerto de escucha HTTP MCP
hostDirección de enlace HTTP MCP
baseUrlURL base pública del servicio
allowedHostsLista de permitidos de hosts para la validación del encabezado Host
trustProxyHopsConteo exacto de saltos de proxy inverso de confianza (predeterminado: 0)
statelessTransporte HTTP con ámbito de solicitud sin afinidad de sesión (predeterminado: false)
maxConcurrentRequestsLímite de concurrencia POST/parser solo sin estado por proceso (predeterminado: 20)
credentialRateLimitStoreBackend de contador de credenciales: memory o dynamodb (predeterminado: memory)
rateLimitDynamoDbTableTabla DynamoDB utilizada por el contador de credenciales compartido
metricsNamespaceEspacio de nombres CloudWatch EMF para métricas de saturación sin estado
metricsServiceDimensión de servicio CloudWatch EMF estable
metricsEnvironmentDimensión de entorno CloudWatch EMF estable
maxSessionsLímite máximo de sesiones MCP activas (predeterminado: 1000)
maxSessionsPerCredentialLímite de sesiones activas e inicializándose por portador (predeterminado: 20)
maxUnvalidatedSessionsLímite de sesiones que no han completado una llamada a la API qURL descendente (predeterminado: 100)
sessionIdleTtlMsVentana de expulsión por inactividad de sesiones conectadas (predeterminado: 15 minutos)
sessionAbsoluteTtlMsVida útil absoluta de la sesión, incluidas las solicitudes activas SSE/herramientas (predeterminado: 24 horas)
unvalidatedSessionTtlMsPlazo absoluto de validación para sesiones de portador nunca validadas (predeterminado: 1 minuto)
mcpRateLimitPerMinuteLímite de solicitudes /mcp por cliente (predeterminado: 120)
publicFileRateLimitPerMinuteLímite de solicitudes de ruta pública por cliente (predeterminado: 300)

Los campos HTTP tienen anulaciones de entorno correspondientes:

Variable de entornoCampo de configuración
MCP_PORTport
MCP_HOSThost
MCP_BASE_URLbaseUrl
MCP_ALLOWED_HOSTSallowedHosts
MCP_TRUST_PROXY_HOPStrustProxyHops
MCP_HTTP_STATELESSstateless
MCP_MAX_CONCURRENT_REQUESTSmaxConcurrentRequests
MCP_CREDENTIAL_RATE_LIMIT_STOREcredentialRateLimitStore
MCP_RATE_LIMIT_DYNAMODB_TABLErateLimitDynamoDbTable
MCP_METRICS_NAMESPACEmetricsNamespace
MCP_METRICS_SERVICEmetricsService
MCP_METRICS_ENVIRONMENTmetricsEnvironment
MCP_MAX_SESSIONSmaxSessions
MCP_MAX_SESSIONS_PER_CREDENTIALmaxSessionsPerCredential
MCP_MAX_UNVALIDATED_SESSIONSmaxUnvalidatedSessions
MCP_SESSION_IDLE_TTL_MSsessionIdleTtlMs
MCP_SESSION_ABSOLUTE_TTL_MSsessionAbsoluteTtlMs
MCP_UNVALIDATED_SESSION_TTL_MSunvalidatedSessionTtlMs
MCP_RATE_LIMIT_PER_MINUTEmcpRateLimitPerMinute
MCP_PUBLIC_FILE_RATE_LIMIT_PER_MINUTEpublicFileRateLimitPerMinute
MCP_MAX_UPLOAD_FILE_DATA_BYTESmaxUploadFileDataBytes (compartido)
El listener usa por defecto 127.0.0.1. Se rechaza una host que no sea de loopback a menos que
allowedHosts esté configurada 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 falsear 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 petición se mantenga
acotada incluso bajo una configuración operativa patológica.
/mcp aplica la asignación de peticiones configurada de forma independiente tanto a la
IP del cliente como al resumen SHA-256 del bearer autenticado. El almacén en 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 bearer. Como con cualquier
ventana fija, las peticiones 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 petición. Usa capacidad bajo demanda o aprovisiona suficiente capacidad de escritura para
la tasa esperada del 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 petición de dos segundos que lanza una excepción;
estos límites explícitos acotan cuánto tiempo una petición mantiene un permiso de concurrencia
durante un fallo 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 en 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 las 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 número correcto de saltos 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 respaldada 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 de flota agregado /mcp inferior, con
prueba de margen en vivo rastreada en el issue #1306. El bucket de credenciales también evita que una
clave omita la asignación de peticiones rotando IPs de origen, mientras que
maxSessionsPerCredential evita que ocupe todo el pool de sesiones.
Cada valor de bearer distinto conserva 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 sola 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 dentro del proceso contra la rotación arbitraria de bearers porque
cadenas de bearer no validadas y distintas ocupan necesariamente buckets de credenciales distintos.
En el modo con estado, presupuesta la memoria del parser de sesiones pendientes como
maxUnvalidatedSessions multiplicado por aproximadamente
1,5 veces el menor entre maxUploadFileDataBytes y 10 MB (más unos 64 KiB
por petición). Con los valores predeterminados, el techo de concurrencia teórico es de aproximadamente
1,5 GiB. Reduce maxUnvalidatedSessions y el límite de concurrencia de borde compartido
a la vez cuando el despliegue tiene un presupuesto de memoria menor.
Las credenciales de bearer se validan de forma concluyente con la primera
llamada exitosa a la API qURL posterior. Hasta entonces, las sesiones usan el tope menor de sesiones pendientes
y el plazo de validación de un minuto, por lo que cadenas de bearer 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; tras la expulsión por plazo
debe reinicializarse antes de su siguiente petición. 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 conservan su
ranura de sesión acotada durante un período de gracia de reconexión de 30 segundos. Una reconexión
elimina ese plazo; de lo contrario, la sesión se recolecta 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 petición de herramienta. Esto evita que los keepalives
fijen una ranura de sesión global o por credencial indefinidamente.
La primera operación qURL posterior debe, por tanto, completarse antes de ese
plazo; una primera llamada a la API inusualmente lenta puede interrumpirse y el cliente
debe reinicializarse. Este comportamiento de fallo cerrado evita que una credencial inválida
extienda su ranura pendiente con una petición deliberadamente de larga duración.
Aceptar un bearer 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 peticiones acotan el uso de ranuras con claves inválidas. El
middleware MCP no valida la clave por sí mismo; solo una respuesta exitosa de la API qURL
posterior promueve la sesión.
Los errores posteriores, incluidas respuestas no-2xx que parecen autenticadas, no
la promueven porque un intermediario puede haberlas generado antes de que la API qURL
autenticara el bearer.
La promoción asume, por tanto, que el endpoint HTTPS configurado de la API qURL 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 bearer no vacío puede enumerar el catálogo público
de herramientas/recursos/prompts y mantener brevemente 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 bearer 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 con la
API qURL ni con el conector, ni envían correo. Las llamadas a manejadores dependen de la API qURL
configurada para autenticar el bearer reenviado antes de devolver datos o aplicar una
operación. El conector configurado es una segunda autoridad de credenciales: debe
autenticar el bearer qURL reenviado antes de aceptar o almacenar bytes de subida.
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 petición 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 de 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 multiplicado por (1,5 veces maxUploadFileDataBytes más 64 KiB) por proceso; la concurrencia predeterminada con el techo de subida de 100 MB es de aproximadamente 3 GiB antes del trabajo posterior. 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 menor. En contraste, las sesiones con estado por encima del techo predeterminado deben primero completar una llamada exitosa a la API qURL posterior. En redes hostiles, un límite de tamaño de petición de borde autenticado no mayor que el techo de parser configurado es un requisito de despliegue: el permiso acota la memoria agregada, pero un bearer no vacío no se valida de forma autoritativa hasta que la operación analizada llega a la API qURL posterior.

El listener sin estado acota la recepción de cabeceras a 15 segundos y tanto la recepción completa de la petición 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 detenidos 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 a herramientas. Dimensione esa cuota para el patrón completo de solicitudes esperado, no solo para llamadas a herramientas. El permiso también abarca el incremento acotado de DynamoDB: durante un apagón 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 superan el límite de credenciales; los límites de tasa perimetrales y las alarmas de escritura/aceleración de DynamoDB deben, por 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 registrada en su ledger de despliegue y en el issue #1306 antes de la promoción. Los integradores directos de createHttpRuntime que inyecten una implementación de almacén de credenciales deben declarar igualmente credentialRateLimitStore: "dynamodb" para el modo stateless no loopback. La interfaz de inyección genérica no puede demostrar 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, de modo que ninguna solicitud pueda 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 finalizar, por lo que el backlog puede acercarse transitoriamente al doble de esa cifra, pero permanece acotado. Mientras la puerta de admisión está cerrada, las nuevas solicitudes fallan con 503 e incrementan McpConcurrencyRejected en lugar de hacer crecer la memoria de teardown sin límite. Ese contador representa intencionalmente el fallo de admisión por saturación de solicitudes activas o por contrapresión de teardown. El autoescalado debe usar únicamente McpConcurrencyUtilization; el contador de rechazos merece atención de página, y una 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 publicFileRateLimitPerMinute, aislados del tráfico legal/páginas de video y entre sí. Mantenga la frecuencia del balanceador de carga, de las sondas de liveness y de 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 mencionados. 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_CONFIG
  • QURL_MCP_HTTP_CONFIG

QURL_MCP_HTTP_CONFIG nunca reemplaza la ruta de configuración de runtime compartida. Esto evita que los ajustes del listener oculten silenciosamente los ajustes de SMTP, conectores o 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 comprometa claves de API, credenciales SMTP ni rutas privadas del sistema de archivos.

Rutas HTTP

Después de iniciar en modo http, las rutas comunes son:

RutaPropósito
/mcpEndpoint MCP remoto principal
/healthzEndpoint de verificación de salud
/legal/privacyPágina pública de política de privacidad
/legal/termsPágina pública de términos de servicio
publicVideo.pagePathPágina pública de reproducción de video
publicVideo.pagePath + /fileEndpoint de streaming MP4

/healthz está intencionalmente sin autenticación y sin validación de Host para cada llamada, 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 rutas legales/video. Un 429 desde 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. Está registrada antes de la validación de Host porque las sondas de destino ALB usan la IP y el puerto de la tarea 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 leer el catálogo público de herramientas/recursos/prompts 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 ni 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. Las sesiones solo de introspección permanecen por 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—las llamadas rechazadas o limitadas por tasa no demuestran que la credencial sea válida. 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ónValor
URL del servidor MCPSu URL HTTPS pública más /mcp
AutenticaciónToken bearer
TokenLa 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

Verificaciones de Páginas Públicas

Verifique también las páginas legales y, cuando esté configurado, la página de video:

  • /legal/privacy
  • /legal/terms
  • la ruta pública de página de video 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 despliegue contenerizado.

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 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 modo HTTP localmente en Docker:

La imagen usa por defecto el punto de entrada stdio y el servidor HTTP usa por defecto loopback local al contenedor. Los despliegues HTTP deben anular el comando y vincularse a 0.0.0.0 con una lista de Host permitidos explícita:

docker run --rm -p 3000:3000 \
  -e MCP_HOST=0.0.0.0 \
  -e MCP_ALLOWED_HOSTS=127.0.0.1,localhost \
  qurl-mcp node dist/http.js

Para un único proxy inverso de producción de confianza, 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

ComandoPropósito
npm run buildCompilar TypeScript
npm testEjecutar pruebas
npm run test:coverageEjecutar cobertura obligatoria
npm run lintEjecutar ESLint
npm run devModo de observación de TypeScript
npm run formatFormatear código fuente
npm run format:checkVerificar formato
npm run startIniciar modo stdio
npm run start:httpIniciar modo HTTP

Orden de Despliegue Recomendado

  1. Copie y actualice los dos archivos de configuración de ejemplo
  2. Establezca las credenciales mediante variables de entorno
  3. Ejecute npm install
  4. Ejecute npm run build
  5. Ejecute npm run start:http
  6. Verifique /healthz
  7. Verifique que las solicitudes /mcp no autenticadas reciban 401
  8. Configure el proxy inverso HTTPS
  9. 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 variable Noto Sans SC de 17.8 MB para cobertura de glifos multilingüe sin conexión. Esto aumenta intencionalmente el tarball npm a aproximadamente 11.4 MB y el paquete descomprimido a aproximadamente 18.4 MB para todas las instalaciones, incluyendo despliegues que no habilitan flujos de trabajo PDF. Incluir la fuente en el paquete evita una dependencia de red en runtime y preserva un renderizado CJK predecible; los operadores que prioricen una instalación más pequeña pueden eliminar el activo y aceptar el fallback documentado de Helvetica con cobertura CJK limitada. Su licencia SIL Open Font y el aviso de copyright están incluidos en assets/fonts/OFL.txt.

Licencia

MIT -- LayerV AI