LicenseGuard

Responde si una licencia de dependencia crea una obligación según la forma en que distribuyes el producto — SaaS alojado, binario distribuido, on-premise, solo interno o biblioteca publicada — y cita la cláusula.

Documentación

LicenseGuard

Español | 日本語

Glama quality MCP Registry License

También listado en mcpservers.org, Smithery, el TensorBlock MCP Index y el Docker MCP catalog.

¿La licencia de esta dependencia crea una obligación para la forma en que distribuyes software?

Los escáneres de licencias genéricos responden a una pregunta diferente — "¿qué licencia es esta?" — y luego advierten sobre todo. LicenseGuard evalúa la licencia contra tu modelo de distribución, de modo que la misma licencia produce veredictos diferentes según cómo el software llega a sus usuarios.

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

El origen workers.dev a continuación es el mismo despliegue, mantenido como el endpoint estable al que apuntan los catálogos de MCP.

¿Por qué el modelo de distribución decide esto?

Cómo distribuyesDependencia AGPL-3.0
SaaS (accesible por red)bloqueada — cláusula de red §13
Uso interno únicamentepermitida
Binario distribuido / on-prembloqueada — términos de distribución GPL heredados
devDependency (nunca en el artefacto)permitida

Esa última fila es el punto central. Un linter de tiempo de compilación bajo AGPL nunca se distribuye, por lo que no activa nada — pero las herramientas que advierten sobre ello entrenan a las personas para ignorar todas las advertencias que producen.

La misma división atraviesa el resto del panorama de licencias, y las distinciones no son intercambiables:

  • GPL las obligaciones se adjuntan a la distribución. Ejecutar código GPL como un servicio de red no es distribución.
  • AGPL añade §13, que se adjunta a la interacción de red — un disparador separado de los términos de distribución de GPL. Citar §13 para un caso de distribución es simplemente incorrecto, y LicenseGuard no lo hace.
  • MPL / EPL / CDDL están limitadas por archivo y son independientes del enlazado. MPL-2.0 §3.3 permite explícitamente distribuir una Obra Mayor bajo tus propios términos. Aplicar la lógica de reenlazado de LGPL a ellas produce falsos positivos.
  • LGPL es la que realmente depende del enlazado: el enlazado estático conlleva una obligación de reenlazado, el enlazado dinámico no.

Los veredictos se expresan como hechos con la cláusula citada. LicenseGuard no te dice qué hacer.

Preguntas comunes

¿Se aplica la GPL si solo alojo el software y nunca lo distribuyo? No. GPL-3.0 activa sus obligaciones en la distribución. El SaaS alojado no es distribución, por lo que no surge ninguna obligación hoy — pero distribuir el mismo software más tarde, como un despliegue on-prem, un binario o una librería publicada, activaría la divulgación del código fuente de toda la obra.

¿Se aplica la AGPL si solo alojo el software como SaaS? Sí. AGPL-3.0 §13 requiere que los usuarios que interactúan con una versión modificada a través de una red reciban la oferta del código fuente correspondiente de toda la obra. Esta es la cláusula que hace que AGPL se comporte de manera diferente a GPL para servicios alojados, y es toda la diferencia práctica entre las dos.

¿Las dependencias de tiempo de compilación y desarrollo crean obligaciones de licencia? No. Una dependencia de desarrollo no es parte del artefacto que distribuyes, por lo que las obligaciones activadas por distribución no surgen. Las herramientas que emiten código en tu salida, como los generadores de código, son un caso aparte que vale la pena verificar individualmente.

¿Es MIT segura para uso comercial? Sí, en todos los modelos de distribución. MIT pide atribución y nada más. Apache-2.0 llega al mismo resultado mientras añade una concesión de patentes y un requisito de NOTICE — obligaciones, pero que no varían según cómo distribuyas.

