LicenseGuard

Responde se uma licença de dependência cria uma obrigação para a forma como você distribui — SaaS hospedado, binário distribuído, on-premise, uso interno apenas ou biblioteca publicada — e cita a cláusula.

Documentação

LicenseGuard

English | 日本語

Glama quality MCP Registry License

Também listado em mcpservers.org, Smithery, no TensorBlock MCP Index e no catálogo Docker MCP.

A licença desta dependência cria uma obrigação para a forma como você distribui software?

Scanners de licença genéricos respondem a uma pergunta diferente — "qual é a licença?" — e então alertam sobre tudo. O LicenseGuard avalia a licença em relação ao seu modelo de distribuição, de modo que a mesma licença produz vereditos diferentes dependendo de como o software chega aos seus usuários.

Ao vivo: https://licenseguard.tenchorooms.com

A origem workers.dev abaixo é a mesma implantação, mantida como o endpoint estável para o qual os catálogos MCP apontam.

Por que o modelo de distribuição decide isso?

Como você distribuiDependência AGPL-3.0
SaaS (acessível via rede)bloqueada — cláusula de rede §13
Uso interno apenaspermitida
Binário distribuído / on-premisebloqueada — termos de distribuição GPL herdados
devDependency (nunca no artefato)permitida

Essa última linha é o ponto central. Um linter de tempo de build sob AGPL nunca é distribuído, então não aciona nada — mas ferramentas que alertam sobre ele mesmo assim treinam as pessoas a ignorar todos os alertas que produzem.

A mesma divisão percorre o restante do cenário de licenças, e as distinções não são intercambiáveis:

  • GPL — as obrigações estão vinculadas à distribuição. Executar código GPL como um serviço de rede não é distribuição.
  • AGPL — adiciona a §13, que está vinculada à interação em rede — um gatilho separado dos termos de distribuição da GPL. Citar a §13 para um caso de distribuição é simplesmente errado, e o LicenseGuard não faz isso.
  • MPL / EPL / CDDL — são limitadas a arquivos e independentes de vinculação. A MPL-2.0 §3.3 permite explicitamente distribuir uma Obra Maior sob seus próprios termos. Aplicar a lógica de religação da LGPL a elas produz falsos positivos.
  • LGPL — é a única que realmente depende da vinculação: vinculação estática acarreta uma obrigação de religação; vinculação dinâmica, não.

Os vereditos são declarados como fatos com a cláusula citada. O LicenseGuard não diz o que você deve fazer.

Perguntas frequentes

A GPL se aplica se eu apenas hospedar o software e nunca distribuí-lo? Não. A GPL-3.0 aciona suas obrigações na distribuição. SaaS hospedado não é distribuição, então nenhuma obrigação surge hoje — mas distribuir o mesmo software posteriormente, como implantação on-premise, binário ou biblioteca publicada, acionaria a divulgação do código-fonte da obra completa.

A AGPL se aplica se eu apenas hospedar o software como SaaS? Sim. A AGPL-3.0 §13 exige que usuários que interajam com uma versão modificada por meio de uma rede recebam a oferta do código-fonte correspondente da obra completa. Essa é a cláusula que faz a AGPL se comportar de forma diferente da GPL para serviços hospedados, e é toda a diferença prática entre as duas.

Dependências de build e desenvolvimento criam obrigações de licença? Não. Uma dependência de desenvolvimento não faz parte do artefato que você distribui, portanto obrigações acionadas por distribuição não surgem. Ferramentas que emitem código no seu resultado, como geradores de código, são um caso separado que vale a pena verificar individualmente.

MIT é segura para uso comercial? Sim, em todos os modelos de distribuição. A MIT exige atribuição e nada mais. A Apache-2.0 chega ao mesmo resultado, adicionando uma concessão de patentes e um requisito de NOTICE — obrigações, mas que não variam conforme a forma de distribuição.

A vinculação estática muda a resposta? Somente para LGPL, e somente para linguagens compiladas. O design da LGPL é que você pode usar a biblioteca em um trabalho proprietário, desde que o usuário possa substituí-la, o que a vinculação dinâmica oferece e a estática não. Go e Rust vinculam estaticamente por padrão, e nada em Cargo.lock ou go.sum diz o contrário.

