Skip to main content
Glama
server.ts3.89 kB
#!/usr/bin/env node import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { z } from 'zod'; import { createTask, STATUSES, type Task } from './schema.js'; import { loadBacklog, getTask, listTasks, addTask, saveTask, getTaskCounts, type StorageOptions } from './storage.js'; // ============================================================================ // Server // ============================================================================ const server = new McpServer({ name: 'backlog-mcp', version: '0.1.0', }); const storageOptions: StorageOptions = { dataDir: process.env.BACKLOG_DATA_DIR ?? 'data', }; // ============================================================================ // Tools // ============================================================================ server.registerTool( 'backlog_list', { description: 'List tasks, optionally filtered by status. Use summary=true for counts.', inputSchema: { status: z.array(z.enum(STATUSES)).optional().describe('Filter by status'), summary: z.boolean().optional().describe('Return counts instead of list'), }, }, async ({ status, summary }) => { const tasks = listTasks(status ? { status } : undefined, storageOptions); if (summary) { const counts = getTaskCounts(storageOptions); return { content: [{ type: 'text' as const, text: JSON.stringify(counts, null, 2) }] }; } const list = tasks.map((t) => ({ id: t.id, title: t.title, status: t.status })); return { content: [{ type: 'text' as const, text: JSON.stringify(list, null, 2) }] }; } ); server.registerTool( 'backlog_get', { description: 'Get a task by ID', inputSchema: { id: z.string().describe('Task ID') }, }, async ({ id }) => { const task = getTask(id, storageOptions); if (!task) { return { content: [{ type: 'text' as const, text: `Not found: ${id}` }], isError: true }; } return { content: [{ type: 'text' as const, text: JSON.stringify(task, null, 2) }] }; } ); server.registerTool( 'backlog_create', { description: 'Create a new task', inputSchema: { title: z.string().describe('Task title'), description: z.string().optional().describe('Task description'), }, }, async ({ title, description }) => { const backlog = loadBacklog(storageOptions); const task = createTask({ title, description }, backlog.tasks); addTask(task, storageOptions); return { content: [{ type: 'text' as const, text: `Created ${task.id}` }] }; } ); server.registerTool( 'backlog_update', { description: 'Update a task (any field)', inputSchema: { id: z.string().describe('Task ID'), title: z.string().optional(), description: z.string().optional(), status: z.enum(STATUSES).optional(), blocked_reason: z.string().optional(), evidence: z.array(z.string()).optional(), }, }, async ({ id, ...updates }) => { const task = getTask(id, storageOptions); if (!task) { return { content: [{ type: 'text' as const, text: `Not found: ${id}` }], isError: true }; } const updated: Task = { ...task, ...Object.fromEntries(Object.entries(updates).filter(([_, v]) => v !== undefined)), updated_at: new Date().toISOString(), }; saveTask(updated, storageOptions); return { content: [{ type: 'text' as const, text: `Updated ${id}` }] }; } ); // ============================================================================ // Main // ============================================================================ async function main() { const transport = new StdioServerTransport(); await server.connect(transport); } main().catch((error) => { console.error('Fatal error:', error); process.exit(1); });

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/gkoreli/backlog-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server