¿El enlazado estático cambia la respuesta? Solo para LGPL, y solo para lenguajes compilados. El diseño de LGPL es que puedes usar la librería en una obra propietaria siempre que el usuario pueda reemplazarla, lo cual el enlazado dinámico te da y el estático no. Go y Rust enlazan estáticamente por defecto, y nada en Cargo.lock o go.sum dice lo contrario.

¿Por qué la misma licencia da respuestas diferentes para proyectos diferentes? Porque las obligaciones de copyleft se adjuntan a eventos — distribuir, o permitir que los usuarios interactúen a través de una red — no a la presencia del código. Si esos eventos ocurren es un hecho sobre tu negocio, no sobre tu repositorio, por lo que un escáner que solo lee tu lockfile no puede decidirlo.

Úsalo desde tu agente de codificación

Lo necesitas cuando estás añadiendo una dependencia, no cuando buscas en la web. Por lo tanto, la superficie principal es una herramienta MCP, no un resultado de búsqueda.

Alojado — nada que instalar:

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

Local (stdio) — tu manifiesto nunca sale de tu máquina. Solo los nombres de paquetes y versiones se envían a registros públicos para buscar licencias:

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

La imagen se publica en cada lanzamiento y se declara como un paquete OCI en el registro oficial de MCP, por lo que los clientes que leen el registro pueden instalarla sin nada de esto. Para compilarla tú mismo en su lugar: docker build -t licenseguard . && docker run -i --rm licenseguard.

Ambas rutas ejecutan el mismo motor de políticas. No pueden discrepar — una suite de extremo a extremo (npm run e2e:stdio) las mantiene unidas.

Streamable HTTP sin estado, sin autenticación, sin estado de sesión.

HerramientaCuándo llamarla
check_dependency_licenseAntes de añadir una sola dependencia
check_manifest_licensesPara auditar un manifiesto o lockfile completo
explain_licensePara ver qué requiere una licencia en cada modelo de distribución

Úsalo desde cualquier otra cosa

La API JSON devuelve los mismos veredictos:

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

Escanea un lockfile completo:

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)"

Un índice orientado a agentes vive en /llms.txt.

Manifiestos soportados

Ecosistemas: npm · PyPI · Go modules · crates.io · RubyGems · NuGet

FormatoDependencias transitivasBúsquedas en registros
package-lock.jsonninguna — las licencias están incrustadas
pnpm-lock.yaml, yarn.locksí (amortizadas por una caché compartida)
go.sum
Cargo.lock, poetry.lock, uv.lock
Gemfile.lock
packages.lock.json
package.json, requirements.txt, pyproject.toml, go.mod, Cargo.tomlsolo directas
.csproj, Directory.Packages.props, packages.configsolo directas
CycloneDX (JSON), SPDX (JSON)solo donde el documento no tiene licencia

Las licencias problemáticas suelen llegar como una dependencia de una dependencia, no como algo que añadiste a propósito — por lo que la ruta del lockfile es la que importa. package-lock.json v2/v3 incrusta una licencia para cada entrada, lo que significa una auditoría transitiva completa con cero búsquedas de red y las versiones exactas que realmente se instalarán.

Un escaneo incompleto nunca se reporta como limpio. Las dependencias que no pudieron resolverse aparecen como not-checked o review y se cuentan en el resumen. Nunca se convierten en allowed.

Un solo escaneo realiza como máximo 300 búsquedas en registros, lo que limita cuánto puede costar una solicitud. Los paquetes en caché no consumen ese presupuesto, por lo que el límite solo aplica a paquetes que nadie ha buscado todavía — un primer escaneo de un Cargo.lock de ~1000 crates típicamente deja unas pocas docenas de entradas marcadas como not-checked, y escanear de nuevo las resuelve (medido: los 1043 crates de servo alcanzan cero crates publicados sin resolver en la segunda pasada). El resultado lo dice explícitamente en lugar de mostrar silenciosamente una lista más corta.