Por que a mesma licença dá respostas diferentes para projetos diferentes? Porque as obrigações de copyleft estão vinculadas a eventos — distribuir ou permitir que usuários interajam por rede — e não à presença do código. Se esses eventos ocorrem é um fato sobre o seu negócio, não sobre o seu repositório, e é por isso que um scanner que apenas lê seu lockfile não pode decidir isso.

Use com seu agente de codificação

Você precisa disso ao adicionar uma dependência, não ao pesquisar na web. Portanto, a superfície principal é uma ferramenta MCP, não um resultado de busca.

Hospedado — nada para instalar:

claude mcp add licenseguard --transport http https://license-guard.rcc-aoki.workers.dev/mcp

Local (stdio) — seu manifesto nunca sai da sua máquina. Apenas nomes de pacotes e versões são enviados a registros públicos para consulta de licenças:

claude mcp add licenseguard -- docker run -i --rm ghcr.io/rccaoki-wq/license-guard:1.1.0

A imagem é publicada em cada release e declarada como um pacote OCI no registro oficial do MCP, então clientes que leem o registro podem instalá-la sem nada disso. Para compilá-la você mesmo: docker build -t licenseguard . && docker run -i --rm licenseguard.

Ambos os caminhos executam o mesmo mecanismo de políticas. Eles não podem discordar — uma suíte de ponta a ponta (npm run e2e:stdio) os mantém alinhados.

HTTP Streamable sem estado, sem autenticação, sem estado de sessão.

FerramentaQuando chamar
check_dependency_licenseAntes de adicionar uma única dependência
check_manifest_licensesPara auditar um manifesto ou lockfile inteiro
explain_licensePara ver o que uma licença exige em todos os modelos de distribuição

Use com qualquer outra coisa

A API JSON retorna os mesmos vereditos:

curl "https://license-guard.rcc-aoki.workers.dev/api/pkg/pypi/pyload-ng?model=saas"
# => {"license":"AGPL-3.0-only","verdict":"blocked", ...}

Escaneie um lockfile inteiro:

curl -X POST https://license-guard.rcc-aoki.workers.dev/api/scan \
  -H 'content-type: application/json' \
  -d "$(jq -Rs '{content: ., distributionModel: "saas"}' package-lock.json)"

Um índice voltado a agentes está em /llms.txt.

Manifestos suportados

Ecossistemas: npm · PyPI · módulos Go · crates.io · RubyGems · NuGet

FormatoDependências transitivasConsultas a registros
package-lock.jsonsimnenhuma — licenças estão embutidas
pnpm-lock.yaml, yarn.locksimsim (amortizadas por um cache compartilhado)
go.sumsimsim
Cargo.lock, poetry.lock, uv.locksimsim
Gemfile.locksimsim
packages.lock.jsonsimsim
package.json, requirements.txt, pyproject.toml, go.mod, Cargo.tomlapenas diretassim
.csproj, Directory.Packages.props, packages.configapenas diretassim
CycloneDX (JSON), SPDX (JSON)simsomente onde o documento não tem licença

Licenças problemáticas geralmente chegam como dependência de uma dependência, não como algo que você adicionou de propósito — então o caminho do lockfile é o que importa. package-lock.json v2/v3 embute uma licença para cada entrada, o que significa uma auditoria transitiva completa com zero consultas de rede e as versões exatas que serão realmente instaladas.

Uma varredura incompleta nunca é relatada como limpa. Dependências que não puderam ser resolvidas aparecem como not-checked ou review e são contadas no resumo. Elas nunca se tornam allowed.

Uma única varredura realiza no máximo 300 consultas a registros, o que limita o custo de uma requisição. Pacotes em cache não consomem esse orçamento, então o teto só se aplica a pacotes que ninguém consultou ainda — uma primeira varredura de um Cargo.lock com ~1000 crates normalmente deixa algumas dezenas de entradas marcadas como not-checked, e escanear novamente as resolve (medido: os 1043 crates do servo chegam a zero crates publicados não resolvidos na segunda passagem). O resultado diz isso explicitamente, em vez de mostrar silenciosamente uma lista mais curta.

