// src/server/McpServerManager.ts
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";
import { StoriesService } from "../services/StoriesService";
import { ResourceHandler } from "../handlers/ResourceHandler";
import { PromptHandler } from "../handlers/PromptHandler";
import { ToolHandler } from "../handlers/ToolHandler";
import { ServerConstants } from "../constants/ServerConstants";
import { Logger } from "../utils/logger";
/**
* MCP Server Manager - manages MCP server lifecycle and registrations
*/
export class McpServerManager {
private readonly server: McpServer;
private readonly resourceHandler: ResourceHandler;
private readonly promptHandler: PromptHandler;
private readonly toolHandler: ToolHandler;
private readonly storiesService: StoriesService;
constructor(
storiesService: StoriesService,
resourceHandler: ResourceHandler,
promptHandler: PromptHandler,
toolHandler: ToolHandler
) {
this.storiesService = storiesService;
this.resourceHandler = resourceHandler;
this.promptHandler = promptHandler;
this.toolHandler = toolHandler;
this.server = this.createServer();
this.registerResources();
this.registerPrompts();
this.registerTools();
}
public getServer(): McpServer {
return this.server;
}
private createServer(): McpServer {
return new McpServer({
name: ServerConstants.SERVER_NAME,
version: ServerConstants.SERVER_VERSION,
defaultResource: ServerConstants.DEFAULT_RESOURCE,
capabilities: {
resources: {},
prompts: {},
tools: {},
},
});
}
private registerResources(): void {
this.registerStoryResource();
this.registerAllStoriesResource();
this.registerStoriesByStatusResource();
}
private registerStoryResource(): void {
const template = this.resourceHandler.createStoryTemplate();
this.server.resource("story", template, async (uri, variables) =>
this.resourceHandler.handleStoryResource(uri, variables)
);
}
private registerAllStoriesResource(): void {
const template = this.resourceHandler.createAllStoriesTemplate();
this.server.resource("all-stories", template, async (uri) =>
this.resourceHandler.handleAllStoriesResource(uri)
);
}
private registerStoriesByStatusResource(): void {
const template = this.resourceHandler.createStoriesByStatusTemplate();
this.server.resource(
"stories-by-status",
template,
async (uri, variables) =>
this.resourceHandler.handleStoriesByStatusResource(uri, variables)
);
}
private registerPrompts(): void {
this.registerViewAllStoriesPrompt();
this.registerViewStoryPrompt();
this.registerViewStoriesByStatusPrompt();
this.registerAskAboutStoriesPrompt();
// Don't register dynamic prompts here - they'll be registered after stories load
}
public registerDynamicStoryPrompts(): void {
// Only register prompts for active (focus) stories
const stories = this.storiesService.getActiveStories();
Logger.info(
`📝 Registering ${stories.length} ACTIVE story prompts (todo/backlog/in progress)...`
);
stories.forEach((story) => {
this.server.prompt(
story.key,
`${story.key}: ${story.title}`,
{ userQuestion: z.string().optional() },
async ({ userQuestion }) =>
this.promptHandler.handleStoryWithQuestion(story.key, userQuestion)
);
});
Logger.success(`Registered ${stories.length} story prompts`);
}
private registerViewAllStoriesPrompt(): void {
this.server.prompt(
ServerConstants.PROMPT_VIEW_ALL,
"View all Jira stories",
{},
async () => this.promptHandler.handleViewAllStories()
);
}
private registerViewStoryPrompt(): void {
this.server.prompt(
ServerConstants.PROMPT_VIEW_STORY,
"View details of a specific Jira story",
{ storyKey: z.string() },
async ({ storyKey }) => this.promptHandler.handleViewStory(storyKey)
);
}
private registerViewStoriesByStatusPrompt(): void {
this.server.prompt(
ServerConstants.PROMPT_VIEW_BY_STATUS,
"View Jira stories filtered by status",
{ status: z.string() },
async ({ status }) => this.promptHandler.handleViewStoriesByStatus(status)
);
}
private registerAskAboutStoriesPrompt(): void {
this.server.prompt(
ServerConstants.PROMPT_ASK_ABOUT_STORIES,
"Ask a question about Jira stories",
{ question: z.string() },
async ({ question }) => this.promptHandler.handleAskAboutStories(question)
);
}
private registerTools(): void {
this.registerPingTool();
this.registerGetAllStoriesTool();
this.registerGetStoryTool();
this.registerGetStoriesByStatusTool();
this.registerSearchStoriesTool();
this.registerGetOpenStoriesTool();
this.registerRefreshStoriesTool();
}
private registerPingTool(): void {
this.server.tool(
ServerConstants.TOOL_PING,
"Simple test tool",
{ message: z.string() },
async ({ message }) => this.toolHandler.handlePing(message)
);
}
private registerGetAllStoriesTool(): void {
this.server.tool(
ServerConstants.TOOL_GET_ALL,
"Get all Jira stories from the project",
{},
async () => this.toolHandler.handleGetAllStories()
);
}
private registerGetStoryTool(): void {
this.server.tool(
ServerConstants.TOOL_GET_STORY,
"Get details of a specific Jira story by key",
{ storyKey: z.string() },
async ({ storyKey }) => this.toolHandler.handleGetStory(storyKey)
);
}
private registerGetStoriesByStatusTool(): void {
this.server.tool(
ServerConstants.TOOL_GET_BY_STATUS,
"Get Jira stories filtered by status",
{ status: z.string() },
async ({ status }) => this.toolHandler.handleGetStoriesByStatus(status)
);
}
private registerSearchStoriesTool(): void {
this.server.tool(
ServerConstants.TOOL_SEARCH,
"Search Jira stories by keyword in title or description",
{ keyword: z.string() },
async ({ keyword }) => this.toolHandler.handleSearchStories(keyword)
);
}
private registerGetOpenStoriesTool(): void {
this.server.tool(
ServerConstants.TOOL_GET_OPEN,
"Get all open/in-progress Jira stories",
{},
async () => this.toolHandler.handleGetOpenStories()
);
}
private registerRefreshStoriesTool(): void {
this.server.tool(
ServerConstants.TOOL_REFRESH,
"Refresh Jira stories from the server",
{},
async () => this.toolHandler.handleRefreshStories()
);
}
}