Las dependencias de Git y los miembros del workspace que se está escaneando se reportan como not-published en su lugar, en todos los formatos que los identifican — el campo source de Cargo.lock, los protocolos workspace: y git+ de yarn, las URLs de tarball de pnpm y el resolved de package-lock.json. Ningún registro público tiene datos de licencia para ellos, por lo que nunca se buscan. Esa es una situación diferente a alcanzar el límite de búsqueda: re-escanear no los resolverá, y el resultado lo dice.

Esto importa por más que la velocidad. Un miembro del workspace con el nombre de un paquete que también existe públicamente — utils, core o cualquier otra cosa genérica — de otro modo se resolvería contra ese paquete público no relacionado y se reportaría como allowed, porque la versión del workspace (0.0.0-use.local) no coincide con nada y la búsqueda cae en la última versión. Marcar el origen es lo que evita que un paquete privado herede la licencia de un extraño.

Los registros privados son la excepción deliberada. Una URL de resolved que apunta a otro lugar que no sea npmjs tiene la misma probabilidad de ser un proxy transparente de Artifactory o Nexus que sirve el paquete público real, y nada en el lockfile distingue los dos — por lo que esos aún se buscan, y aún se reportan como unresolved si fallan.

SBOMs

CycloneDX y SPDX se leen en JSON, incluido el sobre {"sbom": …} de GitHub, por lo que la respuesta de gh api repos/OWNER/REPO/dependency-graph/sbom se puede pegar sin modificar. Una licencia registrada en el documento se usa tal cual; los componentes sin una se buscan. NOASSERTION, NONE y LicenseRef-* no se tratan como declaraciones, porque son el documento diciendo que no sabe.

Dos cosas sobre SBOMs reales se reportan en lugar de suavizarse, porque ambas cambian lo que significa el resultado:

La mayoría de los componentes no llevan una licencia utilizable. En cinco SBOMs publicados de GitHub, el documento proporcionó la licencia para 8 de 44 componentes en expressjs/express y 1 de 51 en tokio-rs/tokio. El resto se resolvió contra un registro hoy. El resultado indica la división en lugar de afirmar que las licencias vinieron del documento.

Un rango de versión no es una versión. La exportación del grafo de dependencias de GitHub escribe el rango del manifiesto — ^2.0.0, >= 0.2.42,< 0.3.0 — en el purl y en el campo de versión: 36 de 44 componentes en express, 50 de 51 en tokio. No hay una versión bajo esa cadena, por lo que un escáner que la pasa tal cual resuelve contra la última versión mientras muestra una versión que no está en tu artefacto. Los rangos no se aceptan como versiones, y el resultado dice cuántos componentes se vieron afectados. Un lockfile no tiene este problema, que es la razón práctica para preferir uno.

Los componentes que se omiten se cuentan y nombran en lugar de descartarse, y la razón importa más que el conteo. Las GitHub Actions no son una brecha. Se ejecutan en CI, nunca entran en el artefacto y, por lo tanto, no pueden crear una obligación de distribución o red — por eso se excluyen, y el resultado dice exactamente eso en lugar de archivarlas bajo "no soportado". Esta es la diferencia entre un hecho sobre tu software y una limitación de la herramienta, y no es un error de redondeo: 52 de los 194 componentes en esos cinco SBOMs son githubactions, el segundo tipo más grande después de cargo. Los tipos de paquetes que el escaneo realmente no puede leer (maven, deb, …) se reportan por separado.

Un documento donde nada es comprobable se rechaza con la razón, no se devuelve como un informe limpio vacío — y las dos razones difieren. El SBOM de gorilla/mux es 100% GitHub Actions, por lo que la respuesta es que el documento no contiene nada que distribuyas; buscar un escáner diferente no cambiará eso.

Estado

Fase 0 — validando la disposición a pagar. El servidor MCP y la herramienta web gratuita están en vivo. Debido a que el punto real de uso está dentro del flujo de trabajo de un agente en lugar de un resultado de búsqueda, la señal que se mide son las instalaciones de MCP y las llamadas repetidas a herramientas, no la tasa de clics. La GitHub App (Fase 1) comienza solo si esa señal aparece.

