UNPKG

@shirokuma-library/mcp-knowledge-base

Version:

Shirokuma MCP Server for comprehensive knowledge management including issues, plans, documents, and work sessions. All stored data is structured for AI processing, not human readability.

371 lines (370 loc) 14.4 kB
import { TypeRepository } from '../database/type-repository.js'; import { McpError, ErrorCode } from '@modelcontextprotocol/sdk/types.js'; import { GetItemsParams, GetItemDetailParams, CreateItemParams, UpdateItemParams, DeleteItemParams, SearchItemsByTagParams } from '../schemas/unified-schemas.js'; export function createUnifiedHandlers(fileDb) { const itemRepository = fileDb.getItemRepository(); const typeRepository = new TypeRepository(fileDb); (async () => { await typeRepository.init(); })(); async function handleGetItems(params) { const { type, statuses, includeClosedStatuses, start_date, end_date, limit } = params; if (type === 'sessions' && !start_date && !end_date && limit === 1) { const now = new Date(); const year = now.getFullYear(); const month = String(now.getMonth() + 1).padStart(2, '0'); const day = String(now.getDate()).padStart(2, '0'); const today = `${year}-${month}-${day}`; const sessions = await itemRepository.getItems(type, includeClosedStatuses, statuses, today, today, limit); return sessions; } return itemRepository.getItems(type, includeClosedStatuses, statuses, start_date, end_date, limit); } async function handleGetItemDetail(params) { const { type, id } = params; const item = await itemRepository.getItem(type, String(id)); if (!item) { throw new McpError(ErrorCode.InvalidRequest, `${type} with ID ${id} not found. Use 'get_items' with type='${type}' to see available items.`); } return item; } async function handleCreateItem(params) { if (params.version) { console.log('unified-handlers: Received version field', { type: params.type, version: params.version }); } return itemRepository.createItem(params); } async function handleUpdateItem(params) { const { type, id, ...updateData } = params; const updated = await itemRepository.updateItem({ type, id: String(id), ...updateData }); if (!updated) { throw new McpError(ErrorCode.InvalidRequest, `${type} with ID ${id} not found. Use 'get_items' with type='${type}' to see available items.`); } return updated; } async function handleDeleteItem(params) { const { type, id } = params; const deleted = await itemRepository.deleteItem(type, String(id)); if (!deleted) { throw new McpError(ErrorCode.InvalidRequest, `${type} with ID ${id} not found. Use 'get_items' with type='${type}' to see available items.`); } return `${type} ID ${id} deleted`; } async function handleSearchItemsByTag(params) { const { tag, types } = params; const listItems = await itemRepository.searchItemsByTag(tag, types); const items = listItems.map(item => ({ ...item, content: '', status_id: 1, priority: item.priority || 'medium', start_date: null, end_date: null, start_time: null, related: [], created_at: item.updated_at })); const result = { tasks: {}, documents: {} }; const uniqueTypes = [...new Set(items.map(item => item.type))]; const typeInfos = await Promise.all(uniqueTypes.map(async (type) => { const baseType = await typeRepository.getBaseType(type); return { type, baseType: baseType || 'documents' }; })); const typeToBaseType = new Map(typeInfos.map(info => [info.type, info.baseType])); for (const item of items) { const baseType = typeToBaseType.get(item.type) || 'documents'; if (item.type === 'sessions') { if (!result.tasks[item.type]) { result.tasks[item.type] = []; } result.tasks[item.type].push(item); } else if (baseType === 'tasks') { if (!result.tasks[item.type]) { result.tasks[item.type] = []; } result.tasks[item.type].push(item); } else { if (!result.documents[item.type]) { result.documents[item.type] = []; } result.documents[item.type].push(item); } } return result; } return { get_items: handleGetItems, get_item_detail: handleGetItemDetail, create_item: handleCreateItem, update_item: handleUpdateItem, delete_item: handleDeleteItem, search_items_by_tag: handleSearchItemsByTag }; } export const unifiedTools = [ { name: 'get_items', description: 'Retrieve list of items by type. Tasks types support status filtering. Document types return all items.', inputSchema: { type: 'object', properties: { type: { type: 'string', description: 'Type of items to retrieve (use get_types to see available types)' }, statusIds: { type: 'array', items: { type: 'number' }, description: 'Filter by specific status IDs (tasks types only)' }, includeClosedStatuses: { type: 'boolean', description: 'Include items with closed statuses (tasks types only, default: false)' }, start_date: { type: 'string', description: 'Filter by start date (sessions/dailies) or updated_at (other types) from this date' }, end_date: { type: 'string', description: 'Filter by start date (sessions/dailies) or updated_at (other types) until this date' }, limit: { type: 'number', description: 'Maximum number of items to return (useful for getting latest item)' } }, required: ['type'] } }, { name: 'get_item_detail', description: 'Get detailed information for specified item.', inputSchema: { type: 'object', properties: { type: { type: 'string', description: 'Type of item (use get_types to see available types)' }, id: { type: ['string', 'number'], description: 'Item ID' } }, required: ['type', 'id'] } }, { name: 'create_item', description: 'Create new item.', inputSchema: { type: 'object', properties: { type: { type: 'string', description: 'Type of item to create (use get_types to see available types)' }, title: { type: 'string', description: 'Item title (required)' }, description: { type: 'string', description: 'Item description (optional for all types)' }, content: { type: 'string', description: 'Item content (required for document types)' }, priority: { type: 'string', enum: ['high', 'medium', 'low'], description: 'Priority (for tasks types)' }, status: { type: 'string', description: 'Status name (for tasks types)' }, tags: { type: 'array', items: { type: 'string' }, description: 'Array of tag names' }, related_documents: { type: 'array', items: { type: 'string' }, description: 'Related document references (for all types, e.g. ["docs-1", "knowledge-2"])' }, related_tasks: { type: 'array', items: { type: 'string' }, description: 'Related task references (for tasks types, e.g. ["issues-1", "plans-2"])' }, start_date: { type: 'string', description: 'Start date in YYYY-MM-DD format (for tasks types)' }, end_date: { type: 'string', description: 'End date in YYYY-MM-DD format (for tasks types)' }, datetime: { type: 'string', description: 'ISO 8601 datetime (for sessions, optional for past data migration)' }, date: { type: 'string', description: 'Date in YYYY-MM-DD format (for dailies)' }, id: { type: 'string', description: 'Custom ID (for sessions, optional)', pattern: '^[a-zA-Z0-9\\-_.]+$' }, category: { type: 'string', description: 'Category (for sessions, optional)' } }, required: ['type', 'title'] } }, { name: 'update_item', description: 'Update existing item.', inputSchema: { type: 'object', properties: { type: { type: 'string', description: 'Type of item to update (use get_types to see available types)' }, id: { type: ['string', 'number'], description: 'Item ID' }, title: { type: 'string', description: 'New title' }, description: { type: 'string', description: 'New description' }, content: { type: 'string', description: 'New content (for document types)' }, priority: { type: 'string', enum: ['high', 'medium', 'low'], description: 'New priority (for tasks types)' }, status: { type: 'string', description: 'New status name (for tasks types)' }, tags: { type: 'array', items: { type: 'string' }, description: 'New array of tag names' }, related_documents: { type: 'array', items: { type: 'string' }, description: 'New related document references (for all types, e.g. ["docs-1", "knowledge-2"])' }, related_tasks: { type: 'array', items: { type: 'string' }, description: 'New related task references (for tasks types, e.g. ["issues-1", "plans-2"])' }, start_date: { type: ['string', 'null'], description: 'New start date (for tasks types)' }, end_date: { type: ['string', 'null'], description: 'New end date (for tasks types)' } }, required: ['type', 'id'] } }, { name: 'delete_item', description: 'Delete item.', inputSchema: { type: 'object', properties: { type: { type: 'string', description: 'Type of item to delete (use get_types to see available types)' }, id: { type: ['string', 'number'], description: 'Item ID' } }, required: ['type', 'id'] } }, { name: 'search_items_by_tag', description: 'Search items by tag, optionally filtered by types.', inputSchema: { type: 'object', properties: { tag: { type: 'string', description: 'Tag name to search for' }, types: { type: 'array', items: { type: 'string' }, description: 'Types to search (omit to search all types, use get_types to see available types)' } }, required: ['tag'] } } ]; export async function handleUnifiedToolCall(name, args, handlers) { let result; switch (name) { case 'get_items': result = await handlers.get_items(GetItemsParams.parse(args)); break; case 'get_item_detail': result = await handlers.get_item_detail(GetItemDetailParams.parse(args)); break; case 'create_item': result = await handlers.create_item(CreateItemParams.parse(args)); break; case 'update_item': result = await handlers.update_item(UpdateItemParams.parse(args)); break; case 'delete_item': result = await handlers.delete_item(DeleteItemParams.parse(args)); break; case 'search_items_by_tag': result = await handlers.search_items_by_tag(SearchItemsByTagParams.parse(args)); break; default: throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: ${name}`); } return { content: [ { type: 'text', text: JSON.stringify({ data: result }) } ] }; }