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 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
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 páginas legales públicas
- servir una página de reproducción de video MP4 configurable
Modos de ejecución
| Modo | Propósito | Comando de inicio | Caso de uso típico |
|---|---|---|---|
stdio | Servidor MCP local en subproceso | 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 qURL |
update_qurl | Actualizar metadatos o expiración de qURL |
mint_link | Acuñar un nuevo enlace de acceso para un recurso existente |
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 |
Herramientas de subida
| 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 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
| URI | Descripción |
|---|---|
qurl://links | Lista actual de qURL |
qurl://usage | Información actual de cuota y uso |
Prompts MCP
| Prompt | Descripción |
|---|---|
secure_a_service | Prompt de integración segura de servicios |
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 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:
| Archivo | Propósito |
|---|---|
qurl-mcp.config.json | Configuración de ejecución compartida utilizada por ambos modos stdio y http |
qurl-mcp.http.json | Configuración de escucha del servidor y acceso público solo HTTP |
Referencia de qurl-mcp.config.json
Configuración central compartida
| Campo | Propósito |
|---|---|
maxUploadFileDataBytes | Limita las subidas decodificadas y de archivos locales (predeterminado 10mb) |
defaultQurlApiUrl | URL base de la API backend de qURL |
defaultQurlConnectorUrl | URL 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 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 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
| 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 aplicación |
smtp.fromEmail | Dirección de correo 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 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 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
| Campo | Propósito |
|---|---|
publicVideo.title | Título mostrado en la página pública de video |
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 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.
| Campo | Propósito |
|---|---|
port | Puerto de escucha HTTP MCP |
host | Dirección de enlace HTTP MCP |
baseUrl | URL base pública del servicio |
allowedHosts | Lista de permitidos de hosts para la validación del encabezado Host |
trustProxyHops | Conteo exacto de saltos de proxy inverso de confianza (predeterminado: 0) |
stateless | Transporte HTTP con ámbito de solicitud sin afinidad de sesión (predeterminado: false) |
maxConcurrentRequests | Límite de concurrencia POST/parser 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 a la API qURL descendente (predeterminado: 100) |
sessionIdleTtlMs | Ventana de expulsión por inactividad de sesiones conectadas (predeterminado: 15 minutos) |
sessionAbsoluteTtlMs | Vida útil absoluta de la sesión, incluidas las solicitudes activas SSE/herramientas (predeterminado: 24 horas) |
unvalidatedSessionTtlMs | Plazo absoluto de validación 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_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 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_CONFIGQURL_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:
| 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 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ó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
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
| Comando | Propósito |
|---|---|
npm run build | Compilar TypeScript |
npm test | Ejecutar pruebas |
npm run test:coverage | Ejecutar cobertura obligatoria |
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 mediante 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 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