maven-central-mcp 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +21 -0
- package/README.md +59 -0
- package/dist/discovery/discover.d.ts +2 -0
- package/dist/discovery/discover.js +56 -0
- package/dist/discovery/gradle-parser.d.ts +2 -0
- package/dist/discovery/gradle-parser.js +38 -0
- package/dist/discovery/maven-parser.d.ts +2 -0
- package/dist/discovery/maven-parser.js +14 -0
- package/dist/discovery/types.d.ts +9 -0
- package/dist/discovery/types.js +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +84 -0
- package/dist/maven/client.d.ts +8 -0
- package/dist/maven/client.js +40 -0
- package/dist/maven/repository.d.ts +18 -0
- package/dist/maven/repository.js +43 -0
- package/dist/maven/resolver.d.ts +8 -0
- package/dist/maven/resolver.js +61 -0
- package/dist/maven/types.d.ts +23 -0
- package/dist/maven/types.js +1 -0
- package/dist/project/find-project-root.d.ts +2 -0
- package/dist/project/find-project-root.js +24 -0
- package/dist/tools/check-multiple-dependencies.d.ts +20 -0
- package/dist/tools/check-multiple-dependencies.js +26 -0
- package/dist/tools/check-version-exists.d.ts +15 -0
- package/dist/tools/check-version-exists.js +27 -0
- package/dist/tools/compare-dependency-versions.d.ts +31 -0
- package/dist/tools/compare-dependency-versions.js +41 -0
- package/dist/tools/get-latest-version.d.ts +15 -0
- package/dist/tools/get-latest-version.js +17 -0
- package/dist/version/classify.d.ts +3 -0
- package/dist/version/classify.js +24 -0
- package/dist/version/compare.d.ts +2 -0
- package/dist/version/compare.js +22 -0
- package/dist/version/types.d.ts +7 -0
- package/dist/version/types.js +1 -0
- package/package.json +49 -0
- package/plugin/.claude-plugin/plugin.json +12 -0
- package/plugin/hooks/hooks.json +15 -0
- package/plugin/hooks/post-edit-deps.sh +28 -0
- package/plugin/skills/check-deps/SKILL.md +36 -0
- package/plugin/skills/latest-version/SKILL.md +34 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://anthropic.com/claude-code/marketplace.schema.json",
|
|
3
|
+
"name": "maven-mcp",
|
|
4
|
+
"description": "Maven dependency intelligence for Claude Code",
|
|
5
|
+
"owner": {
|
|
6
|
+
"name": "kirich1409"
|
|
7
|
+
},
|
|
8
|
+
"plugins": [
|
|
9
|
+
{
|
|
10
|
+
"name": "maven-mcp",
|
|
11
|
+
"description": "Maven dependency intelligence — auto-registers MCP server, provides /check-deps and /latest-version skills",
|
|
12
|
+
"author": {
|
|
13
|
+
"name": "kirich1409"
|
|
14
|
+
},
|
|
15
|
+
"version": "0.2.0",
|
|
16
|
+
"category": "development",
|
|
17
|
+
"homepage": "https://github.com/kirich1409/maven-central-mcp",
|
|
18
|
+
"source": "./plugin"
|
|
19
|
+
}
|
|
20
|
+
]
|
|
21
|
+
}
|
package/README.md
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# maven-central-mcp
|
|
2
|
+
|
|
3
|
+
MCP server for Maven Central dependency intelligence. Provides AI assistants with structured, live dependency data from Maven Central.
|
|
4
|
+
|
|
5
|
+
Works with any JVM build tool that uses Maven Central coordinates (Maven, Gradle, SBT, etc).
|
|
6
|
+
|
|
7
|
+
## Quick Start
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npx maven-central-mcp
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
### Claude Desktop
|
|
14
|
+
|
|
15
|
+
Add to your Claude Desktop config (`~/Library/Application Support/Claude/claude_desktop_config.json`):
|
|
16
|
+
|
|
17
|
+
```json
|
|
18
|
+
{
|
|
19
|
+
"mcpServers": {
|
|
20
|
+
"maven-central": {
|
|
21
|
+
"command": "npx",
|
|
22
|
+
"args": ["maven-central-mcp"]
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### VS Code
|
|
29
|
+
|
|
30
|
+
Create `.vscode/mcp.json` in your workspace:
|
|
31
|
+
|
|
32
|
+
```json
|
|
33
|
+
{
|
|
34
|
+
"servers": {
|
|
35
|
+
"maven-central": {
|
|
36
|
+
"type": "stdio",
|
|
37
|
+
"command": "npx",
|
|
38
|
+
"args": ["maven-central-mcp"]
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Tools
|
|
45
|
+
|
|
46
|
+
| Tool | Description |
|
|
47
|
+
|------|-------------|
|
|
48
|
+
| `get_latest_version` | Find the latest version with stability-aware selection (STABLE_ONLY / PREFER_STABLE / ALL) |
|
|
49
|
+
| `check_version_exists` | Verify a specific version exists and classify its stability |
|
|
50
|
+
| `check_multiple_dependencies` | Bulk lookup of latest versions for a list of dependencies |
|
|
51
|
+
| `compare_dependency_versions` | Compare current versions against latest, with upgrade type (major/minor/patch) |
|
|
52
|
+
|
|
53
|
+
## Version Stability
|
|
54
|
+
|
|
55
|
+
Versions are classified as: `stable`, `rc`, `beta`, `alpha`, `milestone`, or `snapshot`.
|
|
56
|
+
|
|
57
|
+
## License
|
|
58
|
+
|
|
59
|
+
MIT
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { parseGradleRepositories } from "./gradle-parser.js";
|
|
4
|
+
import { parseMavenRepositories } from "./maven-parser.js";
|
|
5
|
+
const GRADLE_FILES = [
|
|
6
|
+
"settings.gradle.kts",
|
|
7
|
+
"settings.gradle",
|
|
8
|
+
"build.gradle.kts",
|
|
9
|
+
"build.gradle",
|
|
10
|
+
];
|
|
11
|
+
export function discoverRepositories(projectRoot) {
|
|
12
|
+
const allRepos = [];
|
|
13
|
+
const seen = new Set();
|
|
14
|
+
let buildSystem = "unknown";
|
|
15
|
+
function addRepos(repos) {
|
|
16
|
+
for (const repo of repos) {
|
|
17
|
+
if (!seen.has(repo.url)) {
|
|
18
|
+
seen.add(repo.url);
|
|
19
|
+
allRepos.push(repo);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
// Try Gradle files
|
|
24
|
+
for (const file of GRADLE_FILES) {
|
|
25
|
+
const path = join(projectRoot, file);
|
|
26
|
+
if (existsSync(path)) {
|
|
27
|
+
buildSystem = "gradle";
|
|
28
|
+
try {
|
|
29
|
+
const content = readFileSync(path, "utf-8");
|
|
30
|
+
addRepos(parseGradleRepositories(content));
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
console.error(`Failed to parse ${path}`);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
// Try Maven pom.xml (only if no Gradle files found)
|
|
38
|
+
if (buildSystem === "unknown") {
|
|
39
|
+
const pomPath = join(projectRoot, "pom.xml");
|
|
40
|
+
if (existsSync(pomPath)) {
|
|
41
|
+
buildSystem = "maven";
|
|
42
|
+
try {
|
|
43
|
+
const content = readFileSync(pomPath, "utf-8");
|
|
44
|
+
addRepos(parseMavenRepositories(content));
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
console.error(`Failed to parse ${pomPath}`);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return {
|
|
52
|
+
repositories: allRepos,
|
|
53
|
+
buildSystem,
|
|
54
|
+
projectRoot,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { MAVEN_CENTRAL, GOOGLE_MAVEN, GRADLE_PLUGIN_PORTAL } from "../maven/repository.js";
|
|
2
|
+
const WELL_KNOWN_REPOS = {
|
|
3
|
+
mavenCentral: { name: MAVEN_CENTRAL.name, url: MAVEN_CENTRAL.url },
|
|
4
|
+
google: { name: GOOGLE_MAVEN.name, url: GOOGLE_MAVEN.url },
|
|
5
|
+
gradlePluginPortal: { name: GRADLE_PLUGIN_PORTAL.name, url: GRADLE_PLUGIN_PORTAL.url },
|
|
6
|
+
};
|
|
7
|
+
export function parseGradleRepositories(content) {
|
|
8
|
+
const repos = [];
|
|
9
|
+
// Well-known: mavenCentral(), google(), gradlePluginPortal()
|
|
10
|
+
for (const [funcName, config] of Object.entries(WELL_KNOWN_REPOS)) {
|
|
11
|
+
const pattern = new RegExp(`\\b${funcName}\\s*\\(\\s*\\)`, "g");
|
|
12
|
+
if (pattern.test(content)) {
|
|
13
|
+
repos.push(config);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
// maven("url") or maven('url')
|
|
17
|
+
const mavenDirectRegex = /\bmaven\s*\(\s*["']([^"']+)["']\s*\)/g;
|
|
18
|
+
let match;
|
|
19
|
+
while ((match = mavenDirectRegex.exec(content)) !== null) {
|
|
20
|
+
repos.push({ name: match[1], url: match[1] });
|
|
21
|
+
}
|
|
22
|
+
// maven(url = "url") or maven(url = 'url')
|
|
23
|
+
const mavenUrlParamRegex = /\bmaven\s*\(\s*url\s*=\s*["']([^"']+)["']\s*\)/g;
|
|
24
|
+
while ((match = mavenUrlParamRegex.exec(content)) !== null) {
|
|
25
|
+
repos.push({ name: match[1], url: match[1] });
|
|
26
|
+
}
|
|
27
|
+
// maven { url = uri("url") } or maven { url = uri('url') }
|
|
28
|
+
const mavenBlockUriRegex = /\bmaven\s*\{[^}]*url\s*=\s*uri\s*\(\s*["']([^"']+)["']\s*\)/g;
|
|
29
|
+
while ((match = mavenBlockUriRegex.exec(content)) !== null) {
|
|
30
|
+
repos.push({ name: match[1], url: match[1] });
|
|
31
|
+
}
|
|
32
|
+
// Groovy: maven { url 'url' } or maven { url "url" }
|
|
33
|
+
const mavenBlockGroovyRegex = /\bmaven\s*\{[^}]*url\s+["']([^"']+)["']/g;
|
|
34
|
+
while ((match = mavenBlockGroovyRegex.exec(content)) !== null) {
|
|
35
|
+
repos.push({ name: match[1], url: match[1] });
|
|
36
|
+
}
|
|
37
|
+
return repos;
|
|
38
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export function parseMavenRepositories(content) {
|
|
2
|
+
const repos = [];
|
|
3
|
+
const repoBlockRegex = /<repository>([\s\S]*?)<\/repository>/g;
|
|
4
|
+
let match;
|
|
5
|
+
while ((match = repoBlockRegex.exec(content)) !== null) {
|
|
6
|
+
const block = match[1];
|
|
7
|
+
const url = block.match(/<url>([^<]+)<\/url>/)?.[1]?.trim();
|
|
8
|
+
if (!url)
|
|
9
|
+
continue;
|
|
10
|
+
const id = block.match(/<id>([^<]+)<\/id>/)?.[1]?.trim();
|
|
11
|
+
repos.push({ name: id ?? url, url });
|
|
12
|
+
}
|
|
13
|
+
return repos;
|
|
14
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
import { HttpMavenRepository, MAVEN_CENTRAL, GOOGLE_MAVEN, GRADLE_PLUGIN_PORTAL } from "./maven/repository.js";
|
|
6
|
+
import { findProjectRoot } from "./project/find-project-root.js";
|
|
7
|
+
import { discoverRepositories } from "./discovery/discover.js";
|
|
8
|
+
import { getLatestVersionHandler } from "./tools/get-latest-version.js";
|
|
9
|
+
import { checkVersionExistsHandler } from "./tools/check-version-exists.js";
|
|
10
|
+
import { checkMultipleDependenciesHandler } from "./tools/check-multiple-dependencies.js";
|
|
11
|
+
import { compareDependencyVersionsHandler } from "./tools/compare-dependency-versions.js";
|
|
12
|
+
const server = new McpServer({
|
|
13
|
+
name: "maven-central-mcp",
|
|
14
|
+
version: "0.2.0",
|
|
15
|
+
});
|
|
16
|
+
let cachedRepos = null;
|
|
17
|
+
function getRepositories() {
|
|
18
|
+
if (cachedRepos)
|
|
19
|
+
return cachedRepos;
|
|
20
|
+
const repos = [];
|
|
21
|
+
const projectRoot = findProjectRoot(process.cwd());
|
|
22
|
+
if (projectRoot) {
|
|
23
|
+
const discovery = discoverRepositories(projectRoot);
|
|
24
|
+
console.error(`Discovered ${discovery.repositories.length} repositories from ${discovery.buildSystem} project at ${projectRoot}`);
|
|
25
|
+
for (const config of discovery.repositories) {
|
|
26
|
+
repos.push(new HttpMavenRepository(config.name, config.url));
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
// Add well-known repos as fallback (skip if already discovered)
|
|
30
|
+
for (const fallback of [GOOGLE_MAVEN, GRADLE_PLUGIN_PORTAL, MAVEN_CENTRAL]) {
|
|
31
|
+
if (!repos.some((r) => r.url === fallback.url)) {
|
|
32
|
+
repos.push(fallback);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
cachedRepos = repos;
|
|
36
|
+
return repos;
|
|
37
|
+
}
|
|
38
|
+
server.tool("get_latest_version", "Find the latest version of a Maven artifact with stability-aware selection", {
|
|
39
|
+
groupId: z.string().describe("Maven group ID (e.g. io.ktor)"),
|
|
40
|
+
artifactId: z.string().describe("Maven artifact ID (e.g. ktor-server-core)"),
|
|
41
|
+
stabilityFilter: z
|
|
42
|
+
.enum(["STABLE_ONLY", "PREFER_STABLE", "ALL"])
|
|
43
|
+
.optional()
|
|
44
|
+
.describe("Version stability filter (default: PREFER_STABLE)"),
|
|
45
|
+
}, async (params) => {
|
|
46
|
+
const result = await getLatestVersionHandler(getRepositories(), params);
|
|
47
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
48
|
+
});
|
|
49
|
+
server.tool("check_version_exists", "Verify a specific version exists and classify its stability", {
|
|
50
|
+
groupId: z.string().describe("Maven group ID"),
|
|
51
|
+
artifactId: z.string().describe("Maven artifact ID"),
|
|
52
|
+
version: z.string().describe("Version to check"),
|
|
53
|
+
}, async (params) => {
|
|
54
|
+
const result = await checkVersionExistsHandler(getRepositories(), params);
|
|
55
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
56
|
+
});
|
|
57
|
+
server.tool("check_multiple_dependencies", "Bulk lookup of latest versions for a list of Maven dependencies", {
|
|
58
|
+
dependencies: z.array(z.object({
|
|
59
|
+
groupId: z.string().describe("Maven group ID"),
|
|
60
|
+
artifactId: z.string().describe("Maven artifact ID"),
|
|
61
|
+
})).describe("List of dependencies to check"),
|
|
62
|
+
}, async (params) => {
|
|
63
|
+
const result = await checkMultipleDependenciesHandler(getRepositories(), params);
|
|
64
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
65
|
+
});
|
|
66
|
+
server.tool("compare_dependency_versions", "Compare current dependency versions against latest available, showing upgrade type (major/minor/patch)", {
|
|
67
|
+
dependencies: z.array(z.object({
|
|
68
|
+
groupId: z.string().describe("Maven group ID"),
|
|
69
|
+
artifactId: z.string().describe("Maven artifact ID"),
|
|
70
|
+
currentVersion: z.string().describe("Currently used version"),
|
|
71
|
+
})).describe("Dependencies with current versions to compare"),
|
|
72
|
+
}, async (params) => {
|
|
73
|
+
const result = await compareDependencyVersionsHandler(getRepositories(), params);
|
|
74
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
75
|
+
});
|
|
76
|
+
async function main() {
|
|
77
|
+
const transport = new StdioServerTransport();
|
|
78
|
+
await server.connect(transport);
|
|
79
|
+
console.error("maven-central-mcp running on stdio");
|
|
80
|
+
}
|
|
81
|
+
main().catch((error) => {
|
|
82
|
+
console.error("Fatal error:", error);
|
|
83
|
+
process.exit(1);
|
|
84
|
+
});
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { MavenMetadata, MavenSearchResponse } from "./types.js";
|
|
2
|
+
export declare class MavenCentralClient {
|
|
3
|
+
buildSearchUrl(groupId: string, artifactId: string, rows: number): string;
|
|
4
|
+
buildMetadataUrl(groupId: string, artifactId: string): string;
|
|
5
|
+
searchArtifact(groupId: string, artifactId: string): Promise<MavenSearchResponse>;
|
|
6
|
+
fetchMetadata(groupId: string, artifactId: string): Promise<MavenMetadata>;
|
|
7
|
+
parseMetadataXml(xml: string, groupId: string, artifactId: string): MavenMetadata;
|
|
8
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
const SEARCH_BASE = "https://search.maven.org/solrsearch/select";
|
|
2
|
+
const REPO_BASE = "https://repo1.maven.org/maven2";
|
|
3
|
+
export class MavenCentralClient {
|
|
4
|
+
buildSearchUrl(groupId, artifactId, rows) {
|
|
5
|
+
return `${SEARCH_BASE}?q=g:${groupId}+AND+a:${artifactId}&rows=${rows}&wt=json`;
|
|
6
|
+
}
|
|
7
|
+
buildMetadataUrl(groupId, artifactId) {
|
|
8
|
+
const groupPath = groupId.replace(/\./g, "/");
|
|
9
|
+
return `${REPO_BASE}/${groupPath}/${artifactId}/maven-metadata.xml`;
|
|
10
|
+
}
|
|
11
|
+
async searchArtifact(groupId, artifactId) {
|
|
12
|
+
const url = this.buildSearchUrl(groupId, artifactId, 1);
|
|
13
|
+
const response = await fetch(url);
|
|
14
|
+
if (!response.ok) {
|
|
15
|
+
throw new Error(`Maven Central search failed: ${response.status} ${response.statusText}`);
|
|
16
|
+
}
|
|
17
|
+
return response.json();
|
|
18
|
+
}
|
|
19
|
+
async fetchMetadata(groupId, artifactId) {
|
|
20
|
+
const url = this.buildMetadataUrl(groupId, artifactId);
|
|
21
|
+
const response = await fetch(url);
|
|
22
|
+
if (!response.ok) {
|
|
23
|
+
throw new Error(`Maven Central metadata fetch failed: ${response.status} ${response.statusText}`);
|
|
24
|
+
}
|
|
25
|
+
const xml = await response.text();
|
|
26
|
+
return this.parseMetadataXml(xml, groupId, artifactId);
|
|
27
|
+
}
|
|
28
|
+
parseMetadataXml(xml, groupId, artifactId) {
|
|
29
|
+
const versions = [];
|
|
30
|
+
const versionRegex = /<version>([^<]+)<\/version>/g;
|
|
31
|
+
let match;
|
|
32
|
+
while ((match = versionRegex.exec(xml)) !== null) {
|
|
33
|
+
versions.push(match[1]);
|
|
34
|
+
}
|
|
35
|
+
const latest = xml.match(/<latest>([^<]+)<\/latest>/)?.[1];
|
|
36
|
+
const release = xml.match(/<release>([^<]+)<\/release>/)?.[1];
|
|
37
|
+
const lastUpdated = xml.match(/<lastUpdated>([^<]+)<\/lastUpdated>/)?.[1];
|
|
38
|
+
return { groupId, artifactId, versions, latest, release, lastUpdated };
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { MavenMetadata } from "./types.js";
|
|
2
|
+
export interface MavenRepository {
|
|
3
|
+
readonly name: string;
|
|
4
|
+
readonly url: string;
|
|
5
|
+
fetchMetadata(groupId: string, artifactId: string): Promise<MavenMetadata>;
|
|
6
|
+
}
|
|
7
|
+
export declare class HttpMavenRepository implements MavenRepository {
|
|
8
|
+
readonly name: string;
|
|
9
|
+
readonly url: string;
|
|
10
|
+
constructor(name: string, url: string);
|
|
11
|
+
buildMetadataUrl(groupId: string, artifactId: string): string;
|
|
12
|
+
fetchMetadata(groupId: string, artifactId: string): Promise<MavenMetadata>;
|
|
13
|
+
parseMetadataXml(xml: string, groupId: string, artifactId: string): MavenMetadata;
|
|
14
|
+
}
|
|
15
|
+
export declare const MAVEN_CENTRAL: HttpMavenRepository;
|
|
16
|
+
export declare const GOOGLE_MAVEN: HttpMavenRepository;
|
|
17
|
+
export declare const GRADLE_PLUGIN_PORTAL: HttpMavenRepository;
|
|
18
|
+
export declare const PROXY_TARGET_URLS: Set<string>;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
export class HttpMavenRepository {
|
|
2
|
+
name;
|
|
3
|
+
url;
|
|
4
|
+
constructor(name, url) {
|
|
5
|
+
this.name = name;
|
|
6
|
+
this.url = url.replace(/\/+$/, "");
|
|
7
|
+
}
|
|
8
|
+
buildMetadataUrl(groupId, artifactId) {
|
|
9
|
+
const groupPath = groupId.replace(/\./g, "/");
|
|
10
|
+
return `${this.url}/${groupPath}/${artifactId}/maven-metadata.xml`;
|
|
11
|
+
}
|
|
12
|
+
async fetchMetadata(groupId, artifactId) {
|
|
13
|
+
const url = this.buildMetadataUrl(groupId, artifactId);
|
|
14
|
+
const response = await fetch(url, { signal: AbortSignal.timeout(10_000) });
|
|
15
|
+
if (!response.ok) {
|
|
16
|
+
throw new Error(`Metadata fetch failed from ${this.name}: ${response.status} ${response.statusText}`);
|
|
17
|
+
}
|
|
18
|
+
const xml = await response.text();
|
|
19
|
+
return this.parseMetadataXml(xml, groupId, artifactId);
|
|
20
|
+
}
|
|
21
|
+
parseMetadataXml(xml, groupId, artifactId) {
|
|
22
|
+
const versions = [];
|
|
23
|
+
const versionRegex = /<version>([^<]+)<\/version>/g;
|
|
24
|
+
let match;
|
|
25
|
+
while ((match = versionRegex.exec(xml)) !== null) {
|
|
26
|
+
versions.push(match[1]);
|
|
27
|
+
}
|
|
28
|
+
const latest = xml.match(/<latest>([^<]+)<\/latest>/)?.[1];
|
|
29
|
+
const release = xml.match(/<release>([^<]+)<\/release>/)?.[1];
|
|
30
|
+
const lastUpdated = xml.match(/<lastUpdated>([^<]+)<\/lastUpdated>/)?.[1];
|
|
31
|
+
return { groupId, artifactId, versions, latest, release, lastUpdated };
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
export const MAVEN_CENTRAL = new HttpMavenRepository("Maven Central", "https://repo1.maven.org/maven2");
|
|
35
|
+
export const GOOGLE_MAVEN = new HttpMavenRepository("Google", "https://maven.google.com");
|
|
36
|
+
export const GRADLE_PLUGIN_PORTAL = new HttpMavenRepository("Gradle Plugin Portal", "https://plugins.gradle.org/m2");
|
|
37
|
+
// URLs commonly proxied by corporate repos (Nexus, Artifactory).
|
|
38
|
+
// When a custom repo returns results, these are deprioritized to avoid
|
|
39
|
+
// duplicate/stale metadata. Google Maven and Gradle Plugin Portal are NOT
|
|
40
|
+
// included — they host unique artifacts not typically proxied.
|
|
41
|
+
export const PROXY_TARGET_URLS = new Set([
|
|
42
|
+
MAVEN_CENTRAL.url,
|
|
43
|
+
]);
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { MavenRepository } from "./repository.js";
|
|
2
|
+
import type { MavenMetadata } from "./types.js";
|
|
3
|
+
export interface ResolveFirstResult {
|
|
4
|
+
metadata: MavenMetadata;
|
|
5
|
+
repository: MavenRepository;
|
|
6
|
+
}
|
|
7
|
+
export declare function resolveFirst(repos: MavenRepository[], groupId: string, artifactId: string): Promise<ResolveFirstResult | null>;
|
|
8
|
+
export declare function resolveAll(repos: MavenRepository[], groupId: string, artifactId: string): Promise<MavenMetadata>;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { PROXY_TARGET_URLS } from "./repository.js";
|
|
2
|
+
export async function resolveFirst(repos, groupId, artifactId) {
|
|
3
|
+
for (const repo of repos) {
|
|
4
|
+
try {
|
|
5
|
+
const metadata = await repo.fetchMetadata(groupId, artifactId);
|
|
6
|
+
return { metadata, repository: repo };
|
|
7
|
+
}
|
|
8
|
+
catch {
|
|
9
|
+
continue;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
return null;
|
|
13
|
+
}
|
|
14
|
+
export async function resolveAll(repos, groupId, artifactId) {
|
|
15
|
+
if (repos.length === 0) {
|
|
16
|
+
throw new Error(`No repositories configured to search for ${groupId}:${artifactId}`);
|
|
17
|
+
}
|
|
18
|
+
const results = await Promise.all(repos.map(async (repo) => {
|
|
19
|
+
try {
|
|
20
|
+
return await repo.fetchMetadata(groupId, artifactId);
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
}));
|
|
26
|
+
// Custom repos (Nexus, Artifactory) typically proxy Maven Central.
|
|
27
|
+
// When a custom repo returns results for an artifact, skip Maven Central
|
|
28
|
+
// results to avoid duplicates and stale metadata from proxy caching.
|
|
29
|
+
// Google Maven and Gradle Plugin Portal are NOT proxy targets —
|
|
30
|
+
// they host unique artifacts and always contribute results.
|
|
31
|
+
const all = results.map((r, i) => ({ result: r, repo: repos[i] }));
|
|
32
|
+
const hasCustomResult = all.some(({ result, repo }) => result !== null && !PROXY_TARGET_URLS.has(repo.url));
|
|
33
|
+
const successful = all
|
|
34
|
+
.filter(({ result, repo }) => result !== null && !(hasCustomResult && PROXY_TARGET_URLS.has(repo.url)))
|
|
35
|
+
.map(({ result }) => result);
|
|
36
|
+
if (successful.length === 0) {
|
|
37
|
+
throw new Error(`Artifact ${groupId}:${artifactId} not found in any repository`);
|
|
38
|
+
}
|
|
39
|
+
const orderedVersions = [];
|
|
40
|
+
const seen = new Set();
|
|
41
|
+
for (const meta of successful) {
|
|
42
|
+
for (const v of meta.versions) {
|
|
43
|
+
if (!seen.has(v)) {
|
|
44
|
+
seen.add(v);
|
|
45
|
+
orderedVersions.push(v);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
// Pick the most recent latest/release across all repos
|
|
50
|
+
const allLatest = successful.map((m) => m.latest).filter(Boolean);
|
|
51
|
+
const allRelease = successful.map((m) => m.release).filter(Boolean);
|
|
52
|
+
const lastVersion = orderedVersions[orderedVersions.length - 1];
|
|
53
|
+
return {
|
|
54
|
+
groupId,
|
|
55
|
+
artifactId,
|
|
56
|
+
versions: orderedVersions,
|
|
57
|
+
latest: allLatest.includes(lastVersion) ? lastVersion : allLatest[allLatest.length - 1] ?? lastVersion,
|
|
58
|
+
release: allRelease.includes(lastVersion) ? lastVersion : allRelease[allRelease.length - 1] ?? lastVersion,
|
|
59
|
+
lastUpdated: successful[0].lastUpdated,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export interface MavenSearchResponse {
|
|
2
|
+
response: {
|
|
3
|
+
numFound: number;
|
|
4
|
+
docs: MavenArtifact[];
|
|
5
|
+
};
|
|
6
|
+
}
|
|
7
|
+
export interface MavenArtifact {
|
|
8
|
+
id: string;
|
|
9
|
+
g: string;
|
|
10
|
+
a: string;
|
|
11
|
+
v: string;
|
|
12
|
+
latestVersion: string;
|
|
13
|
+
timestamp: number;
|
|
14
|
+
versionCount: number;
|
|
15
|
+
}
|
|
16
|
+
export interface MavenMetadata {
|
|
17
|
+
groupId: string;
|
|
18
|
+
artifactId: string;
|
|
19
|
+
versions: string[];
|
|
20
|
+
latest?: string;
|
|
21
|
+
release?: string;
|
|
22
|
+
lastUpdated?: string;
|
|
23
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { dirname, join, resolve } from "node:path";
|
|
3
|
+
export const BUILD_FILE_MARKERS = [
|
|
4
|
+
"settings.gradle.kts",
|
|
5
|
+
"settings.gradle",
|
|
6
|
+
"build.gradle.kts",
|
|
7
|
+
"build.gradle",
|
|
8
|
+
"pom.xml",
|
|
9
|
+
];
|
|
10
|
+
export function findProjectRoot(startDir) {
|
|
11
|
+
let current = resolve(startDir);
|
|
12
|
+
while (true) {
|
|
13
|
+
for (const marker of BUILD_FILE_MARKERS) {
|
|
14
|
+
if (existsSync(join(current, marker))) {
|
|
15
|
+
return current;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
const parent = dirname(current);
|
|
19
|
+
if (parent === current) {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
current = parent;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { MavenRepository } from "../maven/repository.js";
|
|
2
|
+
interface Dependency {
|
|
3
|
+
groupId: string;
|
|
4
|
+
artifactId: string;
|
|
5
|
+
}
|
|
6
|
+
export interface CheckMultipleDependenciesInput {
|
|
7
|
+
dependencies: Dependency[];
|
|
8
|
+
}
|
|
9
|
+
export interface DependencyResult {
|
|
10
|
+
groupId: string;
|
|
11
|
+
artifactId: string;
|
|
12
|
+
latestVersion: string;
|
|
13
|
+
stability: string;
|
|
14
|
+
error?: string;
|
|
15
|
+
}
|
|
16
|
+
export interface CheckMultipleDependenciesResult {
|
|
17
|
+
results: DependencyResult[];
|
|
18
|
+
}
|
|
19
|
+
export declare function checkMultipleDependenciesHandler(repos: MavenRepository[], input: CheckMultipleDependenciesInput): Promise<CheckMultipleDependenciesResult>;
|
|
20
|
+
export {};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { classifyVersion, findLatestVersion } from "../version/classify.js";
|
|
2
|
+
import { resolveAll } from "../maven/resolver.js";
|
|
3
|
+
export async function checkMultipleDependenciesHandler(repos, input) {
|
|
4
|
+
const results = await Promise.all(input.dependencies.map(async (dep) => {
|
|
5
|
+
try {
|
|
6
|
+
const metadata = await resolveAll(repos, dep.groupId, dep.artifactId);
|
|
7
|
+
const latest = findLatestVersion(metadata.versions);
|
|
8
|
+
return {
|
|
9
|
+
groupId: dep.groupId,
|
|
10
|
+
artifactId: dep.artifactId,
|
|
11
|
+
latestVersion: latest,
|
|
12
|
+
stability: classifyVersion(latest),
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
catch (e) {
|
|
16
|
+
return {
|
|
17
|
+
groupId: dep.groupId,
|
|
18
|
+
artifactId: dep.artifactId,
|
|
19
|
+
latestVersion: "",
|
|
20
|
+
stability: "",
|
|
21
|
+
error: e instanceof Error ? e.message : String(e),
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
}));
|
|
25
|
+
return { results };
|
|
26
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { MavenRepository } from "../maven/repository.js";
|
|
2
|
+
export interface CheckVersionExistsInput {
|
|
3
|
+
groupId: string;
|
|
4
|
+
artifactId: string;
|
|
5
|
+
version: string;
|
|
6
|
+
}
|
|
7
|
+
export interface CheckVersionExistsResult {
|
|
8
|
+
groupId: string;
|
|
9
|
+
artifactId: string;
|
|
10
|
+
version: string;
|
|
11
|
+
exists: boolean;
|
|
12
|
+
stability?: string;
|
|
13
|
+
repository?: string;
|
|
14
|
+
}
|
|
15
|
+
export declare function checkVersionExistsHandler(repos: MavenRepository[], input: CheckVersionExistsInput): Promise<CheckVersionExistsResult>;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { classifyVersion } from "../version/classify.js";
|
|
2
|
+
export async function checkVersionExistsHandler(repos, input) {
|
|
3
|
+
for (const repo of repos) {
|
|
4
|
+
try {
|
|
5
|
+
const metadata = await repo.fetchMetadata(input.groupId, input.artifactId);
|
|
6
|
+
if (metadata.versions.includes(input.version)) {
|
|
7
|
+
return {
|
|
8
|
+
groupId: input.groupId,
|
|
9
|
+
artifactId: input.artifactId,
|
|
10
|
+
version: input.version,
|
|
11
|
+
exists: true,
|
|
12
|
+
stability: classifyVersion(input.version),
|
|
13
|
+
repository: repo.name,
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
continue;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return {
|
|
22
|
+
groupId: input.groupId,
|
|
23
|
+
artifactId: input.artifactId,
|
|
24
|
+
version: input.version,
|
|
25
|
+
exists: false,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { MavenRepository } from "../maven/repository.js";
|
|
2
|
+
interface DependencyWithVersion {
|
|
3
|
+
groupId: string;
|
|
4
|
+
artifactId: string;
|
|
5
|
+
currentVersion: string;
|
|
6
|
+
}
|
|
7
|
+
export interface CompareDependencyVersionsInput {
|
|
8
|
+
dependencies: DependencyWithVersion[];
|
|
9
|
+
}
|
|
10
|
+
export interface CompareResult {
|
|
11
|
+
groupId: string;
|
|
12
|
+
artifactId: string;
|
|
13
|
+
currentVersion: string;
|
|
14
|
+
latestVersion: string;
|
|
15
|
+
latestStability: string;
|
|
16
|
+
upgradeType: string;
|
|
17
|
+
upgradeAvailable: boolean;
|
|
18
|
+
error?: string;
|
|
19
|
+
}
|
|
20
|
+
export interface CompareDependencyVersionsResult {
|
|
21
|
+
results: CompareResult[];
|
|
22
|
+
summary: {
|
|
23
|
+
total: number;
|
|
24
|
+
upgradeable: number;
|
|
25
|
+
major: number;
|
|
26
|
+
minor: number;
|
|
27
|
+
patch: number;
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
export declare function compareDependencyVersionsHandler(repos: MavenRepository[], input: CompareDependencyVersionsInput): Promise<CompareDependencyVersionsResult>;
|
|
31
|
+
export {};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { classifyVersion, findLatestVersion } from "../version/classify.js";
|
|
2
|
+
import { getUpgradeType } from "../version/compare.js";
|
|
3
|
+
import { resolveAll } from "../maven/resolver.js";
|
|
4
|
+
export async function compareDependencyVersionsHandler(repos, input) {
|
|
5
|
+
const results = await Promise.all(input.dependencies.map(async (dep) => {
|
|
6
|
+
try {
|
|
7
|
+
const metadata = await resolveAll(repos, dep.groupId, dep.artifactId);
|
|
8
|
+
const latest = findLatestVersion(metadata.versions);
|
|
9
|
+
const upgradeType = getUpgradeType(dep.currentVersion, latest);
|
|
10
|
+
return {
|
|
11
|
+
groupId: dep.groupId,
|
|
12
|
+
artifactId: dep.artifactId,
|
|
13
|
+
currentVersion: dep.currentVersion,
|
|
14
|
+
latestVersion: latest,
|
|
15
|
+
latestStability: classifyVersion(latest),
|
|
16
|
+
upgradeType,
|
|
17
|
+
upgradeAvailable: upgradeType !== "none",
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
catch (e) {
|
|
21
|
+
return {
|
|
22
|
+
groupId: dep.groupId,
|
|
23
|
+
artifactId: dep.artifactId,
|
|
24
|
+
currentVersion: dep.currentVersion,
|
|
25
|
+
latestVersion: "",
|
|
26
|
+
latestStability: "",
|
|
27
|
+
upgradeType: "none",
|
|
28
|
+
upgradeAvailable: false,
|
|
29
|
+
error: e instanceof Error ? e.message : String(e),
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
}));
|
|
33
|
+
const summary = {
|
|
34
|
+
total: results.length,
|
|
35
|
+
upgradeable: results.filter((r) => r.upgradeAvailable).length,
|
|
36
|
+
major: results.filter((r) => r.upgradeType === "major").length,
|
|
37
|
+
minor: results.filter((r) => r.upgradeType === "minor").length,
|
|
38
|
+
patch: results.filter((r) => r.upgradeType === "patch").length,
|
|
39
|
+
};
|
|
40
|
+
return { results, summary };
|
|
41
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { StabilityFilter } from "../version/types.js";
|
|
2
|
+
import type { MavenRepository } from "../maven/repository.js";
|
|
3
|
+
export interface GetLatestVersionInput {
|
|
4
|
+
groupId: string;
|
|
5
|
+
artifactId: string;
|
|
6
|
+
stabilityFilter?: StabilityFilter;
|
|
7
|
+
}
|
|
8
|
+
export interface GetLatestVersionResult {
|
|
9
|
+
groupId: string;
|
|
10
|
+
artifactId: string;
|
|
11
|
+
latestVersion: string;
|
|
12
|
+
stability: string;
|
|
13
|
+
allVersionsCount: number;
|
|
14
|
+
}
|
|
15
|
+
export declare function getLatestVersionHandler(repos: MavenRepository[], input: GetLatestVersionInput): Promise<GetLatestVersionResult>;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { classifyVersion, findLatestVersion } from "../version/classify.js";
|
|
2
|
+
import { resolveAll } from "../maven/resolver.js";
|
|
3
|
+
export async function getLatestVersionHandler(repos, input) {
|
|
4
|
+
const metadata = await resolveAll(repos, input.groupId, input.artifactId);
|
|
5
|
+
const filter = input.stabilityFilter ?? "PREFER_STABLE";
|
|
6
|
+
const selected = findLatestVersion(metadata.versions, filter);
|
|
7
|
+
if (!selected) {
|
|
8
|
+
throw new Error(`No stable version found for ${input.groupId}:${input.artifactId}`);
|
|
9
|
+
}
|
|
10
|
+
return {
|
|
11
|
+
groupId: input.groupId,
|
|
12
|
+
artifactId: input.artifactId,
|
|
13
|
+
latestVersion: selected,
|
|
14
|
+
stability: classifyVersion(selected),
|
|
15
|
+
allVersionsCount: metadata.versions.length,
|
|
16
|
+
};
|
|
17
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
const STABILITY_PATTERNS = [
|
|
2
|
+
[/[-.]?SNAPSHOT$/i, "snapshot"],
|
|
3
|
+
[/[-.](?:alpha|a(?=\d|[-.]|$))[-.]?\d*/i, "alpha"],
|
|
4
|
+
[/[-.](?:beta|b(?=\d|[-.]|$))[-.]?\d*/i, "beta"],
|
|
5
|
+
[/[-.](?:M|milestone)[-.]?\d*/i, "milestone"],
|
|
6
|
+
[/[-.](?:RC|CR)[-.]?\d*/i, "rc"],
|
|
7
|
+
];
|
|
8
|
+
export function classifyVersion(version) {
|
|
9
|
+
for (const [pattern, stability] of STABILITY_PATTERNS) {
|
|
10
|
+
if (pattern.test(version)) {
|
|
11
|
+
return stability;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
return "stable";
|
|
15
|
+
}
|
|
16
|
+
export function findLatestVersion(versions, filter = "PREFER_STABLE") {
|
|
17
|
+
const reversed = [...versions].reverse();
|
|
18
|
+
if (filter === "ALL")
|
|
19
|
+
return reversed[0];
|
|
20
|
+
const stable = reversed.find((v) => classifyVersion(v) === "stable");
|
|
21
|
+
if (filter === "STABLE_ONLY")
|
|
22
|
+
return stable;
|
|
23
|
+
return stable ?? reversed[0];
|
|
24
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
function parseSegments(version) {
|
|
2
|
+
return version
|
|
3
|
+
.replace(/[-+].*$/, "")
|
|
4
|
+
.split(".")
|
|
5
|
+
.map((s) => parseInt(s, 10) || 0);
|
|
6
|
+
}
|
|
7
|
+
export function getUpgradeType(current, latest) {
|
|
8
|
+
const cur = parseSegments(current);
|
|
9
|
+
const lat = parseSegments(latest);
|
|
10
|
+
const maxLen = Math.max(cur.length, lat.length);
|
|
11
|
+
while (cur.length < maxLen)
|
|
12
|
+
cur.push(0);
|
|
13
|
+
while (lat.length < maxLen)
|
|
14
|
+
lat.push(0);
|
|
15
|
+
if (lat[0] !== cur[0])
|
|
16
|
+
return lat[0] > cur[0] ? "major" : "none";
|
|
17
|
+
if (lat[1] !== cur[1])
|
|
18
|
+
return lat[1] > cur[1] ? "minor" : "none";
|
|
19
|
+
if (lat[2] !== cur[2])
|
|
20
|
+
return lat[2] > cur[2] ? "patch" : "none";
|
|
21
|
+
return "none";
|
|
22
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export type StabilityType = "stable" | "rc" | "beta" | "alpha" | "milestone" | "snapshot";
|
|
2
|
+
export type StabilityFilter = "STABLE_ONLY" | "PREFER_STABLE" | "ALL";
|
|
3
|
+
export type UpgradeType = "major" | "minor" | "patch" | "none";
|
|
4
|
+
export interface VersionInfo {
|
|
5
|
+
version: string;
|
|
6
|
+
stability: StabilityType;
|
|
7
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "maven-central-mcp",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "MCP server for Maven Central dependency intelligence",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"bin": {
|
|
8
|
+
"maven-central-mcp": "./dist/index.js"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"build": "tsc",
|
|
12
|
+
"lint": "eslint src/",
|
|
13
|
+
"test": "vitest run",
|
|
14
|
+
"dev": "tsc --watch"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"mcp",
|
|
18
|
+
"maven",
|
|
19
|
+
"maven-central",
|
|
20
|
+
"dependencies",
|
|
21
|
+
"jvm"
|
|
22
|
+
],
|
|
23
|
+
"author": "",
|
|
24
|
+
"license": "MIT",
|
|
25
|
+
"repository": {
|
|
26
|
+
"type": "git",
|
|
27
|
+
"url": "https://github.com/kirich1409/maven-central-mcp"
|
|
28
|
+
},
|
|
29
|
+
"files": [
|
|
30
|
+
"dist",
|
|
31
|
+
".claude-plugin",
|
|
32
|
+
"plugin"
|
|
33
|
+
],
|
|
34
|
+
"engines": {
|
|
35
|
+
"node": ">=18"
|
|
36
|
+
},
|
|
37
|
+
"dependencies": {
|
|
38
|
+
"@modelcontextprotocol/sdk": "^1.27.1",
|
|
39
|
+
"zod": "^4.3.6"
|
|
40
|
+
},
|
|
41
|
+
"devDependencies": {
|
|
42
|
+
"@eslint/js": "^10.0.1",
|
|
43
|
+
"@types/node": "^25.3.5",
|
|
44
|
+
"eslint": "^10.0.3",
|
|
45
|
+
"typescript": "^5.9.3",
|
|
46
|
+
"typescript-eslint": "^8.56.1",
|
|
47
|
+
"vitest": "^4.0.18"
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "maven-mcp",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Maven dependency intelligence — auto-registers MCP server, provides /check-deps and /latest-version skills",
|
|
5
|
+
"mcpServers": {
|
|
6
|
+
"maven-mcp": {
|
|
7
|
+
"command": "npx",
|
|
8
|
+
"args": ["-y", "maven-central-mcp"]
|
|
9
|
+
}
|
|
10
|
+
},
|
|
11
|
+
"skills": "./skills"
|
|
12
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
# Read hook input from stdin
|
|
4
|
+
HOOK_INPUT=$(cat)
|
|
5
|
+
|
|
6
|
+
# Extract tool name and file path
|
|
7
|
+
TOOL_NAME=$(echo "$HOOK_INPUT" | jq -r '.tool_name // empty')
|
|
8
|
+
FILE_PATH=$(echo "$HOOK_INPUT" | jq -r '.tool_input.file_path // empty')
|
|
9
|
+
|
|
10
|
+
# Only care about Edit and Write tools
|
|
11
|
+
if [[ "$TOOL_NAME" != "Edit" && "$TOOL_NAME" != "Write" ]]; then
|
|
12
|
+
exit 0
|
|
13
|
+
fi
|
|
14
|
+
|
|
15
|
+
# Check if the file is a build dependency file
|
|
16
|
+
BASENAME=$(basename "$FILE_PATH")
|
|
17
|
+
case "$BASENAME" in
|
|
18
|
+
build.gradle|build.gradle.kts|settings.gradle|settings.gradle.kts|pom.xml|libs.versions.toml)
|
|
19
|
+
;;
|
|
20
|
+
*)
|
|
21
|
+
exit 0
|
|
22
|
+
;;
|
|
23
|
+
esac
|
|
24
|
+
|
|
25
|
+
# Output reminder as JSON systemMessage
|
|
26
|
+
cat <<'EOF'
|
|
27
|
+
{"systemMessage":"Build dependency file was modified. Consider running /check-deps to verify dependency versions are up to date."}
|
|
28
|
+
EOF
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: check-deps
|
|
3
|
+
description: Scan current project build files for Maven/Gradle dependencies and check for available updates. Use when user says "check deps", "check dependencies", "outdated dependencies", "update dependencies", or "/check-deps".
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Check Dependencies
|
|
7
|
+
|
|
8
|
+
Scan the current project for Maven/Gradle dependencies and report available updates.
|
|
9
|
+
|
|
10
|
+
## Steps
|
|
11
|
+
|
|
12
|
+
1. Find build dependency files in the project root:
|
|
13
|
+
- `gradle/libs.versions.toml` (Gradle version catalog)
|
|
14
|
+
- `build.gradle.kts` or `build.gradle`
|
|
15
|
+
- `pom.xml`
|
|
16
|
+
|
|
17
|
+
2. Read the found files and extract all dependencies with their current versions.
|
|
18
|
+
- For `libs.versions.toml`: parse the `[versions]` and `[libraries]` sections
|
|
19
|
+
- For Gradle files: find `implementation`, `api`, `compileOnly`, `testImplementation` etc. with group:artifact:version
|
|
20
|
+
- For `pom.xml`: find `<dependency>` blocks with `<groupId>`, `<artifactId>`, `<version>`
|
|
21
|
+
|
|
22
|
+
3. Call the `check_multiple_dependencies` MCP tool (from maven-mcp server) with all extracted dependencies.
|
|
23
|
+
|
|
24
|
+
4. Present results as a markdown table:
|
|
25
|
+
|
|
26
|
+
| Artifact | Current | Latest | Upgrade |
|
|
27
|
+
|----------|---------|--------|---------|
|
|
28
|
+
| io.ktor:ktor-server-core | 2.3.5 | 2.3.8 | PATCH |
|
|
29
|
+
|
|
30
|
+
5. If all dependencies are up to date, say so.
|
|
31
|
+
|
|
32
|
+
## Important
|
|
33
|
+
|
|
34
|
+
- Always check `libs.versions.toml` first — it's the modern Gradle standard for version management.
|
|
35
|
+
- Dependencies in `libs.versions.toml` may use version references in build files — resolve them.
|
|
36
|
+
- Skip dependencies without explicit versions (e.g., BOM-managed or platform dependencies).
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: latest-version
|
|
3
|
+
description: Find the latest version of a Maven artifact. Use when user says "latest version", "find version", "what version", or "/latest-version groupId:artifactId".
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Latest Version
|
|
7
|
+
|
|
8
|
+
Find the latest version of a specific Maven artifact.
|
|
9
|
+
|
|
10
|
+
## Arguments
|
|
11
|
+
|
|
12
|
+
The user provides `groupId:artifactId`, for example:
|
|
13
|
+
- `io.ktor:ktor-server-core`
|
|
14
|
+
- `org.jetbrains.kotlin:kotlin-stdlib`
|
|
15
|
+
- `com.google.dagger:hilt-android`
|
|
16
|
+
|
|
17
|
+
## Steps
|
|
18
|
+
|
|
19
|
+
1. Parse the user's input to extract `groupId` and `artifactId` (split by `:`).
|
|
20
|
+
|
|
21
|
+
2. Call the `get_latest_version` MCP tool (from maven-mcp server) with:
|
|
22
|
+
- `groupId`: extracted group ID
|
|
23
|
+
- `artifactId`: extracted artifact ID
|
|
24
|
+
- `stabilityFilter`: `PREFER_STABLE` (default)
|
|
25
|
+
|
|
26
|
+
3. Display the result:
|
|
27
|
+
- Latest stable version
|
|
28
|
+
- Latest version (if different from stable)
|
|
29
|
+
- All available versions (abbreviated if more than 10)
|
|
30
|
+
|
|
31
|
+
## Error Handling
|
|
32
|
+
|
|
33
|
+
- If the artifact is not found, suggest checking the groupId and artifactId spelling.
|
|
34
|
+
- If the format is wrong (no `:`), ask the user to provide `groupId:artifactId`.
|