Skip to content

feat(sources): add Maven Central package source for JVM MCP servers - #1209

Open
MukundaKatta wants to merge 1 commit into
modelcontextprotocol:mainfrom
MukundaKatta:feat/maven-package-source
Open

feat(sources): add Maven Central package source for JVM MCP servers#1209
MukundaKatta wants to merge 1 commit into
modelcontextprotocol:mainfrom
MukundaKatta:feat/maven-package-source

Conversation

@MukundaKatta

Copy link
Copy Markdown

Why

#1102 asks for first-class support for JVM MCP servers (Java/Kotlin/Scala) so they can be published to Maven Central and discovered through the registry. Today the official registry covers npm, PyPI, NuGet, OCI and MCPB; JVM is the obvious gap, and the JVM ecosystem already has a strong single-shot CLI story via jbang (per the discussion comment).

What

Adds a new maven registry type with the same shape as the existing JVM-friendly handlers, following docs/contributing/add-package-registry.md:

  • Constants: RegistryTypeMaven, RegistryURLMaven (https://repo.maven.apache.org/maven2), RuntimeHintJBang.
  • Validator (internal/validators/registries/maven.go):
    • identifier is groupId:artifactId (e.g., io.github.example:demo-mcp-server); version stays in the dedicated field.
    • Fetches the published POM at <base>/<group/path>/<artifactId>/<version>/<artifactId>-<version>.pom and parses it with encoding/xml.
    • Verifies ownership by matching either an <mcpName>...</mcpName> Maven property (preferred, most explicit) or an mcp-name: <serverName> line in the POM <description> (mirrors the PyPI/NuGet README convention). Maven Central already validates groupId domain ownership at publish time, so a matching declaration in the POM is enough to bind a package to a server name in the same namespace.
    • Body is read through io.LimitReader to keep a hostile mirror from blowing up memory; sanity checks that the POM's own groupId/artifactId match the requested coordinate.
  • Wiring: registered in internal/validators/package.go switch.
  • Schema/docs:
    • docs/reference/server-json/draft/server.schema.json and internal/validators/schemas/2025-12-11.json: add maven to registryType examples, the Maven Central URL to registryBaseUrl examples, and jbang to runtimeHint examples.
    • docs/reference/api/openapi.yaml: same example additions.
    • docs/reference/server-json/official-registry-requirements.md: add Maven Central to the supported-registries list.
    • docs/reference/server-json/generic-server-json.md: add a Maven (JVM) example with the jbang runtime hint.

Tested

  • New internal/validators/registries/maven_test.go exercises:
    • missing identifier / version / wrong identifier shape / disallowed fileSha256 / non-Central base URL rejected,
    • happy path with <mcpName> property and with mcp-name: description fallback (against an httptest.Server),
    • mismatched mcpName, missing ownership signal (asserts the error tells publishers exactly what to add), POM groupId mismatch, 404, and malformed XML.
  • Existing TestValidate_RegistryTypesAndUrls invalid_maven case is updated to a invalid_maven_wrong_baseurl case (since maven is now a valid type, but the official registry still restricts it to Maven Central).
  • A ValidateMavenAt helper is exported only via maven_export_test.go so the production binary stays minimal while tests can point the validator at an in-process repo.

Closes #1102

@MukundaKatta
MukundaKatta force-pushed the feat/maven-package-source branch from 8885358 to c57da80 Compare April 28, 2026 05:31
Add a `maven` registry type so JVM-based MCP servers (Java/Kotlin/Scala)
can be published to Maven Central and resolved through the registry.

Ownership is verified by reading the published POM and matching either
an `<mcpName>` Maven property or an `mcp-name: <serverName>` line in the
POM `<description>`. Maven Central already validates `groupId` domain
ownership at publish time, so a matching declaration in the POM is
sufficient to bind a package to an MCP server name in the same namespace.

The identifier is the Maven `groupId:artifactId` coordinate (e.g.,
`org.example:my-mcp-server`); version stays in the dedicated `version`
field so it's consistent with the other registry types.

Closes modelcontextprotocol#1102
@MukundaKatta
MukundaKatta force-pushed the feat/maven-package-source branch from c57da80 to e0315e4 Compare April 28, 2026 05:42

@HarperZ9 HarperZ9 left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did a passive review pass against the package-registry checklist and current branch state. The direction looks useful for #1102, but I found a couple of concrete items I would resolve before this is considered merge-ready.

Checks performed

  • gh pr view 1209 --repo modelcontextprotocol/registry --json mergeStateStatus,statusCheckRollup -> current merge state is DIRTY; the old CI runs from 2026-04-28 were green but are now stale against main.
  • git merge-tree $(git merge-base HEAD origin/main) HEAD origin/main -> overlapping changes in docs/reference/api/openapi.yaml, docs/reference/server-json/draft/server.schema.json, docs/reference/server-json/generic-server-json.md, docs/reference/server-json/official-registry-requirements.md, internal/validators/package.go, internal/validators/schemas/2025-12-11.json, internal/validators/validators_test.go, pkg/model/constants.go, and pkg/model/types.go.
  • Python syntax/parse check: json.loads for both edited schema JSON files and yaml.safe_load for docs/reference/api/openapi.yaml -> passed.
  • I could not run the Go test suite locally because this environment does not have go or Docker installed, so runtime validation remains unverified in this pass.

Finding 1 ? Maven coordinate validation should protect the URL path invariant

parseMavenIdentifier currently accepts any non-empty groupId:artifactId, and buildPOMURL only does strings.ReplaceAll(groupID, ".", "/") before concatenating groupPath into the request path. artifactId and version are PathEscaped, but groupID is not escaped or segment-validated.

Because Maven Central is allowlisted, this is not an SSRF issue, but it still weakens the canonical Maven-layout invariant: a malformed groupId containing /, \, empty/dot-dot segments, or other path-significant characters can produce unexpected POM paths under the allowed base URL. I did not see a negative test covering that class.

Suggested fix: validate the Maven coordinate pieces before building the URL. At minimum, reject slash/backslash, empty segments, ./.. segments, and characters outside the intended Maven coordinate set for groupId/artifactId; or split the groupId on . and PathEscape each validated segment. Add table tests for invalid groupId/artifactId shapes.

Finding 2 ? public publishing docs are incomplete

The PR updates schemas and the generic server JSON example, but the public package-type docs still omit Maven:

  • docs/modelcontextprotocol-io/package-types.mdx currently documents npm, PyPI, NuGet, Docker/OCI, and MCPB only.
  • docs/modelcontextprotocol-io/quickstart.mdx still says non-npm packages are PyPI, NuGet, OCI, MCPB, excluding Maven.

That leaves publishers without the authoritative place to learn the Maven identifier shape, required POM ownership marker, Maven Central base URL, and jbang runtime hint. The repo?s docs/contributing/add-package-registry.md also expects publishing documentation to be updated when adding a registry type.

Suggested fix: add a Maven section to package-types.mdx and update the quickstart note/troubleshooting text so Maven is included alongside the other non-npm package types.

Remaining risk

This review did not validate the Go implementation by executing tests. After rebasing/merging main, I would rerun the Maven validator tests plus the full package validation suite before treating this as ready.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support JVM packages on maven central

2 participants