UNPKG

jrnl-mcp

Version:

Model Context Protocol server for jrnl CLI journal application

228 lines (227 loc) 8.77 kB
#!/usr/bin/env node "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const index_js_1 = require("@modelcontextprotocol/sdk/server/index.js"); const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js"); const types_js_1 = require("@modelcontextprotocol/sdk/types.js"); const jrnlExecutor_js_1 = require("./utils/jrnlExecutor.js"); const entryHandlers_js_1 = require("./handlers/entryHandlers.js"); const tagHandlers_js_1 = require("./handlers/tagHandlers.js"); const statisticsHandlers_js_1 = require("./handlers/statisticsHandlers.js"); const journalHandlers_js_1 = require("./handlers/journalHandlers.js"); const server = new index_js_1.Server({ name: "jrnl-mcp", version: "1.0.0", }, { capabilities: { tools: {}, }, }); const executor = new jrnlExecutor_js_1.JrnlExecutor(); server.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => ({ tools: [ { name: "search_entries", description: "Search and filter journal entries", inputSchema: { type: "object", properties: { from: { type: "string", description: 'Start date (e.g., "yesterday", "2024-01-01")', }, to: { type: "string", description: "End date" }, tags: { type: "array", items: { type: "string" }, description: "Tags to filter by", }, contains: { type: "string", description: "Text to search for" }, limit: { type: "number", description: "Maximum number of entries" }, starred: { type: "boolean", description: "Only show starred entries", }, journal: { type: "string", description: "Journal name (uses current/default if not specified)", }, }, }, }, { name: "list_tags", description: "List all tags with their usage counts", inputSchema: { type: "object", properties: { journal: { type: "string", description: "Journal name (uses current/default if not specified)", }, }, }, }, { name: "analyze_tag_cooccurrence", description: "Analyze which tags frequently appear together", inputSchema: { type: "object", properties: { tags: { type: "array", items: { type: "string" }, description: "Tags to analyze for co-occurrence", minItems: 2, }, journal: { type: "string", description: "Journal name (uses current/default if not specified)", }, }, required: ["tags"], }, }, { name: "get_statistics", description: "Get journal statistics and analytics", inputSchema: { type: "object", properties: { journal: { type: "string", description: "Journal name (uses current/default if not specified)", }, timeGrouping: { type: "string", enum: ["day", "week", "month", "year"], description: "Group statistics by time period", }, includeTopTags: { type: "boolean", description: "Include top tags in statistics", }, }, }, }, { name: "list_journals", description: "List all available journals", inputSchema: { type: "object", properties: {}, }, }, { name: "set_journal", description: "Set the active journal for subsequent operations", inputSchema: { type: "object", properties: { journalName: { type: "string", description: "Name of the journal to set as active", }, }, required: ["journalName"], }, }, ], })); server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => { const { name, arguments: args } = request.params; try { const journal = (typeof args?.journal === "string" ? args.journal : undefined) || (0, journalHandlers_js_1.getCurrentJournal)(); switch (name) { case "search_entries": return { content: [ { type: "text", text: JSON.stringify(await (0, entryHandlers_js_1.searchEntries)({ ...args, journal }, executor), null, 2), }, ], }; case "list_tags": return { content: [ { type: "text", text: JSON.stringify(await (0, tagHandlers_js_1.listTags)(journal, executor), null, 2), }, ], }; case "analyze_tag_cooccurrence": return { content: [ { type: "text", text: JSON.stringify(await (0, tagHandlers_js_1.analyzeTagCooccurrence)(Array.isArray(args?.tags) ? args.tags : [], journal, executor), null, 2), }, ], }; case "get_statistics": return { content: [ { type: "text", text: JSON.stringify(await (0, statisticsHandlers_js_1.getStatistics)(journal, typeof args?.timeGrouping === "string" ? args.timeGrouping : undefined, typeof args?.includeTopTags === "boolean" ? args.includeTopTags : true, executor), null, 2), }, ], }; case "list_journals": return { content: [ { type: "text", text: JSON.stringify(await (0, journalHandlers_js_1.listJournals)(executor), null, 2), }, ], }; case "set_journal": return { content: [ { type: "text", text: JSON.stringify(await (0, journalHandlers_js_1.setJournal)(typeof args?.journalName === "string" ? args.journalName : ""), null, 2), }, ], }; default: throw new Error(`Unknown tool: ${name}`); } } catch (error) { // Log errors to stderr for debugging process.stderr.write(`[${new Date().toISOString()}] Error in tool ${name}: ${error instanceof Error ? error.message : String(error)}\n`); return { content: [ { type: "text", text: `Error: ${error instanceof Error ? error.message : String(error)}`, }, ], }; } }); async function main() { try { const transport = new stdio_js_1.StdioServerTransport(); await server.connect(transport); // Server started - logging to stderr for debugging process.stderr.write(`[${new Date().toISOString()}] jrnl MCP server started\n`); } catch (error) { process.stderr.write(`[${new Date().toISOString()}] Failed to start MCP server: ${error}\n`); process.exit(1); } } main().catch((error) => { process.stderr.write(`[${new Date().toISOString()}] Server error: ${error}\n`); process.exit(1); });