mcp-tavily-search
by y7ut
#!/usr/bin/env node
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
CallToolRequestSchema,
ListToolsRequestSchema,
ToolSchema,
} from "@modelcontextprotocol/sdk/types.js";
import { z } from "zod";
import { zodToJsonSchema } from "zod-to-json-schema";
import JiraClient from 'jira-client';
// Parse command line arguments
const args = process.argv.slice(2);
let jiraUrl, jiraUsername, jiraApiToken;
for (let i = 0; i < args.length; i++) {
switch (args[i]) {
case '--jira-url':
jiraUrl = args[++i];
break;
case '--jira-username':
jiraUsername = args[++i];
break;
case '--jira-api-token':
jiraApiToken = args[++i];
break;
}
}
// Validate required command line arguments
if (!jiraUrl || !jiraUsername || !jiraApiToken) {
console.error('Error: Missing required arguments');
console.error('Usage: mcp-server-jira --jira-url <url> --jira-username <username> --jira-api-token <token>');
process.exit(1);
}
// Validate URL format
try {
new URL(jiraUrl);
} catch (e) {
console.error('Error: Invalid Jira URL format');
process.exit(1);
}
// Validate username format (basic email format check)
if (!jiraUsername.includes('@')) {
console.error('Error: Username should be an email address');
process.exit(1);
}
// Validate API token is not empty and has minimum length
if (jiraApiToken.length < 32) {
console.error('Error: Invalid API token format');
process.exit(1);
}
// Schema definitions
const JiraGetIssueArgsSchema = z.object({
issue_key: z.string().describe('Jira issue key (e.g., \'PROJ-123\')'),
expand: z.string().describe('Optional fields to expand').optional(),
});
const ToolInputSchema = ToolSchema.shape.inputSchema;
type ToolInput = z.infer<typeof ToolInputSchema>;
// Add Jira client configuration schema
const JiraConfigSchema = z.object({
protocol: z.string().default('https'),
host: z.string().describe('Jira host (e.g., your-domain.atlassian.net)'),
username: z.string().describe('Jira username/email'),
password: z.string().describe('Jira API token'),
apiVersion: z.string().default('2'),
strictSSL: z.boolean().default(true)
});
// Initialize Jira client from command line arguments
const jiraConfig = JiraConfigSchema.parse({
protocol: 'https',
host: jiraUrl.replace(/^https?:\/\//, ''), // Remove protocol
username: jiraUsername,
password: jiraApiToken,
apiVersion: '3',
strictSSL: true
});
const jira = new JiraClient(jiraConfig);
// Server setup
const server = new Server(
{
name: "secure-jira-server",
version: "0.1.0",
},
{
capabilities: {
tools: {},
},
},
);
// Tool implementations
// Tool handlers
server.setRequestHandler(ListToolsRequestSchema, async () => {
return {
tools: [
{
name: "jira_get_issue",
description: "Get details of a specific Jira issue.",
inputSchema: zodToJsonSchema(JiraGetIssueArgsSchema) as ToolInput,
}
]
};
});
server.setRequestHandler(CallToolRequestSchema, async (request) => {
try {
const { name, arguments: args } = request.params;
switch (name) {
case "jira_get_issue": {
const parsed = JiraGetIssueArgsSchema.safeParse(args);
if (!parsed.success) {
throw new Error(`Invalid arguments for jira_get_issue: ${parsed.error}`);
}
const { issue_key, expand } = parsed.data;
const issue = await jira.findIssue(issue_key, expand);
return {
content: [{ type: "text", text: JSON.stringify(issue, null, 2) }],
isError: false,
};
}
default:
throw new Error(`Unknown tool: ${name}`);
}
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
return {
content: [{ type: "text", text: `Error: ${errorMessage}` }],
isError: true,
};
}
});
// Start server
async function runServer() {
const transport = new StdioServerTransport();
await server.connect(transport);
console.error("Secure MCP Jira Server running on stdio");
}
runServer().catch((error) => {
console.error("Fatal error running server:", error);
process.exit(1);
});