NeoDB MCP Server
by xytangme
#!/usr/bin/env node
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
ListToolsRequestSchema,
CallToolRequestSchema,
} from "@modelcontextprotocol/sdk/types.js";
// Retrieve environment variables
const JIRA_INSTANCE_URL = process.env.JIRA_INSTANCE_URL;
const JIRA_API_KEY = process.env.JIRA_API_KEY;
const JIRA_USER_EMAIL = process.env.JIRA_USER_EMAIL;
// Validate environment variables
if (!JIRA_INSTANCE_URL || !JIRA_API_KEY || !JIRA_USER_EMAIL) {
console.error(
"Error: JIRA_INSTANCE_URL, JIRA_USER_EMAIL, and JIRA_API_KEY must be set in the environment.",
);
process.exit(1);
}
// Initialize the server
const server = new Server(
{
name: "jira-mcp",
version: "1.0.0",
},
{
capabilities: {
tools: {},
},
},
);
// Define available tools
server.setRequestHandler(ListToolsRequestSchema, async () => {
return {
tools: [
{
name: "jql_search",
description: "Perform enhanced JQL search in Jira",
inputSchema: {
type: "object",
properties: {
jql: { type: "string", description: "JQL query string" },
nextPageToken: {
type: "string",
description: "Token for next page",
},
maxResults: {
type: "integer",
description: "Maximum results to fetch",
},
fields: {
type: "array",
items: { type: "string" },
description: "List of fields to return for each issue",
},
expand: {
type: "string",
description: "Additional info to include in the response",
},
},
required: ["jql"],
},
},
{
name: "get_issue",
description: "Retrieve details about an issue by its ID or key.",
inputSchema: {
type: "object",
properties: {
issueIdOrKey: {
type: "string",
description: "ID or key of the issue",
},
fields: {
type: "array",
items: { type: "string" },
description: "Fields to include in the response",
},
expand: {
type: "string",
description: "Additional information to include in the response",
},
properties: {
type: "array",
items: { type: "string" },
description: "Properties to include in the response",
},
failFast: {
type: "boolean",
description: "Fail quickly on errors",
default: false,
},
},
required: ["issueIdOrKey"],
},
},
],
};
});
// Handle tool execution
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
if (name === "jql_search") {
const { jql, nextPageToken, maxResults, fields, expand } = args;
try {
const response = await fetch(`${JIRA_INSTANCE_URL}/rest/api/2/search`, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Basic ${Buffer.from(`${JIRA_USER_EMAIL}:${JIRA_API_KEY}`).toString("base64")}`,
},
body: JSON.stringify({
jql,
startAt: nextPageToken || 0,
maxResults: maxResults || 50,
fields: fields || ["*all"],
expand,
}),
});
if (!response.ok) {
throw new Error(`Jira API Error: ${response.statusText}`);
}
const data = await response.json();
return {
content: [
{
type: "text",
text: JSON.stringify(data, null, 2), // Format JSON response
},
],
};
} catch (error) {
return {
isError: true,
content: [
{
type: "text",
text: `Error: ${error.message}`,
},
],
};
}
} else if (name === "get_issue") {
const { issueIdOrKey, fields, expand, properties, failFast } = args;
try {
const queryParams = new URLSearchParams();
if (fields) queryParams.append("fields", fields.join(","));
if (expand) queryParams.append("expand", expand);
if (properties) queryParams.append("properties", properties.join(","));
if (failFast !== undefined)
queryParams.append("failFast", String(failFast));
const response = await fetch(
`${JIRA_INSTANCE_URL}/rest/api/2/issue/${issueIdOrKey}?${queryParams.toString()}`,
{
method: "GET",
headers: {
"Content-Type": "application/json",
Authorization: `Basic ${Buffer.from(`${JIRA_USER_EMAIL}:${JIRA_API_KEY}`).toString("base64")}`,
},
},
);
if (!response.ok) {
throw new Error(`Jira API Error: ${response.statusText}`);
}
const data = await response.json();
return {
content: [
{
type: "text",
text: JSON.stringify(data, null, 2), // Format JSON response
},
],
};
} catch (error) {
return {
isError: true,
content: [
{
type: "text",
text: `Error: ${error.message}`,
},
],
};
}
}
throw new Error(`Tool not found: ${name}`);
});
// Start the server
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
console.log("JIRA MCP Server is running.");
}
main().catch((error) => {
console.error("Error starting the server:", error);
});