Something went wrong. Try again.
🐱 Medium-horizon agent planning MCP server
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292/** * MCP Server factory * * Creates and configures the 9plan MCP server with all tools and prompts. * Manages session state across tool calls. */
import { z } from "zod";import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";import type { SessionStore } from "./db/session-store.js";import { createNewSession, resumeSession, getSessionPath,} from "./container.js";import { logger } from "./logger.js";import { NinePlanError, formatResponse } from "./types.js";
// Import all toolsimport { sessionCreateToolConfig, handleSessionResume, sessionResumeToolConfig, handleQueueAdd, queueAddToolConfig, handleQueuePull, queuePullToolConfig, handlePlanDefer, planDeferToolConfig, handlePlanComplete, planCompleteToolConfig, handlePlanDiscard, planDiscardToolConfig, handleHistorySearch, historySearchToolConfig, handleHistoryGet, historyGetToolConfig,} from "./tools/index.js";
// Import promptsimport { BOOTSTRAP_PROMPT_CONTENT } from "./prompts/bootstrap.js";
/* eslint-disable @typescript-eslint/no-unnecessary-condition */
/** * Session state manager * Tracks the currently active session across tool calls */class SessionManager { private currentStore: SessionStore | null = null; private currentSessionName: string | null = null;
/** * Set the current session */ setSession(sessionName: string, store: SessionStore): void { // Close previous session if exists this.close(); this.currentSessionName = sessionName; this.currentStore = store; logger.info({ sessionName }, "Session activated"); }
/** * Get the current session store, throwing if none active */ getStore(): SessionStore { if (!this.currentStore || !this.currentSessionName) { throw new NinePlanError( "SESSION_NOT_FOUND", "No session is currently active", "Use 9plan_session_create to create a new session, or 9plan_session_resume to load an existing one.", ); } return this.currentStore; }
/** * Get the current session name */ getSessionName(): string { if (!this.currentSessionName) { throw new NinePlanError( "SESSION_NOT_FOUND", "No session is currently active", "Use 9plan_session_create to create a new session, or 9plan_session_resume to load an existing one.", ); } return this.currentSessionName; }
/** * Check if a session is active */ hasSession(): boolean { return this.currentStore !== null && this.currentSessionName !== null; }
/** * Close the current session */ close(): void { if (this.currentStore) { this.currentStore.close(); logger.info({ sessionName: this.currentSessionName }, "Session closed"); } this.currentStore = null; this.currentSessionName = null; }}
/** * Create the MCP server with all tools registered */export function createServer(): McpServer { const server = new McpServer({ name: "9plan", version: "1.0.0", });
const sessionManager = new SessionManager();
// Register session create tool - handles session creation internally server.registerTool( "9plan_session_create", sessionCreateToolConfig, (args) => { const taskDescription = args.task_description; const { sessionName, store } = createNewSession(taskDescription); const sessionPath = getSessionPath(sessionName);
sessionManager.setSession(sessionName, store);
const response = formatResponse( sessionName, `Session created: ${sessionName}Directory: ${sessionPath}
The session is ready. Use 9plan_queue_add to add plans, then 9plan_queue_pull to begin work.`, );
return { content: [{ type: "text" as const, text: response }], }; }, );
// Register session resume tool server.registerTool( "9plan_session_resume", sessionResumeToolConfig, (args) => { const sessionName = args.session_name; const store = resumeSession(sessionName); sessionManager.setSession(sessionName, store); return handleSessionResume({ session_name: sessionName }); }, );
// Register queue tools server.registerTool("9plan_queue_add", queueAddToolConfig, (args) => { const store = sessionManager.getStore(); const sessionName = sessionManager.getSessionName(); const queueAddArgs = { context: args.context, goal: args.goal, approach: args.approach, success_criteria: args.success_criteria, testing: args.testing, ...(args.inputs !== undefined && { inputs: args.inputs }), ...(args.outputs !== undefined && { outputs: args.outputs }), ...(args.position !== undefined && { position: args.position, }), }; return handleQueueAdd(store, sessionName, queueAddArgs); });
server.registerTool( "9plan_queue_pull", queuePullToolConfig, (_args) => { const store = sessionManager.getStore(); const sessionName = sessionManager.getSessionName(); return handleQueuePull(store, sessionName, {}); }, );
// Register plan lifecycle tools server.registerTool("9plan_plan_defer", planDeferToolConfig, (args) => { const store = sessionManager.getStore(); const sessionName = sessionManager.getSessionName(); const deferArgs = { reason: args.reason, ...(args.position !== undefined && { position: args.position, }), }; return handlePlanDefer(store, sessionName, deferArgs); });
server.registerTool( "9plan_plan_complete", planCompleteToolConfig, (args) => { const store = sessionManager.getStore(); const sessionName = sessionManager.getSessionName(); return handlePlanComplete(store, sessionName, { outcome: args.outcome, }); }, );
server.registerTool( "9plan_plan_discard", planDiscardToolConfig, (args) => { const store = sessionManager.getStore(); const sessionName = sessionManager.getSessionName(); return handlePlanDiscard(store, sessionName, { reason: args.reason, }); }, );
// Register history tools server.registerTool( "9plan_history_search", historySearchToolConfig, (args) => { const store = sessionManager.getStore(); const sessionName = sessionManager.getSessionName(); const searchArgs = { query: args.query, ...(args.max_results !== undefined && { max_results: args.max_results, }), }; return handleHistorySearch(store, sessionName, searchArgs); }, );
server.registerTool( "9plan_history_get", historyGetToolConfig, (args) => { const store = sessionManager.getStore(); const sessionName = sessionManager.getSessionName(); return handleHistoryGet(store, sessionName, { plan_id: args.plan_id, }); }, );
// Register bootstrap prompt server.registerPrompt( "bootstrap", { description: "Set up a new 9plan session for a complex task. Guides you through clarifying requirements, decomposing into plans, and capturing dependencies.", argsSchema: { task: z .string() .optional() .describe("Optional initial task description to start with"), }, }, (args) => { const taskArg = args.task; const taskText = taskArg ? `\n\nThe user has provided an initial task: "${taskArg}"\n\nStart by clarifying the task and then proceed with the workflow.` : "";
return { messages: [ { role: "user" as const, content: { type: "text" as const, text: BOOTSTRAP_PROMPT_CONTENT + taskText, }, }, ], }; }, );
logger.info("9plan MCP server initialized with 9 tools and 1 prompt");
return server;}