@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.
199 lines (198 loc) • 8.58 kB
JavaScript
if (!process.env.NODE_ENV || process.env.NODE_ENV === 'production') {
process.env.MCP_MODE = 'production';
process.env.NODE_ENV = 'production';
process.env.LOG_LEVEL = 'silent';
}
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { CallToolRequestSchema, ErrorCode, ListToolsRequestSchema, McpError } from '@modelcontextprotocol/sdk/types.js';
import { FileIssueDatabase } from './database.js';
import { getConfig } from './config.js';
import { createUnifiedHandlers, handleUnifiedToolCall } from './handlers/unified-handlers.js';
import { StatusHandlers } from './handlers/status-handlers.js';
import { guardStdio } from './utils/stdio-guard.js';
import { TagHandlers } from './handlers/tag-handlers.js';
import { TypeHandlers } from './handlers/type-handlers.js';
import { SearchHandlers } from './handlers/search-handlers.js';
import { CurrentStateHandlers } from './handlers/current-state-handlers.js';
import { ChangeTypeHandlers } from './handlers/change-type-handlers.js';
import { FileIndexHandlers, fileIndexSchemas } from './handlers/file-index-handlers.js';
import { toolDefinitions } from './tool-definitions.js';
import { checkDatabaseVersion, VersionMismatchError } from './utils/db-version-utils.js';
import { createLogger } from './utils/logger.js';
export class IssueTrackerServer {
server;
db;
unifiedHandlers;
statusHandlers;
tagHandlers;
typeHandlers;
searchHandlers;
currentStateHandlers;
versionError = null;
changeTypeHandlers;
fileIndexHandlers;
checkVersionError() {
if (this.versionError) {
throw new McpError(ErrorCode.InternalError, `Database version mismatch: ${this.versionError.message}. ` +
'Please update your database or application to matching versions.');
}
}
constructor() {
const config = getConfig();
this.db = new FileIssueDatabase(config.database.path);
this.statusHandlers = new StatusHandlers(this.db);
this.tagHandlers = new TagHandlers(this.db);
this.typeHandlers = new TypeHandlers(this.db);
this.searchHandlers = new SearchHandlers(this.db);
this.currentStateHandlers = new CurrentStateHandlers(config.database.path);
this.changeTypeHandlers = new ChangeTypeHandlers(this.db);
this.fileIndexHandlers = new FileIndexHandlers(this.db);
this.server = new Server({
name: 'shirokuma-ai-project-management-server',
version: '1.0.0'
}, {
capabilities: {
tools: {}
}
});
this.setupToolHandlers();
this.server.onerror = (error) => {
};
process.on('SIGINT', async () => {
await this.server.close();
this.db.close();
process.exit(0);
});
}
setupToolHandlers() {
this.setupToolList();
this.setupCallHandlers();
}
setupToolList() {
this.server.setRequestHandler(ListToolsRequestSchema, async () => {
return { tools: toolDefinitions };
});
}
setupCallHandlers() {
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
try {
return await this.handleToolCall(request.params.name, request.params.arguments);
}
catch (error) {
if (error instanceof McpError) {
throw error;
}
const errorMessage = error instanceof Error ? error.message : String(error);
throw new McpError(ErrorCode.InternalError, errorMessage);
}
});
}
async handleToolCall(toolName, args) {
this.checkVersionError();
switch (toolName) {
case 'get_items':
case 'get_item_detail':
case 'create_item':
case 'update_item':
case 'delete_item':
case 'search_items_by_tag':
if (!this.unifiedHandlers) {
throw new McpError(ErrorCode.InternalError, 'Server not fully initialized');
}
return handleUnifiedToolCall(toolName, args, this.unifiedHandlers);
case 'get_statuses': return this.statusHandlers.handleGetStatuses();
case 'get_tags': return this.tagHandlers.handleGetTags();
case 'create_tag': return this.tagHandlers.handleCreateTag(args);
case 'delete_tag': return this.tagHandlers.handleDeleteTag(args);
case 'search_tags': return this.tagHandlers.handleSearchTags(args);
case 'create_type': return this.typeHandlers.handleCreateType(args);
case 'get_types': return this.typeHandlers.handleGetTypes(args);
case 'update_type': return this.typeHandlers.handleUpdateType(args);
case 'delete_type': return this.typeHandlers.handleDeleteType(args);
case 'search_items': return this.searchHandlers.searchItems(args);
case 'search_suggest': return this.searchHandlers.searchSuggest(args);
case 'get_current_state': return this.currentStateHandlers.handleGetCurrentState();
case 'update_current_state': return this.currentStateHandlers.handleUpdateCurrentState(args);
case 'change_item_type': return this.changeTypeHandlers.handleChangeItemType(args);
case 'index_codebase': {
const validated = fileIndexSchemas.index_codebase.parse(args);
return await this.fileIndexHandlers.createHandlers().index_codebase(validated);
}
case 'search_code': {
const validated = fileIndexSchemas.search_code.parse(args);
return await this.fileIndexHandlers.createHandlers().search_code(validated);
}
case 'get_related_files': {
const validated = fileIndexSchemas.get_related_files.parse(args);
return await this.fileIndexHandlers.createHandlers().get_related_files(validated);
}
case 'get_index_status': {
return await this.fileIndexHandlers.createHandlers().get_index_status();
}
default:
throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: ${toolName}`);
}
}
async run() {
await this.db.initialize();
try {
const logger = createLogger('VersionCheck');
const noop = () => logger;
logger.debug = noop;
logger.info = noop;
logger.warn = noop;
logger.error = noop;
await checkDatabaseVersion(this.db.getDatabase(), logger);
}
catch (error) {
if (error instanceof VersionMismatchError) {
this.versionError = error;
if (process.env.NODE_ENV === 'test') {
throw error;
}
}
else {
throw error;
}
}
await this.typeHandlers.init();
this.unifiedHandlers = createUnifiedHandlers(this.db);
const tagRepo = this.db.getTagRepository();
const itemRepo = this.db.getItemRepository();
const validateRelatedItems = async (items) => {
const validItems = [];
for (const itemId of items) {
const match = itemId.match(/^(\w+)-(.+)$/);
if (match) {
const [, type, id] = match;
try {
const item = await itemRepo.getItem(type, id);
if (item) {
validItems.push(itemId);
}
}
catch {
}
}
}
return validItems;
};
this.currentStateHandlers = new CurrentStateHandlers(getConfig().database.path, tagRepo, validateRelatedItems);
const transport = new StdioServerTransport();
await this.server.connect(transport);
}
}
if (process.argv[1] && process.argv[1].endsWith('server.js')) {
process.env.NODE_ENV = 'production';
process.env.LOG_LEVEL = 'silent';
process.env.SQLITE_TRACE = '';
process.env.SQLITE_PROFILE = '';
process.env.DEBUG = '';
guardStdio();
const server = new IssueTrackerServer();
server.run().catch((error) => {
process.exit(1);
});
}