hataraku
Version:
An autonomous coding agent for building AI-powered development tools. The name "Hataraku" (働く) means "to work" in Japanese.
127 lines • 5.17 kB
JavaScript
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { CallToolRequestSchema, ListToolsRequestSchema, McpError, ErrorCode } from '@modelcontextprotocol/sdk/types.js';
import { TaskToolAdapter } from './task-tool-adapter';
import { createAgent } from '../../agent';
import { createCodeAnalysisTask, createBugAnalysisTask, createPRReviewTask, createRefactoringPlanTask } from '../../sample-tasks';
import { appendFileSync, existsSync, mkdirSync } from 'node:fs';
import path from 'node:path';
import os from 'node:os';
// Should be stored in ~/.hataraku/hataraku-mcp-server-<timestamp>.log
const logDir = path.join(os.homedir(), '.hataraku');
// Create the log directory if it doesn't exist
if (!existsSync(logDir)) {
mkdirSync(logDir, { recursive: true });
}
const logFile = path.join(logDir, `hataraku-mcp-server-${Date.now()}.log`);
function log(message) {
appendFileSync(logFile, message);
}
export class HatarakuMcpServer {
server;
tasks;
adapter;
agent;
constructor(model, defaultAgent) {
this.server = new Server({
name: 'hataraku-mcp-server',
version: '1.0.0',
}, {
capabilities: {
tools: {},
},
});
this.server.onerror = (error) => {
console.error(`Server error: ${error.message}`, { code: error.code, stack: error.stack });
};
this.tasks = new Map();
this.adapter = new TaskToolAdapter();
this.agent = defaultAgent ?? createAgent({
name: 'hataraku-task-agent',
description: 'Agent for executing Hataraku tasks',
role: 'A software development assistant that helps with code analysis, review, and improvement',
model
});
}
async start(transport = new StdioServerTransport()) {
await this.discoverTasks();
await this.registerTools();
await this.server.connect(transport);
return this.server;
}
async discoverTasks() {
// Create instances of all available tasks
const taskFactories = [
createCodeAnalysisTask,
createBugAnalysisTask,
createPRReviewTask,
createRefactoringPlanTask
];
// Initialize tasks with the default agent
for (const factory of taskFactories) {
const task = factory(this.agent);
this.tasks.set(task.name, task);
}
}
async registerTools() {
// Register tasks as MCP tools
this.server.setRequestHandler(ListToolsRequestSchema, async () => {
const tools = Array.from(this.tasks.values()).map(task => {
const tool = this.adapter.convertToMcpTool(task);
return {
name: tool.name,
description: tool.description,
inputSchema: {
type: 'object',
properties: {
content: {
type: 'string',
description: 'The input content for the task'
}
},
required: ['content']
}
};
});
return {
tools,
_meta: {}
};
});
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
const task = this.tasks.get(request.params.name);
if (!task) {
throw new McpError(ErrorCode.MethodNotFound, `Tool not found: ${request.params.name}`);
}
try {
const logEntry = `Calling task: ${task.name}\nRequest params: ${JSON.stringify(request.params)}\n`;
log(logEntry);
const args = request.params.arguments || {};
if (!args.content || typeof args.content !== 'string') {
throw new McpError(ErrorCode.InvalidParams, 'Missing or invalid content parameter');
}
const tool = this.adapter.convertToMcpTool(task);
const result = await tool.execute({ content: args.content });
log(`Task ${task.name} executed with result: ${JSON.stringify(result)}`);
return {
content: [{
type: 'text',
text: typeof result === 'string' ? result : JSON.stringify(result, null, 2)
}],
_meta: {}
};
}
catch (error) {
console.error('Task execution error:', error);
if (error instanceof McpError) {
throw error;
}
throw new McpError(ErrorCode.InvalidRequest, error instanceof Error ? error.message : 'Task execution failed');
}
});
}
async close() {
await this.server.close();
}
}
//# sourceMappingURL=hataraku-mcp-server.js.map