Desarrollo

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 es el que decide qué sucede después, por lo que es deliberadamente conservador: el tráfico marcado como sintético (cada suite E2E establece x-licenseguard-synthetic: 1), el tráfico de rastreadores de registros y bots de calificación, y las filas escritas antes de que existiera la atribución se excluyen y se reportan por separado en lugar de descartarse silenciosamente. Cualquier cosa que no pueda atribuir, se niega a contarla como demanda. Aprobar las pruebas unitarias no es suficiente aquí. Varios defectos reales solo aparecieron cuando se involucraron datos de registro en vivo, por lo que smoke y e2e se ejecutan contra los upstreams reales y deben pasar antes de un lanzamiento.

Despliegue:

npm run db:migrate
npm run deploy

Construido sobre Cloudflare Workers + Hono + D1.

Documentación

De qué depende esto

Dado el tema en cuestión, cada dependencia es deliberadamente MIT o Apache-2.0. Nada aquí obliga a la divulgación para un despliegue SaaS.

RolPaqueteLicencia
Análisis de expresiones SPDXspdx-expression-parseMIT
Marco webhonoMIT
Datos de licencias de GoAPI de deps.dev (respaldo: ClearlyDefined)Apache-2.0

Privacidad

El servicio alojado no almacena contenidos de manifiestos ni direcciones IP. Lo que registra es la forma de uso: qué herramienta se llamó, para qué ecosistema y modelo de distribución, qué veredicto se devolvió, y un identificador de sesión opaco para que el uso repetido pueda contarse en absoluto. Ningún nombre de paquete aparece en ninguna de esas filas. Consulte src/mcp/telemetry.ts para conocer los campos exactos.

Los nombres de paquetes se almacenan en un lugar, y vale la pena ser precisos sobre cuál: una búsqueda que tiene éxito se almacena en caché como (ecosystem, package, version) → SPDX id, de modo que el siguiente llamador no vuelva a golpear el registro. Tres cosas se derivan de cómo se escribe esa tabla, y cada una está fijada por una prueba:

  • No tiene columna para quién preguntó. Las filas no llevan sesión, ni solicitud, ni dirección, por lo que nada en la caché puede rastrearse hasta un usuario — consulte migrations/0001_init.sql.
  • Un nombre que no pudo resolverse nunca se escribe (src/resolver/index.ts regresa antes de la escritura en caché). Un paquete que no está en un registro público — uno interno — es exactamente el caso que falla al resolverse.
  • La caché no es privada: es lo que llena /sitemap.xml. Todo lo que contiene ya está publicado en npm, PyPI, Go o crates.io bajo ese nombre.

El identificador de sesión se emite como el encabezado Mcp-Session-Id de la especificación. Es un valor aleatorio sin significado fuera de esta base de datos, nunca es obligatorio y nunca caduca — los clientes que lo ignoren siguen funcionando.

Si eso sigue siendo más de lo que su organización quiere compartir, ejecute el servidor stdio local. No envía nada más que nombres de paquetes y versiones, y solo a los registros públicos que ya los publican.

Una cosa que vale la pena decir claramente en lugar de dejar que alguien la descubra: las páginas HTML del sitio alojado llevan Cloudflare Web Analytics, porque la zona en la que ahora reside lo tiene habilitado. Es sin cookies y agregado, pero es un script de terceros y no estaba allí antes de que el dominio se moviera. Se inyecta solo en HTML — /mcp, /api/*, /llms.txt y /sitemap.xml no se tocan, por lo que los agentes y los llamadores de API nunca lo cargan.

Descargo de responsabilidad

LicenseGuard proporciona información derivada de textos de licencia publicados y metadatos de dependencias declarados. No es asesoramiento legal, y su uso no crea una relación abogado-cliente. Los veredictos se basan en la información de licencia que declara un paquete; no pretenden identificar cada obligación o violación.

Licencia

Apache-2.0