drupal-modules-mcp MCP Server
#!/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";
import * as cheerio from "cheerio";
interface ModuleInfo {
name: string;
description: string;
version: string;
downloads: string;
status: string;
composerCommand: string;
drupalCompatibility: string[];
projectUrl: string;
readme: string;
}
class DrupalModulesServer {
private server: Server;
constructor() {
this.server = new Server(
{
name: "drupal-modules-mcp",
version: "0.1.0",
},
{
capabilities: {
tools: {},
},
}
);
this.setupToolHandlers();
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_module_info",
description:
"Get information about a Drupal module from drupal.org",
inputSchema: {
type: "object",
properties: {
module_name: {
type: "string",
description:
"Machine name of the Drupal module",
},
},
required: ["module_name"],
},
},
],
}));
this.server.setRequestHandler(
CallToolRequestSchema,
async (request) => {
if (request.params.name !== "get_module_info") {
throw new McpError(
ErrorCode.MethodNotFound,
`Unknown tool: ${request.params.name}`
);
}
const args = request.params.arguments as {
module_name: string;
};
if (!args.module_name) {
throw new McpError(
ErrorCode.InvalidParams,
"Module name is required"
);
}
try {
const moduleInfo = await this.fetchModuleInfo(
args.module_name
);
return {
content: [
{
type: "text",
text: JSON.stringify(moduleInfo, null, 2),
},
],
};
} catch (error) {
if (axios.isAxiosError(error)) {
throw new McpError(
ErrorCode.InternalError,
`Failed to fetch module info: ${error.message}`
);
}
throw error;
}
}
);
}
private async fetchModuleInfo(moduleName: string): Promise<ModuleInfo> {
const url = `https://www.drupal.org/project/${moduleName}`;
const response = await axios.get(url);
const $ = cheerio.load(response.data);
// Get the latest recommended version
const latestVersion = $(
".release.recommended-Yes .views-field-field-release-version strong"
)
.first()
.text()
.trim();
// Get Drupal compatibility from the release info
const compatibility = $(
".release.recommended-Yes div:contains('Works with Drupal:')"
)
.first()
.text()
.replace("Works with Drupal:", "")
.trim()
.split("||")
.map((v) => v.trim());
// Get description from meta tag since it's cleaner
const description = $('meta[name="description"]').attr("content") || "";
const info: ModuleInfo = {
name: $("#page-title").text().replace("| Drupal.org", "").trim(),
description: description,
version: latestVersion,
downloads:
$(".project-info li:contains('sites report')")
.text()
.match(/\d+,\d+/)?.[0] || "0",
status: $(".project-info li:contains('Module categories')")
.text()
.replace("Module categories:", "")
.trim(),
composerCommand: $(`.drupalorg-copy.composer-command`)
.first()
.text()
.trim(),
drupalCompatibility: compatibility,
projectUrl: url,
readme: (() => {
const $body = $(".field-name-body");
// Replace each link with text+url format
$body.find("a").each((_, elem) => {
const $elem = $(elem);
const href = $elem.attr("href");
const text = $elem.text().trim();
$elem.replaceWith(`${text} (${href})`);
});
return $body.text().trim();
})(),
};
// Add additional compatibility info from release table
const releaseInfo = $(
".table-release-compatibility-current tbody tr"
).first();
if (releaseInfo.length) {
const releaseCompatibility = releaseInfo
.find("td")
.map((_, elem) => $(elem).text().trim())
.get();
info.drupalCompatibility = [
...new Set([
...info.drupalCompatibility,
...releaseCompatibility,
]),
];
}
return info;
}
async run() {
const transport = new StdioServerTransport();
await this.server.connect(transport);
console.error("Drupal Modules MCP server running on stdio");
}
}
const server = new DrupalModulesServer();
server.run().catch(console.error);