Dependências Git e membros do workspace sendo escaneado são relatados como not-published em vez disso, em todos os formatos que os identificam — o campo source do Cargo.lock, os protocolos workspace: e git+ do yarn, URLs de tarball do pnpm e o resolved do package-lock.json. Nenhum registro público tem dados de licença para eles, então nunca são consultados. Essa é uma situação diferente de atingir o limite de consultas: reescanear não os resolverá, e o resultado diz isso.

Isso importa por mais do que velocidade. Um membro do workspace com o nome de um pacote que também existe publicamente — utils, core ou qualquer outra coisa genérica — seria resolvido em relação a esse pacote público não relacionado e relatado como allowed, porque a versão do workspace (0.0.0-use.local) não corresponde a nada e a consulta recai sobre a última release. Marcar a origem é o que impede um pacote privado de herdar a licença de um estranho.

Registros privados são a exceção deliberada. Uma URL resolved apontando para outro lugar que não npmjs tem a mesma probabilidade de ser um proxy transparente Artifactory ou Nexus servindo o pacote público real, e nada no lockfile distingue os dois — então esses ainda são consultados e ainda relatados como unresolved se falharem.

SBOMs

CycloneDX e SPDX são lidos em JSON, incluindo o envelope {"sbom": …} do GitHub, então a resposta de gh api repos/OWNER/REPO/dependency-graph/sbom pode ser colada sem modificações. Uma licença registrada no documento é usada como está; componentes sem uma são consultados. NOASSERTION, NONE e LicenseRef-* não são tratados como declarações, porque são o documento dizendo que não sabe.

Duas coisas sobre SBOMs reais são relatadas em vez de suavizadas, porque ambas mudam o significado do resultado:

A maioria dos componentes não traz licença utilizável. Em cinco SBOMs publicados do GitHub, o documento forneceu a licença para 8 de 44 componentes no expressjs/express e 1 de 51 no tokio-rs/tokio. O restante foi resolvido contra um registro hoje. O resultado declara a divisão em vez de afirmar que as licenças vieram do documento.

Um intervalo de versão não é uma versão. A exportação do gráfico de dependências do GitHub escreve o intervalo do manifesto — ^2.0.0, >= 0.2.42,< 0.3.0 — no purl e no campo de versão: 36 de 44 componentes no express, 50 de 51 no tokio. Não há release sob essa string, então um scanner que a repassa resolve contra a última release enquanto exibe uma versão que não está no seu artefato. Intervalos não são aceitos como versões, e o resultado diz quantos componentes foram afetados. Um lockfile não tem esse problema, que é a razão prática para preferi-lo.

Componentes que ficam de fora são contados e nomeados em vez de descartados, e o motivo importa mais do que a contagem. GitHub Actions não são uma lacuna. Elas rodam em CI, nunca entram no artefato e, portanto, não podem criar uma obrigação de distribuição ou rede — é por isso que são excluídas, e o resultado diz exatamente isso em vez de classificá-las como "não suportadas". Essa é a diferença entre um fato sobre o seu software e uma limitação da ferramenta, e não é um erro de arredondamento: 52 dos 194 componentes nesses cinco SBOMs são githubactions, o segundo maior tipo depois de cargo. Tipos de pacote que a varredura genuinamente não consegue ler (maven, deb, …) são relatados separadamente.

Um documento onde nada é verificável é rejeitado com o motivo, não retornado como um relatório limpo vazio — e os dois motivos diferem. O SBOM do gorilla/mux é 100% GitHub Actions, então a resposta é que o documento não contém nada que você distribui; procurar um scanner diferente não mudará isso.

Status

Fase 0 — validando disposição para pagar. O servidor MCP e a ferramenta web gratuita estão no ar. Como o ponto real de uso está dentro do fluxo de trabalho de um agente, e não em um resultado de busca, o sinal medido são instalações MCP e chamadas repetidas de ferramentas, não a taxa de cliques. O GitHub App (Fase 1) só começa se esse sinal aparecer.

Desenvolvimento

