qURL
qURL é o portal para a internet invisível — URLs com expiração e escopo limitado que permitem que agentes de IA acessem serviços que ninguém mais pode ver.
Documentação
@layervai/qurl-mcp
⚠️ Renomeado de
@layerv/qurl-mcpna v0.4.0. O pacote antigo está obsoleto e não receberá mais atualizações. Se você estiver usando@layerv/qurl-mcp@0.3.x, troque o escopo na configuração do seu cliente MCP — mesmo binário, mesma chave de API, nenhuma outra alteração.
Um servidor MCP qURL que suporta tanto o modo
stdiolocal quanto o modoHTTPremoto para criar, gerenciar, resolver e compartilhar links de acesso seguros.
Visão Geral
qURL MCP expõe os recursos do qURL para clientes MCP, GPTs, ChatGPT e outras integrações remotas.
Atualmente, ele suporta:
- criar, ler, atualizar e excluir qURLs
- resolver tokens de acesso
- gerenciar tokens e sessões qURL
- enviar conteúdo de texto ou arquivo e gerar qURLs
- servir páginas legais públicas
- servir uma página de reprodução de vídeo MP4 configurável
Modos de Execução
| Modo | Finalidade | Comando de Início | Caso de Uso Típico |
|---|---|---|---|
stdio | Servidor MCP local em subprocesso | npm run start | Claude Desktop, Cursor, Codex e outros clientes MCP locais |
http | Servidor MCP remoto autenticado | npm run start:http | Ambientes de execução de agentes remotos atrás de HTTPS |
Mapa de Recursos
Ferramentas de Gerenciamento qURL
| Ferramenta | Descrição |
|---|---|
create_qurl | Criar um novo qURL |
resolve_qurl | Resolver um token de acesso em uma URL de destino protegida |
list_qurls | Listar recursos qURL |
get_qurl | Buscar detalhes de um único qURL |
delete_qurl | Excluir um qURL |
extend_qurl | Estender a expiração do qURL |
update_qurl | Atualizar metadados ou expiração do qURL |
mint_link | Gerar um novo link de acesso para um recurso existente |
batch_create_qurls | Criar vários qURLs em uma única solicitação |
revoke_qurl_token | Revogar um token específico |
update_qurl_token | Atualizar um token específico |
list_qurl_sessions | Listar sessões de acesso ativas |
terminate_qurl_sessions | Encerrar uma ou todas as sessões ativas |
Ferramentas de Upload
| Ferramenta | Modo | Descrição |
|---|---|---|
upload_file_qurl | stdio | Enviar um arquivo local e gerar um qURL |
upload_file_data_qurl | stdio/HTTP | Enviar conteúdo de arquivo em base64 e gerar um qURL |
upload_text_qurl | stdio/HTTP | Enviar conteúdo de texto e gerar um qURL |
upload_file_qurl é intencionalmente somente stdio. Ele pode ler qualquer
PDF/imagem suportado que o usuário do processo MCP local possa acessar, portanto, os agentes devem invocá-lo
apenas para um caminho que o usuário selecionou explicitamente para compartilhamento. Não o exponha
a prompts não confiáveis ou agentes autônomos: a injeção de prompt poderia, de outra forma,
selecionar outro PDF/imagem legível no host. Execute o stdio sob uma conta de sistema operacional
cujo acesso ao sistema de arquivos seja limitado ao conteúdo pretendido para compartilhamento. O modo HTTP
nunca registra esta ferramenta de arquivo do host.
As ferramentas de byte/texto também estão disponíveis no stdio para que clientes locais possam compartilhar
anexos no chat sem primeiro materializá-los em um caminho conhecido do host.
O upload do conector e a geração do qURL são operações separadas. Se a geração falhar
após o upload, o conector atualmente não possui endpoint de exclusão; o servidor registra
o resource_id órfão para limpeza pelo operador e retorna a falha de geração.
As tentativas de upload HTTP permanecem limitadas pelos limites de taxa MCP por IP e por credencial;
os operadores de stdio devem restringir separadamente os loops de repetição autônomos.
A validação de upload vincula o tipo de mídia declarado ao nome do arquivo, além dos marcadores
de início/fim de formato; não é um scanner de malware nem um decodificador completo de PDF/imagem.
Para resistência a poliglotas, o marcador final %%EOF de um PDF deve ser seguido apenas por
espaço em branco ASCII; a saída do produtor com outros bytes finais é rejeitada, mesmo
se um leitor de PDF permissivo a aceitasse. A validação JPEG verifica o enquadramento e os
marcadores terminais, em vez de decodificar segmentos de imagem. O conector
autenticado deve decodificar independentemente ou validar completamente o conteúdo antes do
armazenamento quando a validade semântica da mídia for importante. Ele também deve preservar o
tipo de mídia seguro declarado e servir os downloads com X-Content-Type-Options: nosniff, em vez de inferir um tipo executável.
Não há intencionalmente uma lista de permissões de caminho em nível de aplicativo: symlinks e
condições de corrida de tempo de verificação/tempo de uso tornam uma verificação de prefixo lexical um limite
de segurança enganoso. Use uma conta de sistema operacional dedicada, contêiner ou montagem somente leitura
cujos arquivos legíveis já estejam limitados ao diretório de compartilhamento pretendido.
O componente final do caminho é aberto com O_NOFOLLOW; symlinks de diretórios
intermediários mantêm o comportamento normal do sistema de arquivos sob este limite de usuário local confiável.
Recursos MCP
| URI | Descrição |
|---|---|
qurl://links | Lista atual de qURLs |
qurl://usage | Informações atuais de cota e uso |
Prompts MCP
| Prompt | Descrição |
|---|---|
secure_a_service | Prompt de integração segura de serviço |
audit_links | Prompt de auditoria de links |
rotate_access | Prompt de rotação de acesso |
Início Rápido
1. Instalar Dependências
npm install
Para uma instalação local de fonte somente stdio, use npm install --omit=optional; isso
omite o AWS SDK. Implantações HTTP que usam a cota de credenciais DynamoDB devem usar
a instalação comum para que o SDK opcional seja empacotado.
2. Compilar
npm run build
3. Iniciar
Modo stdio local:
npm run start
Modo HTTP remoto:
npm run start:http
Exemplo de Cliente MCP
Se você quiser usar este servidor no modo stdio com um cliente MCP local:
{
"mcpServers": {
"qurl": {
"command": "npx",
"args": ["@layervai/qurl-mcp"],
"env": { "QURL_API_KEY": "lv_live_xxx" }
}
}
}
Arquivos de Configuração
Copie os exemplos rastreados para criar arquivos de configuração locais:
cp qurl-mcp.config.example.json qurl-mcp.config.json
cp qurl-mcp.http.example.json qurl-mcp.http.json
Os arquivos locais são ignorados pelo git para que credenciais e caminhos específicos da máquina não sejam commitados.
Suas responsabilidades são:
| Arquivo | Finalidade |
|---|---|
qurl-mcp.config.json | Configuração de tempo de execução compartilhada usada pelos modos stdio e http |
qurl-mcp.http.json | Configuração de listener do servidor e acesso público somente HTTP |
Referência do qurl-mcp.config.json
Configurações Centrais Compartilhadas
| Campo | Finalidade |
|---|---|
maxUploadFileDataBytes | Limita uploads decodificados e de arquivos locais (padrão 10mb) |
defaultQurlApiUrl | URL base da API de backend qURL |
defaultQurlConnectorUrl | URL base do conector de upload |
As configurações compartilhadas têm estas substituições de ambiente. Os valores de ambiente têm precedência sobre o arquivo de configuração compartilhado. O processo armazena em cache as configurações compartilhadas resolvidas, mas invalida automaticamente esse cache quando os metadados do arquivo ou qualquer valor de ambiente relevante mudam.
| Variável de ambiente | Campo de configuração |
|---|---|
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 é intencionalmente somente ambiente e não possui campo no arquivo de configuração.
Prefira QURL_SMTP_PASSWORD para o segredo SMTP também. Se smtp.password for
armazenado no arquivo de configuração em um host POSIX, restrinja esse arquivo a permissões
somente do proprietário (por exemplo, chmod 600); a inicialização avisa quando os bits de leitura de grupo/outros estão
presentes. Esta verificação é intencionalmente consultiva para que implantações existentes não
falhem após uma atualização, e é ignorada no Windows porque os bits de modo POSIX
não estão disponíveis lá.
A entrega de e-mail em si é de falha fechada, a menos que pelo menos uma entrada exata
smtp.allowedRecipients ou smtp.allowedRecipientDomains esteja
configurada; a inicialização avisa quando credenciais SMTP completas não possuem essa política.
Aumentar maxUploadFileDataBytes também aumenta o teto de memória por solicitação do analisador JSON HTTP
para aproximadamente 1,5 vezes esse valor (até cerca de 150 MB no
máximo de 100 MB), antes que a decodificação base64 aplique o limite exato de bytes. Até que uma
sessão tenha concluído uma chamada de API qURL downstream bem-sucedida, seu teto de analisador
permanece na configuração padrão menor de upload de 10 MB; clientes configurados para um
primeiro upload maior devem validar a sessão com uma pequena chamada de API qURL primeiro.
Dimensione o máximo configurado e o limite de concorrência do proxy reverso juntos.
Defina QURL_API_KEY no ambiente para o modo stdio. No modo HTTP, cada
solicitação de cliente fornece sua própria chave de API qURL como um token bearer.
defaultQurlApiUrl e QURL_API_URL exigem HTTPS para hosts que não sejam de loopback
porque as chaves e dados da API qURL são enviados como bearer para esse destino. HTTP simples
é aceito apenas para endpoints de desenvolvimento literal de loopback. As URLs do conector de upload
seguem a mesma regra de HTTPS-exceto-loopback.
Loopback significa 127.0.0.0/8 ou ::1; endereços de bind curinga, como
0.0.0.0 e ::, são intencionalmente não aceitos como destinos HTTP de saída.
Os destinos do conector são configuração confiável do operador, em vez de entrada do chamador;
endereços privados e resolução de DNS são, portanto, permitidos. Fixe o
nome do host do conector no DNS de implantação e não o aponte para serviços de metadados.
A credencial bearer qURL do chamador é encaminhada para este host, portanto, trate o controle
de URL e DNS do conector como parte do limite de confiança da credencial.
Configure a URL base do serviço do conector, não uma rota de upload: o qurl-mcp anexa
/api/upload a caminhos base comuns, aceita esse sufixo exato de endpoint e
rejeita caminhos ambíguos semelhantes a upload, como /upload ou /api/upload/v2.
O servidor MCP executa verificações limitadas de enquadramento de arquivo, não análise completa de mídia;
o conector deve revalidar independentemente o conteúdo enviado antes do armazenamento ou
serviço, e a entrega deve manter o comportamento nosniff como o limite de tipo autoritativo.
URLs base de API e de conectores que contenham credenciais embutidas, uma string de consulta
ou um fragmento agora são rejeitadas durante a inicialização. Implantações que anteriormente usavam
uma dessas formas incomuns de URL devem mover as credenciais para QURL_API_KEY e manter
a URL do serviço configurada em sua origem e prefixo de caminho opcional.
Configurações de SMTP
| Campo | Finalidade |
|---|---|
smtp.host | Hostname do servidor SMTP |
smtp.port | Porta do servidor SMTP |
smtp.secure | true para TLS implícito; false para STARTTLS obrigatório |
smtp.username | Nome de usuário de login SMTP |
smtp.password | Senha de login SMTP ou código específico do aplicativo |
smtp.fromEmail | Endereço de e-mail do remetente |
smtp.fromName | Nome de exibição do remetente |
smtp.allowedRecipients | Lista de permissões opcional de endereços exatos |
smtp.allowedRecipientDomains | Lista de permissões opcional de domínios exatos (subdomínios não são incluídos) |
smtp.maxRecipientsPerMessage | Limite de destinatários por mensagem (padrão 10) |
smtp.maxRecipientsPerHour | Limite de destinatários tentados por chave qURL por janela horária fixa (padrão 100) |
Estas configurações são usadas quando a entrega de e-mail é solicitada por ferramentas como:
create_qurlmint_linkupload_text_qurlupload_file_qurlupload_file_data_qurl
Se qualquer lista de permissões de destinatários estiver configurada, apenas um endereço ou domínio exato
correspondente será entregue. Se ambas estiverem vazias, os limites de mensagem e horário ainda se aplicam.
As entradas de domínio são exatas: example.com não permite implicitamente
mail.example.com; liste cada subdomínio permitido explicitamente.
Endereços e domínios são normalizados para minúsculas NFC/IDNA ASCII e um
ponto final de raiz DNS é removido antes da comparação e entrega.
Cada lista de permissões de destinatários é limitada a 1.000 entradas configuradas. O
limite de destinatários por mensagem se aplica ao fan-out único solicitado completo
antes da filtragem da lista de permissões, portanto, endereços bloqueados não podem ser usados para enviar um
lote superdimensionado.
No modo HTTP, qualquer chamador com uma chave de API qURL válida pode solicitar uma entrega SMTP
no lado do servidor. Configure allowedRecipients ou allowedRecipientDomains
antes de habilitar SMTP em uma implantação HTTP voltada para a Internet; listas de permissões vazias
permitem a entrega a qualquer endereço sintaticamente válido sujeito às cotas.
O transporte SMTP usa timeouts limitados de conexão/socket e é fechado após
cada lote de entrega. Tentativas SMTP com falha ainda consomem cota—incluindo quando uma
interrupção transitória resulta em zero mensagens entregues—portanto, falhas repetidas não podem
contornar o limite de abuso.
Cada solicitação de entrega também tem um prazo agregado de 60 segundos. Destinatários não
iniciados antes desse prazo são relatados como ignorados; filas no lado do provedor são
o caminho suportado para fan-out maior ou mais lento.
A criptografia de transporte é obrigatória: smtp.secure: true usa TLS implícito,
enquanto smtp.secure: false exige uma atualização STARTTLS bem-sucedida. A porta 465 é
reservada para TLS implícito e, portanto, exige smtp.secure: true.
O estado da cota horária é mantido por processo do servidor: ele é redefinido na reinicialização e
não é compartilhado entre réplicas. Operadores que executam várias instâncias devem
aplicar um limite agregado correspondente no provedor ou gateway SMTP.
A cota no processo é, portanto, uma proteção contra abuso, não um limite global durável
de segurança; o comportamento fail-open de reinicialização/escala horizontal deve ser coberto por esse
limite no lado do provedor.
O rastreamento falha fechado para novos principais após 10.000 principais serem retidos
em um processo; os principais existentes continuam usando seus buckets atuais até que
as entradas expiradas sejam removidas.
Restrinja a emissão de chaves de API qURL e monitore rejeições de limite de cota de novos principais:
alternar muitas chaves válidas pode deliberadamente manter essa tabela compartilhada na capacidade por
até uma janela de cota.
A cota usa uma janela fixa de uma hora que começa com a primeira tentativa de entrega
após a expiração da janela anterior.
Como em qualquer janela fixa, o tráfego imediatamente antes e depois de um limite pode
totalizar quase o dobro do valor horário configurado; use um limite deslizante ou contínuo
no lado do provedor quando esse pico de limite precisar ser evitado entre réplicas.
Os links qURL gerados são incluídos no corpo do e-mail em texto simples. Restrinja os
destinatários com as listas de permissões SMTP e configure a criptografia de transporte no
servidor/provedor SMTP quando a confidencialidade do link for importante.
Prefira variáveis de ambiente para credenciais e 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, e
QURL_SMTP_MAX_RECIPIENTS_PER_HOUR.
Configurações da Página Pública de Vídeo
| Campo | Finalidade |
|---|---|
publicVideo.title | Título exibido na página pública de vídeo |
publicVideo.pagePath | Caminho público da página de reprodução de vídeo |
publicVideo.filePath | Caminho absoluto do servidor para o arquivo MP4 |
Quando configurado, o servidor HTTP também expõe:
- uma página pública de reprodução de vídeo
- um endpoint de streaming para o arquivo MP4
publicVideo.filePath é configuração confiável do operador. O componente final
deve ser um arquivo .mp4 regular sem symlink; symlinks de diretórios intermediários mantêm
a resolução normal do sistema de arquivos e, portanto, devem permanecer sob controle do operador.
Na inicialização, este ativo opcional é verificado e um aviso é emitido quando está ausente, vazio ou não
regular, mas intencionalmente mantém o serviço MCP e /healthz disponíveis. A
rota do arquivo de vídeo ainda falha fechada com 404 até que o ativo seja corrigido.
Referência do qurl-mcp.http.json
Use qurl-mcp.http.example.json para desenvolvimento local,
com estado. qurl-mcp.http.stateless.example.json
mostra todos os campos de armazenamento e métricas exigidos por um serviço sem estado implantado.
| Campo | Finalidade |
|---|---|
port | Porta do listener HTTP MCP |
host | Endereço de bind HTTP MCP |
baseUrl | URL base pública do serviço |
allowedHosts | Lista de permissões de hosts para validação do cabeçalho Host |
trustProxyHops | Contagem exata de saltos de proxy reverso confiável (padrão 0) |
stateless | Transporte HTTP com escopo de solicitação sem afinidade de sessão (padrão false) |
maxConcurrentRequests | Limite de concorrência de POST/parser somente sem estado por processo (padrão 20) |
credentialRateLimitStore | Backend do contador de credenciais: memory ou dynamodb (padrão memory) |
rateLimitDynamoDbTable | Tabela DynamoDB usada pelo contador de credenciais compartilhado |
metricsNamespace | Namespace CloudWatch EMF para métricas de saturação sem estado |
metricsService | Dimensão de Serviço CloudWatch EMF estável |
metricsEnvironment | Dimensão de Ambiente CloudWatch EMF estável |
maxSessions | Limite rígido de sessões MCP ativas (padrão 1000) |
maxSessionsPerCredential | Limite de sessões ativas e inicializando por portador (padrão 20) |
maxUnvalidatedSessions | Limite de sessões que não concluíram uma chamada de API qURL downstream (padrão 100) |
sessionIdleTtlMs | Janela de evicção por inatividade de sessão conectada (padrão 15 minutos) |
sessionAbsoluteTtlMs | Vida útil absoluta da sessão, incluindo solicitações SSE/ferramentas ativas (padrão 24 horas) |
unvalidatedSessionTtlMs | Prazo absoluto de validação para sessões de portador nunca validadas (padrão 1 minuto) |
mcpRateLimitPerMinute | Limite de solicitações /mcp por cliente (padrão 120) |
publicFileRateLimitPerMinute | Limite de solicitações de rota pública por cliente (padrão 300) |
Os campos HTTP têm substituições de ambiente correspondentes:
| Variável de ambiente | Campo de configuração |
|---|---|
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 (compartilhado) |
O listener padrão é 127.0.0.1. Um host que não seja de loopback é rejeitado, a menos que | |
allowedHosts esteja explicitamente configurado. Defina trustProxyHops (ou | |
MCP_TRUST_PROXY_HOPS) para o número exato de saltos de proxy confiáveis; deixe-o em | |
0 para conexões diretas, para que cabeçalhos de IP encaminhado não possam falsificar chaves de rate-limit. | |
| A allowlist de Hosts é limitada a 1.000 entradas para que a validação em tempo de solicitação permaneça | |
| delimitada mesmo sob configuração operacional patológica. | |
/mcp aplica a permissão de solicitação configurada de forma independente tanto ao | |
| IP do cliente quanto ao digest SHA-256 do bearer autenticado. O armazenamento em memória | |
| é local ao processo; o armazenamento DynamoDB usa um contador atômico de janela fixa | |
| chaveado pelo digest da credencial e pelo minuto UTC. Ele nunca armazena o bearer. Como em qualquer | |
| janela fixa, solicitações em torno de um limite de minuto podem totalizar quase o dobro da | |
| permissão configurada. O contrato da tabela é uma chave de partição string chamada | |
rate_key; a atualização atômica grava um contador numérico request_count e um | |
timestamp numérico expires_at de TTL. Habilite o TTL do DynamoDB em expires_at para que | |
| linhas expiradas não se acumulem; o TTL apenas agenda limpeza assíncrona, e | |
| o minuto na chave—não a exclusão física—reinicia a janela ativa. A função | |
de tarefa requer dynamodb:DescribeTable para inicialização e dynamodb:UpdateItem no | |
| caminho de solicitação. Use capacidade sob demanda ou provisione capacidade de escrita suficiente para | |
a taxa esperada do fleet; throttling falha de forma fechada com 503 e nunca cai | |
| para memória. O cliente usa modo de retry padrão com no máximo duas tentativas, | |
| um timeout de conexão de um segundo e um timeout de solicitação de dois segundos que lança exceção; | |
| esses limites explícitos limitam por quanto tempo uma solicitação segura uma permissão de concorrência | |
| durante uma falha parcial do armazenamento. A dependência opcional do AWS SDK é fixada | |
| em versão exata de nível superior, enquanto o package lock commitado fixa seu grafo | |
transitivo @aws-sdk/* e @smithy/*. Qualquer atualização do SDK deve atualizar o lockfile e | |
manter o teste de regressão de materialização de timeout real-NodeHttpHandler verde. | |
| A dependência é carregada somente quando o armazenamento DynamoDB é selecionado, então | |
consumidores somente stdio podem instalar com --omit=optional. Imagens HTTP implantadas | |
| devem incluir dependências opcionais; a inicialização falha antes de escutar se o SDK | |
| estiver ausente ou expuser uma superfície de runtime incompatível. O cliente usa a | |
cadeia padrão de provedor de credenciais AWS_REGION; implantações ECS | |
| normalmente obtêm ambas do ambiente da tarefa e da função de tarefa. Implantações | |
| com proxy reverso devem definir a contagem de saltos correta, ou todos os chamadores atrás do proxy | |
| compartilharão o único bucket de IP do proxy. Somente a cota de credenciais DynamoDB é | |
| fleet-wide: o limitador de IP é local ao processo, então sua permissão efetiva de fleet | |
| multiplica com a contagem de tarefas e deve ser respaldada por um limite de borda compartilhado. A | |
| implantação gerenciada no qurl-integrations-infra PR #1305 aplica tanto um | |
limite WAF por IP de origem quanto um teto agregado de fleet /mcp menor, com | |
| prova de headroom ao vivo rastreada na issue #1306. O bucket de credenciais também impede que uma | |
| chave contorne a permissão de solicitação rotacionando IPs de origem, enquanto | |
maxSessionsPerCredential impede que ela ocupe todo o pool de sessões. | |
| Cada valor de bearer distinto retém uma entrada de bucket de credenciais para a janela | |
| atual de um minuto. O limitador de IP executa primeiro, então a rotação de token de uma única fonte | |
não pode criar entradas mais rápido que mcpRateLimitPerMinute; tráfego | |
| distribuído hostil ainda requer o limite de borda compartilhado documentado. O bucket de IP é o | |
| principal controle em processo contra rotação arbitrária de bearer porque | |
| strings de bearer não validadas distintas necessariamente ocupam buckets de credenciais distintos. | |
| No modo stateful, orce a memória do parser de sessões pendentes como | |
maxUnvalidatedSessions vezes aproximadamente | |
1,5 vezes o menor entre maxUploadFileDataBytes e 10 MB (mais cerca de 64 KiB | |
| por solicitação). Nos padrões, o teto teórico de concorrência é de cerca de | |
1,5 GiB. Reduza maxUnvalidatedSessions e o limite de concorrência de borda compartilhado | |
| juntos quando a implantação tiver um orçamento de memória menor. | |
| Credenciais de bearer são validadas conclusivamente pela primeira chamada bem-sucedida | |
| à API qURL downstream. Até lá, as sessões usam o teto menor de sessões pendentes | |
| e o prazo de validação de um minuto, então strings de bearer não vazias arbitrárias | |
| não podem ocupar todo o pool de sessões pelo TTL normal de 15 minutos. Um cliente que | |
| executa apenas introspecção MCP permanece pendente por design; após a expulsão | |
| por prazo, ele deve reinicializar antes da próxima solicitação. Os tetos de sessão e o | |
| prazo de validação são configuráveis para clientes com lacunas maiores entre | |
| introspecção e chamada de ferramenta. O prazo é absoluto e se aplica independentemente | |
| da atividade, incluindo um stream SSE aberto ou uma primeira chamada de ferramenta de longa duração. | |
Clientes validados que desconectam sem enviar DELETE /mcp retêm seu | |
| slot de sessão limitado por um período de graça de reconexão de 30 segundos. Uma reconexão limpa | |
| esse prazo; caso contrário, a sessão é removida sem esperar pelo TTL | |
ocioso mais longo. Dimensione maxSessions e o TTL ocioso para clientes que permanecem conectados | |
| mas não executam encerramento explícito de sessão. | |
Sessões validadas também expiram em sessionAbsoluteTtlMs (24 horas por padrão), | |
| mesmo durante um stream SSE ativo ou solicitação de ferramenta. Isso impede que keepalives | |
| fixem um slot de sessão global ou por credencial indefinidamente. | |
| A primeira operação qURL downstream deve, portanto, ser concluída antes desse | |
| prazo; uma primeira chamada de API incomumente lenta pode ser interrompida e o cliente | |
| deve reinicializar. Esse comportamento de falha fechada impede que uma credencial inválida | |
| estenda seu slot pendente com uma solicitação deliberadamente de longa duração. | |
| Aceitar um bearer não vazio durante a inicialização MCP é intencional: mantém | |
| a introspecção de protocolo disponível antes da primeira operação qURL, enquanto o | |
| teto global de sessão, o teto de sessão por credencial, o teto de sessões pendentes, o prazo | |
| absoluto e o limite de taxa de solicitação limitam o uso de slots por chaves inválidas. O | |
| middleware MCP não valida a chave em si; somente uma resposta bem-sucedida da API qURL | |
| downstream promove a sessão. | |
| Erros downstream, incluindo respostas não-2xx que parecem autenticadas, não a | |
| promovem porque um intermediário pode tê-las gerado antes que a API qURL | |
| autenticasse o bearer. | |
| A promoção, portanto, assume que o endpoint HTTPS configurado da API qURL e cada | |
| intermediário confiável não armazenam em cache nem sintetizam respostas de sucesso autenticadas. | |
| Proxies reversos nesse caminho devem encaminhar autorização e desabilitar o cache de | |
| respostas para tráfego da API qURL. | |
| Consequentemente, qualquer chamador com um bearer não vazio pode enumerar o catálogo | |
| público de ferramentas/recursos/prompts e manter brevemente estado de sessão pendente limitado. Em | |
| redes hostis, coloque implantações não-loopback atrás de um proxy ciente de identidade | |
que preserve a credencial de bearer qURL do chamador para autorização /mcp. | |
| Inicialização e listagem de catálogo retornam apenas metadados estáticos de propriedade do servidor; | |
| eles não invocam handlers de ferramentas/recursos/prompts, leem arquivos do host, contatam a | |
| API qURL ou o conector, nem enviam email. Chamadas de handler dependem da API qURL | |
| configurada para autenticar o bearer encaminhado antes de retornar dados ou aplicar uma | |
| operação. O conector configurado é uma segunda autoridade de credencial: ele deve | |
| autenticar o bearer qURL encaminhado antes de aceitar ou armazenar bytes de upload. | |
| Implantar um conector não autenticado não é suportado porque permitiria que um | |
| chamador MCP não validado criasse estado no lado do conector. |
O modo stateful é o padrão de compatibilidade e retém o registro de sessão MCP existente,
GET SSE, comportamento DELETE explícito e cobrança de cota de credencial local ao processo
para todos os três métodos MCP. O modo stateless cria e fecha um servidor e
transporte para cada POST, ignora mcp-session-id e retorna
respostas 405 em formato JSON-RPC para GET e DELETE. É o modo
exigido atrás de um load balancer ou serviço de autoscaling porque nenhuma solicitação depende
de afinidade local ao processo. A permissão de concorrência é adquirida antes do parsing
JSON e liberada em cada caminho de resposta/erro/desconexão. O modo stateless
usa o teto de parser maxUploadFileDataBytes configurado diretamente porque
a permissão de concorrência pré-parsing fornece seu limite de amplificação de memória.
Orce aproximadamente maxConcurrentRequests vezes (1,5 vezes
maxUploadFileDataBytes mais 64 KiB) por processo; a concorrência padrão no
teto de upload de 100 MB é de aproximadamente 3 GiB antes do trabalho downstream.
A inicialização stateless rejeita configurações cujo orçamento conservador de parser
exceda 4 GiB. Reduza ainda mais qualquer uma das configurações quando a tarefa ECS tiver um
limite de memória menor. Em contraste, sessões stateful acima do teto padrão devem
primeiro concluir uma chamada bem-sucedida à API qURL downstream.
Em redes hostis, um limite de tamanho de solicitação de borda autenticado não maior que
o teto de parser configurado é um requisito de implantação: a permissão limita
a memória agregada, mas um bearer não vazio não é validado de forma autoritativa até
que a operação parseada alcance a API qURL downstream.
O listener stateless limita o recebimento de cabeçalhos a 15 segundos e tanto o
recebimento completo da solicitação quanto o tempo de vida do socket ocioso a 120 segundos. Uma permissão
de concorrência abrange do parsing até a conclusão da resposta, então clientes travados não podem reter
todo o pool de permissões indefinidamente. Uma chamada de ferramenta que não produz tráfego de socket
por 120 segundos é intencionalmente abortada; integrações que precisam de operações silenciosas
mais longas devem mover esse trabalho para uma API assíncrona em vez de aumentar
esse limite de retenção fleet-wide.
O modo stateless implantado (não-loopback) exige o armazenamento de credenciais DynamoDB
e todos os três campos estáveis de identidade de métricas. Ele emite um heartbeat EMF de 30 segundos:
McpConcurrencyUtilization é a utilização máxima de permissão observada durante o
intervalo na admissão de requisições e no heartbeat (incluindo requisições que iniciam e
terminam entre heartbeats), enquanto
McpConcurrencyRejected e McpRateLimitStoreErrors são deltas de intervalo
do tipo snapshot-and-zero que incluem zeros explícitos. Limites de sessão e cotas de destinatários de e-mail
permanecem em memória; a cota de credenciais DynamoDB é abrangente para todo o fleet e conta
cada POST HTTP autenticado, incluindo inicialização, descoberta e chamadas de
ferramentas. Dimensione essa cota para o padrão completo de requisições esperado, em vez de
apenas chamadas de ferramentas. A permissão também abrange o incremento DynamoDB limitado: durante
uma queda de energia do armazenamento, cada requisição admitida pode reter uma permissão por aproximadamente
quatro segundos (duas tentativas de dois segundos) antes de falhar de forma fechada, enquanto requisições
excedentes recebem um 503 de concorrência rápido. O contador de janela fixa incrementa a cada
tentativa, incluindo
tentativas que já estão acima do limite de credenciais; limites de taxa de borda e alarmes de gravação/limite
do DynamoDB devem, portanto, limitar a amplificação abusiva de gravação.
Os proprietários da implantação devem tornar tanto os alarmes quanto uma sonda de amplificação de gravação
acima do limite portões rígidos de promoção, em vez de tratá-los como observabilidade opcional.
A implantação gerenciada em
qurl-integrations-infra#1305
provisiona esses alarmes, com prova ao vivo rastreada em seu registro de rollout e
issue #1306 antes da promoção.
Integradores diretos de createHttpRuntime que injetam uma implementação de armazenamento de credenciais
ainda devem declarar credentialRateLimitStore: "dynamodb" para
o modo stateless não-loopback. A interface de injeção genérica não pode provar que um
backend personalizado é compartilhado entre réplicas, então a injeção deliberadamente não é uma
saída de emergência do contrato implantado.
Os campos de identidade de métricas são rejeitados no modo stateful para que o medidor de concorrência
não possa relatar silenciosamente um zero enganoso.
Cada POST stateless possui um novo servidor MCP e transporte, de modo que nenhuma requisição possa
herdar o estado do handler de outra credencial. O encerramento da resposta concluída é
rastreado de forma assíncrona. A admissão é interrompida quando esse backlog atinge
maxConcurrentRequests; requisições já em andamento podem então terminar, então o
backlog pode transitoriamente se aproximar do dobro dessa contagem, mas permanece limitado. Enquanto
o guarda de admissão está fechado, novas requisições falham com 503 e incrementam
McpConcurrencyRejected em vez de aumentar a memória de encerramento sem limite.
Esse contador representa intencionalmente falha de admissão por saturação de requisições ativas
ou pressão de backlog de encerramento. O autoscaling deve usar
McpConcurrencyUtilization sozinho; o contador de rejeições permanece digno de página,
e baixa utilização junto com rejeições identifica atraso de encerramento.
Agrupar esses objetos enfraqueceria o isolamento de requisições e deliberadamente não é uma
otimização de desempenho sem pressão de registro medida.
/healthz e o endpoint público de arquivos de vídeo usam cada um seu próprio
bucket publicFileRateLimitPerMinute, isolados do tráfego legal/página de vídeo
e um do outro. Mantenha o balanceador de carga, a sonda de liveness e a frequência esperada de
requisições de intervalo de vídeo abaixo dessa permissão por IP de origem (300
requisições/minuto por padrão), ou aumente-a para clientes excepcionalmente agressivos.
Prioridade de Configuração
Por padrão, a configuração é carregada dos dois arquivos JSON locais acima. Se um
arquivo estiver ausente, os padrões integrados e as variáveis de ambiente são usados.
Caminhos de configuração relativos—incluindo os padrões—são resolvidos a partir do diretório
de trabalho do processo. Defina as variáveis de caminho explícitas abaixo quando um supervisor,
npx, ou um host MCP iniciar o servidor a partir de um diretório diferente.
As seguintes variáveis de ambiente substituem independentemente os caminhos dos arquivos de configuração:
QURL_MCP_CONFIGQURL_MCP_HTTP_CONFIG
QURL_MCP_HTTP_CONFIG nunca substitui o caminho de configuração de runtime compartilhado. Isso mantém
as configurações do listener de sombrear silenciosamente as configurações de SMTP, conector ou API.
server.json e smithery.yaml descrevem o transporte stdio publicado, então
eles incluem configurações compartilhadas de upload/SMTP, mas omitem intencionalmente variáveis
de listener somente HTTP, como QURL_MCP_HTTP_CONFIG e MCP_MAX_SESSIONS.
Não faça commit de chaves de API, credenciais SMTP ou caminhos privados de sistema de arquivos.
Rotas HTTP
Após iniciar no modo http, as rotas comuns são:
| Rota | Propósito |
|---|---|
/mcp | Endpoint MCP remoto principal |
/healthz | Endpoint de verificação de saúde |
/legal/privacy | Página pública de política de privacidade |
/legal/terms | Página pública de termos de serviço |
publicVideo.pagePath | Página pública de reprodução de vídeo |
publicVideo.pagePath + /file | Endpoint de streaming MP4 |
/healthz é intencionalmente não autenticado e sem validação de Host para todos os
chamadores, expõe apenas { "ok": true }, e usa o limite de requisições de rota pública
configurado em um bucket separado, de modo que as sondas de saúde não possam consumir a
permissão da rota legal/vídeo. Um 429 desta rota
significa que a fonte da sonda excedeu publicFileRateLimitPerMinute, não que a
aplicação falhou na verificação de liveness; mantenha a frequência da sonda abaixo desse limite.
Ela é registrada antes da validação de Host porque as sondas de destino do ALB usam o IP
e a porta da tarefa como Host; as rotas públicas de MCP e navegador permanecem com validação de Host.
Autenticação HTTP
O endpoint /mcp exige Authorization: Bearer <qURL API key> em cada
requisição. No modo stateful, o token de portador é vinculado à sessão MCP resultante,
de modo que um ID de sessão não pode ser reutilizado com uma credencial diferente. No
modo stateless, o portador permanece com escopo de requisição e é descartado quando a
resposta é fechada.
Limite de autenticação do operador: a inicialização aceita qualquer token de portador
não vazio e permite que o catálogo público de ferramentas/recursos/prompts seja lido
antes da validação autoritativa pela primeira chamada de API qURL downstream.
Esse catálogo é montado a partir de esquemas e descrições estáticos e não
inclui tokens de portador, credenciais SMTP ou outra configuração do operador.
Limites de sessão não validados, um prazo curto de validação e limites de taxa de requisição
limitam esse estado de pré-validação; o token fornecido é encaminhado apenas para a
API qURL configurada.
Sessões somente de introspecção, portanto, permanecem não validadas e são fechadas em
unvalidatedSessionTtlMs; os clientes podem reinicializar se precisarem de uma sessão de maior
duração. Uma sessão é promovida somente após uma chamada de API qURL bem-sucedida—chamadas
rejeitadas ou limitadas por taxa não provam que a credencial é válida. Sessões desconectadas
permanecem registradas por um período de graça de reconexão SSE de 30 segundos, enquanto
maxSessions e maxSessionsPerCredential limitam essa permissão sob rotatividade.
Requisições sem um cabeçalho Origin são aceitas para clientes MCP que não são de navegador.
Quando Origin está presente, ele deve corresponder à origem de baseUrl; valores malformados ou
de origem cruzada são rejeitados em /mcp. Rotas públicas de saúde, legais e de vídeo
configuradas não usam estado de origem de navegador e não são limitadas por esta verificação.
Configure clientes MCP remotos com:
| Configuração | Valor |
|---|---|
| URL do servidor MCP | Sua URL HTTPS pública mais /mcp |
| Autenticação | Token de portador |
| Token | A chave de API qURL do chamador |
Se um cliente suportar apenas descoberta OAuth, coloque um gateway compatível com OAuth
na frente deste servidor em vez de expor /mcp sem autenticação.
Como Verificar a Implantação
Verificações de Nível de Serviço
Comece com:
/healthz/mcp
Verificações de Páginas Públicas
Verifique também as páginas legais e, quando configurado, a página de vídeo:
/legal/privacy/legal/terms- o caminho da página pública de vídeo configurado
Verificação de Domínio
Se você planeja usar a Plataforma OpenAI, certifique-se de que o seguinte caminho de nível raiz exista:
/.well-known/openai-apps-challenge
Este arquivo de verificação deve estar sob o caminho raiz do domínio
.well-known, não sob/mcp.
Docker
O repositório inclui um Dockerfile para implantação em contêiner.
Exemplo:
docker build -t qurl-mcp .
docker run -i -e QURL_API_KEY=lv_live_xxx qurl-mcp
Se você implantar com Docker, certifique-se de que o contêiner ainda possa acessar os arquivos de configuração corretos, ou substitua os caminhos dos arquivos de configuração com variáveis de ambiente.
Execute o modo HTTP localmente no Docker:
A imagem usa como padrão o ponto de entrada stdio e o servidor HTTP usa como padrão o
loopback local do contêiner. Implantações HTTP devem substituir o comando e vincular
a 0.0.0.0 com uma lista de permissões de Host 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 um único proxy reverso de produção confiável, defina
MCP_TRUST_PROXY_HOPS=1, use a origem HTTPS pública em MCP_BASE_URL, e
defina MCP_ALLOWED_HOSTS para o hostname público. Não exponha o listener do contêiner
diretamente quando a confiança do proxy estiver habilitada.
Comandos Comuns
| Comando | Propósito |
|---|---|
npm run build | Compilar TypeScript |
npm test | Executar testes |
npm run test:coverage | Executar cobertura obrigatória |
npm run lint | Executar ESLint |
npm run dev | Modo de observação TypeScript |
npm run format | Formatar código-fonte |
npm run format:check | Verificar formatação |
npm run start | Iniciar modo stdio |
npm run start:http | Iniciar modo HTTP |
Ordem de Implantação Recomendada
- Copie e atualize os dois arquivos de configuração de exemplo
- Defina as credenciais por meio de variáveis de ambiente
- Execute
npm install - Execute
npm run build - Execute
npm run start:http - Verifique
/healthz - Verifique se requisições não autenticadas
/mcprecebem401 - Configure o proxy reverso HTTPS
- Verifique uma inicialização MCP autenticada e as páginas públicas opcionais
Ativos de Terceiros
A geração de texto para PDF inclui a fonte variável Noto Sans SC de 17,8 MB para
cobertura offline de glifos multilíngues. Isso aumenta intencionalmente o tarball npm
para aproximadamente 11,4 MB e o pacote descompactado para aproximadamente 18,4 MB para todas
as instalações, incluindo implantações que não habilitam fluxos de trabalho de PDF. Enviar a
fonte no pacote evita uma dependência de rede em runtime e preserva a renderização
previsível de CJK; operadores que priorizam uma instalação menor podem remover o ativo
e aceitar o fallback documentado Helvetica com cobertura limitada de CJK. Sua licença SIL
Open Font e aviso de direitos autorais estão incluídos em assets/fonts/OFL.txt.
Licença
MIT -- LayerV AI