maven-mcp-server
by Bigsy
Verified
#!/usr/bin/env node
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import {
CallToolRequestSchema,
ErrorCode,
ListToolsRequestSchema,
McpError,
} from '@modelcontextprotocol/sdk/types.js';
import axios from 'axios';
interface MavenSearchResponse {
response: {
docs: Array<{
id: string;
g: string; // groupId
a: string; // artifactId
v: string; // version
p?: string; // packaging
timestamp: number;
}>;
};
}
interface MavenCoordinate {
groupId: string;
artifactId: string;
version?: string;
packaging?: string;
classifier?: string;
}
const parseMavenCoordinate = (dependency: string): MavenCoordinate => {
const parts = dependency.split(':');
if (parts.length < 2) {
throw new McpError(
ErrorCode.InvalidParams,
'Invalid Maven coordinate format. Minimum format is "groupId:artifactId"'
);
}
return {
groupId: parts[0],
artifactId: parts[1],
version: parts[2],
packaging: parts[3],
classifier: parts[4]
};
};
const isValidMavenArgs = (
args: any
): args is { dependency: string } =>
typeof args === 'object' &&
args !== null &&
typeof args.dependency === 'string';
const isValidVersionCheckArgs = (
args: any
): args is { dependency: string; version?: string } =>
typeof args === 'object' &&
args !== null &&
typeof args.dependency === 'string' &&
(args.version === undefined || typeof args.version === 'string');
class MavenDepsServer {
private server: Server;
private axiosInstance;
constructor() {
this.server = new Server(
{
name: 'maven-deps-server',
version: '0.1.0',
},
{
capabilities: {
tools: {},
},
}
);
this.axiosInstance = axios.create({
baseURL: 'https://search.maven.org/solrsearch/select',
});
this.setupToolHandlers();
// Error handling
this.server.onerror = (error) => console.error('[MCP Error]', error);
process.on('SIGINT', async () => {
await this.server.close();
process.exit(0);
});
}
private setupToolHandlers() {
this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [
{
name: 'get_maven_latest_version',
description: 'Get the latest version of a Maven dependency',
inputSchema: {
type: 'object',
properties: {
dependency: {
type: 'string',
description: 'Maven coordinate in format "groupId:artifactId[:version][:packaging][:classifier]" (e.g. "org.springframework:spring-core" or "org.springframework:spring-core:5.3.20:jar")',
},
},
required: ['dependency'],
},
},
{
name: 'check_maven_version_exists',
description: 'Check if a specific version of a Maven dependency exists',
inputSchema: {
type: 'object',
properties: {
dependency: {
type: 'string',
description: 'Maven coordinate in format "groupId:artifactId[:version][:packaging][:classifier]" (e.g. "org.springframework:spring-core" or "org.springframework:spring-core:5.3.20:jar")',
},
version: {
type: 'string',
description: 'Version to check if not included in dependency string',
},
},
required: ['dependency'],
},
},
],
}));
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
switch (request.params.name) {
case 'get_maven_latest_version':
return this.handleGetLatestVersion(request.params.arguments);
case 'check_maven_version_exists':
return this.handleCheckVersionExists(request.params.arguments);
default:
throw new McpError(
ErrorCode.MethodNotFound,
`Unknown tool: ${request.params.name}`
);
}
});
}
private async handleGetLatestVersion(args: unknown) {
if (!isValidMavenArgs(args)) {
throw new McpError(
ErrorCode.InvalidParams,
'Invalid Maven dependency format'
);
}
const coord = parseMavenCoordinate(args.dependency);
try {
let query = `g:"${coord.groupId}" AND a:"${coord.artifactId}"`;
if (coord.packaging) {
query += ` AND p:"${coord.packaging}"`;
}
const response = await this.axiosInstance.get<MavenSearchResponse>('', {
params: {
q: query,
core: 'gav',
rows: 100,
wt: 'json',
},
});
if (!response.data.response.docs.length) {
return {
content: [
{
type: 'text',
text: `No Maven dependency found for ${coord.groupId}:${coord.artifactId}${coord.packaging ? ':' + coord.packaging : ''}`,
},
],
isError: true,
};
}
const versions = response.data.response.docs.map(doc => doc.v);
const latestVersion = versions.sort((a, b) => {
const aParts = a.split('.').map(Number);
const bParts = b.split('.').map(Number);
for (let i = 0; i < Math.max(aParts.length, bParts.length); i++) {
const aNum = aParts[i] || 0;
const bNum = bParts[i] || 0;
if (aNum !== bNum) {
return bNum - aNum;
}
}
return 0;
})[0];
return {
content: [
{
type: 'text',
text: latestVersion,
},
],
};
} catch (error) {
if (axios.isAxiosError(error)) {
return {
content: [
{
type: 'text',
text: `Maven Central API error: ${
error.response?.data?.error?.msg ?? error.message
}`,
},
],
isError: true,
};
}
throw error;
}
}
private async handleCheckVersionExists(args: unknown) {
if (!isValidVersionCheckArgs(args)) {
throw new McpError(
ErrorCode.InvalidParams,
'Invalid Maven dependency format'
);
}
const coord = parseMavenCoordinate(args.dependency);
// Use version from coordinate if available, otherwise use the version parameter
const version = coord.version || args.version;
if (!version) {
throw new McpError(
ErrorCode.InvalidParams,
'Version must be provided either in dependency string or version parameter'
);
}
try {
let query = `g:"${coord.groupId}" AND a:"${coord.artifactId}" AND v:"${version}"`;
if (coord.packaging) {
query += ` AND p:"${coord.packaging}"`;
}
const response = await this.axiosInstance.get<MavenSearchResponse>('', {
params: {
q: query,
core: 'gav',
rows: 1,
wt: 'json',
},
});
const exists = response.data.response.docs.length > 0;
return {
content: [
{
type: 'text',
text: exists ? 'true' : 'false',
},
],
};
} catch (error) {
if (axios.isAxiosError(error)) {
return {
content: [
{
type: 'text',
text: `Maven Central API error: ${
error.response?.data?.error?.msg ?? error.message
}`,
},
],
isError: true,
};
}
throw error;
}
}
async run() {
const transport = new StdioServerTransport();
await this.server.connect(transport);
console.error('Maven Dependencies MCP server running on stdio');
}
}
const server = new MavenDepsServer();
server.run().catch(console.error);