hataraku
Version:
An autonomous coding agent for building AI-powered development tools. The name "Hataraku" (働く) means "to work" in Japanese.
283 lines • 12 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.McpClient = void 0;
const fs = __importStar(require("fs/promises"));
const path = __importStar(require("path"));
const os = __importStar(require("os"));
const index_js_1 = require("@modelcontextprotocol/sdk/client/index.js");
const stdio_js_1 = require("@modelcontextprotocol/sdk/client/stdio.js");
const types_js_1 = require("@modelcontextprotocol/sdk/types.js");
class McpClient {
settingsPath;
connections = [];
initialized = false;
constructor() {
this.settingsPath = path.join(os.homedir(), '.hataraku', 'mcp_settings.json');
}
async readSettings() {
try {
// Ensure the directory exists
await fs.mkdir(path.dirname(this.settingsPath), { recursive: true });
// Try to read the settings file
try {
const content = await fs.readFile(this.settingsPath, 'utf-8');
return JSON.parse(content);
}
catch (error) {
// If file doesn't exist, create it with default settings
if (error.code === 'ENOENT') {
const defaultSettings = { mcpServers: {} };
await fs.writeFile(this.settingsPath, JSON.stringify(defaultSettings, null, 2));
console.log(`Created new MCP settings file at ${this.settingsPath}`);
return defaultSettings;
}
throw error;
}
}
catch (error) {
console.error('Error accessing MCP settings:', error);
return { mcpServers: {} };
}
}
async connectToServer(name, config) {
// Remove existing connection if it exists
this.connections = this.connections.filter((conn) => conn.server.name !== name);
try {
const client = new index_js_1.Client({
name: "Hataraku",
version: "1.0.0",
}, {
capabilities: {},
});
const transport = new stdio_js_1.StdioClientTransport({
command: config.command,
args: config.args,
env: {
...config.env,
...(process.env.PATH ? { PATH: process.env.PATH } : {}),
},
stderr: "pipe",
});
const connection = {
server: {
name,
config: JSON.stringify(config),
status: "connecting",
disabled: config.disabled === true,
},
client,
transport,
};
this.connections.push(connection);
// Set up stderr handling before connecting
const stderrStream = transport.stderr;
if (stderrStream) {
stderrStream.on("data", (data) => {
const errorOutput = data.toString();
if (connection.server.error) {
connection.server.error += '\n' + errorOutput;
}
else {
connection.server.error = errorOutput;
}
});
}
// Connect to the server - this will start the transport
await client.connect(transport);
connection.server.status = "connected";
connection.server.error = "";
// Initial fetch of tools and resources
connection.server.tools = await this.fetchToolsList(name);
connection.server.resources = await this.fetchResourcesList(name);
connection.server.resourceTemplates = await this.fetchResourceTemplatesList(name);
}
catch (error) {
const connection = this.connections.find((conn) => conn.server.name === name);
if (connection) {
connection.server.status = "disconnected";
connection.server.error = error instanceof Error ? error.message : String(error);
}
throw error;
}
}
async fetchToolsList(serverName) {
try {
const response = await this.connections
.find((conn) => conn.server.name === serverName)
?.client.request({ method: "tools/list" }, types_js_1.ListToolsResultSchema);
const settings = await this.readSettings();
const alwaysAllowConfig = settings.mcpServers[serverName]?.alwaysAllow || [];
const tools = (response?.tools || []).map(tool => ({
...tool,
alwaysAllow: alwaysAllowConfig.includes(tool.name)
}));
return tools;
}
catch (error) {
return [];
}
}
async fetchResourcesList(serverName) {
try {
const response = await this.connections
.find((conn) => conn.server.name === serverName)
?.client.request({ method: "resources/list" }, types_js_1.ListResourcesResultSchema);
return response?.resources || [];
}
catch (error) {
return [];
}
}
async fetchResourceTemplatesList(serverName) {
try {
const response = await this.connections
.find((conn) => conn.server.name === serverName)
?.client.request({ method: "resources/templates/list" }, types_js_1.ListResourceTemplatesResultSchema);
return response?.resourceTemplates || [];
}
catch (error) {
return [];
}
}
async initializeServers() {
if (this.initialized) {
return;
}
const settings = await this.readSettings();
for (const [name, config] of Object.entries(settings.mcpServers)) {
if (config.disabled !== true) {
try {
await this.connectToServer(name, config);
}
catch (error) {
console.error(`Failed to connect to MCP server ${name}:`, error);
}
}
}
this.initialized = true;
}
async callTool(serverName, toolName, args) {
if (!this.initialized) {
throw new Error('MCP servers have not been initialized. Call initializeServers() first.');
}
// First check if we have any connections
if (this.connections.length === 0) {
throw new Error('No MCP servers are connected. Make sure servers are properly configured and enabled.');
}
// List available servers for better error messages
const availableServers = this.connections
.filter(conn => !conn.server.disabled)
.map(conn => conn.server.name);
if (!serverName) {
throw new Error(`Server name is required. Available servers: ${availableServers.join(', ') || 'None'}`);
}
const connection = this.connections.find((conn) => conn.server.name === serverName);
if (!connection) {
throw new Error(`Server "${serverName}" not found. Available servers: ${availableServers.join(', ') || 'None'}`);
}
if (connection.server.disabled) {
throw new Error(`Server "${serverName}" is disabled`);
}
if (connection.server.status !== 'connected') {
throw new Error(`Server "${serverName}" is not connected (status: ${connection.server.status})`);
}
// List available tools for better error messages
const availableTools = connection.server.tools?.map(t => t.name) || [];
if (!toolName) {
throw new Error(`Tool name is required. Available tools for ${serverName}: ${availableTools.join(', ') || 'None'}`);
}
if (availableTools.length > 0 && !availableTools.includes(toolName)) {
throw new Error(`Tool "${toolName}" not found in server "${serverName}". Available tools: ${availableTools.join(', ')}`);
}
return await connection.client.request({
method: "tools/call",
params: {
name: toolName,
arguments: args,
},
}, types_js_1.CallToolResultSchema);
}
async readResource(serverName, uri) {
if (!this.initialized) {
throw new Error('MCP servers have not been initialized. Call initializeServers() first.');
}
const connection = this.connections.find((conn) => conn.server.name === serverName);
if (!connection) {
throw new Error(`No connection found for server: ${serverName}`);
}
if (connection.server.disabled) {
throw new Error(`Server "${serverName}" is disabled`);
}
return await connection.client.request({
method: "resources/read",
params: {
uri,
},
}, types_js_1.ReadResourceResultSchema);
}
getAvailableServers() {
return this.connections
.filter(conn => !conn.server.disabled && conn.server.status === 'connected')
.map(conn => conn.server.name);
}
async getServerTools() {
if (!this.initialized) {
throw new Error('MCP servers have not been initialized. Call initializeServers() first.');
}
const tools = [];
for (const connection of this.connections) {
if (!connection.server.disabled && connection.server.status === 'connected') {
const serverTools = await this.fetchToolsList(connection.server.name);
if (serverTools.length > 0) {
let serverToolXml = `<server name="${connection.server.name}">\n`;
serverToolXml += ` <tools>\n`;
for (const tool of serverTools) {
serverToolXml += ` <tool name="${tool.name}">\n`;
serverToolXml += ` <description>${tool.description}</description>\n`;
serverToolXml += ` <inputSchema>${JSON.stringify(tool.inputSchema)}</inputSchema>\n`;
serverToolXml += ` </tool>\n`;
}
serverToolXml += ` </tools>\n`;
serverToolXml += `</server>\n`;
tools.push(serverToolXml);
}
}
}
return tools;
}
}
exports.McpClient = McpClient;
//# sourceMappingURL=McpClientOld.js.map