npm install
npm test           # unit tests
npm run typecheck
npm run coverage
npm run smoke      # live registry connectivity
npm run e2e        # end-to-end against production
                   #   ui           real browser (Playwright)
                   #   a11y         accessibility
                   #   mcp          official MCP SDK client
                   #   load         consistency under concurrency
                   #   adversarial  hostile input and boundaries
                   #   correctness  against known-good verdicts
                   #   operational  cross-path agreement, HTTP, caching
                   #   stdio        local and hosted paths must agree
npm run dev        # http://localhost:8787
npm run signals    # Phase 0 report: real usage only, test traffic excluded

npm run signals é o que decide o que acontece em seguida, então é deliberadamente conservador: tráfego marcado como sintético (toda suíte E2E define x-licenseguard-synthetic: 1), tráfego de crawlers de registros e bots de avaliação, e linhas escritas antes da atribuição existir são todos excluídos e relatados separadamente, em vez de descartados silenciosamente. Qualquer coisa que ele não consegue atribuir, ele se recusa a contar como demanda. Passar nos testes unitários não é suficiente aqui. Vários defeitos reais só apareceram quando dados reais do registro estavam envolvidos, então smoke e e2e rodam contra os upstreams reais e devem passar antes de um release.

Deploy:

npm run db:migrate
npm run deploy

Construído em Cloudflare Workers + Hono + D1.

Documentação

Do que isso depende

Dado o assunto, cada dependência é deliberadamente MIT ou Apache-2.0. Nada aqui obriga divulgação para uma implantação SaaS.

PapelPacoteLicença
Análise de expressão SPDXspdx-expression-parseMIT
Framework webhonoMIT
Dados de licença GoAPI deps.dev (fallback: ClearlyDefined)Apache-2.0

Privacidade

O serviço hospedado não armazena conteúdos de manifestos ou endereços IP. O que ele registra é a forma de uso: qual ferramenta foi chamada, para qual ecossistema e modelo de distribuição, qual veredito retornou, e um identificador de sessão opaco para que o uso repetido possa ser contabilizado. Nenhum nome de pacote aparece em nenhuma dessas linhas. Veja src/mcp/telemetry.ts para os campos exatos.

Nomes de pacotes são armazenados em um lugar, e vale ser exato sobre qual: uma consulta que tem sucesso é armazenada em cache como (ecosystem, package, version) → SPDX id, então o próximo chamador não acessa o registro novamente. Três coisas decorrem de como essa tabela é escrita, e cada uma é garantida por um teste:

  • Ela não tem coluna para quem perguntou. As linhas não carregam sessão, requisição ou endereço, então nada no cache pode ser rastreado até um usuário — veja migrations/0001_init.sql.
  • Um nome que não pôde ser resolvido nunca é gravado (src/resolver/index.ts retorna antes da gravação no cache). Um pacote que não está em um registro público — um interno — é exatamente o caso que falha ao resolver.
  • O cache não é privado: é ele que preenche /sitemap.xml. Tudo nele já está publicado no npm, PyPI, Go ou crates.io sob esse nome.

O identificador de sessão é emitido como o cabeçalho Mcp-Session-Id da especificação. É um valor aleatório sem significado fora deste banco de dados, nunca é obrigatório e nunca expira — clientes que o ignoram continuam funcionando.

Se isso ainda for mais do que sua organização quer compartilhar, execute o servidor stdio local. Ele envia apenas nomes de pacotes e versões, e somente para os registros públicos que já os publicam.

Uma coisa que vale declarar claramente em vez de deixar para alguém descobrir: as páginas HTML do site hospedado carregam o Cloudflare Web Analytics, porque a zona em que ele agora está tem isso habilitado. É sem cookies e agregado, mas é um script de terceiros e não estava lá antes da mudança de domínio. Ele é injetado apenas em HTML — /mcp, /api/*, /llms.txt e /sitemap.xml não são tocados, então agentes e chamadores de API nunca o carregam.

Aviso legal

LicenseGuard fornece informações derivadas de textos de licença publicados e metadados de dependências declarados. Não é aconselhamento jurídico, e usá-lo não cria uma relação advogado-cliente. Os vereditos se baseiam nas informações de licença que um pacote declara; eles não afirmam identificar todas as obrigações ou violações.

Licença

Apache-2.0