#!/usr/bin/env node
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
CallToolRequestSchema,
ListToolsRequestSchema,
} from "@modelcontextprotocol/sdk/types.js";
import { treehouse } from "./treehouse.js";
const server = new Server(
{
name: "treehouse",
version: "1.0.0",
},
{
capabilities: {
tools: {},
},
}
);
// Define available tools
server.setRequestHandler(ListToolsRequestSchema, async () => {
return {
tools: [
{
name: "treehouse_init",
description: "Initialize treehouse configuration in the current repository",
inputSchema: {
type: "object",
properties: {},
required: [],
},
},
{
name: "treehouse_list",
description: "List all git worktrees with their status and lock information",
inputSchema: {
type: "object",
properties: {},
required: [],
},
},
{
name: "treehouse_create",
description: "Create a new worktree for parallel development. Optionally lock it for an agent.",
inputSchema: {
type: "object",
properties: {
name: {
type: "string",
description: "Name for the worktree (used as directory name)",
},
branch: {
type: "string",
description: "Branch name to use (defaults to the worktree name)",
},
agentId: {
type: "string",
description: "Agent ID to lock the worktree for",
},
lockMessage: {
type: "string",
description: "Optional message describing what the agent is working on",
},
},
required: ["name"],
},
},
{
name: "treehouse_status",
description: "Get detailed status of a specific worktree or all worktrees",
inputSchema: {
type: "object",
properties: {
name: {
type: "string",
description: "Name of the worktree (optional, shows all if omitted)",
},
},
required: [],
},
},
{
name: "treehouse_complete",
description: "Complete work on a worktree, optionally merging changes back",
inputSchema: {
type: "object",
properties: {
name: {
type: "string",
description: "Name of the worktree to complete",
},
merge: {
type: "boolean",
description: "Whether to merge changes into target branch",
},
squash: {
type: "boolean",
description: "Whether to squash commits when merging",
},
targetBranch: {
type: "string",
description: "Branch to merge into (defaults to current branch)",
},
deleteBranch: {
type: "boolean",
description: "Whether to delete the branch after merging",
},
message: {
type: "string",
description: "Commit message for squash merge",
},
},
required: ["name"],
},
},
{
name: "treehouse_remove",
description: "Remove a worktree",
inputSchema: {
type: "object",
properties: {
name: {
type: "string",
description: "Name of the worktree to remove",
},
force: {
type: "boolean",
description: "Force removal even with uncommitted changes",
},
},
required: ["name"],
},
},
{
name: "treehouse_lock",
description: "Lock a worktree to prevent other agents from using it",
inputSchema: {
type: "object",
properties: {
name: {
type: "string",
description: "Name of the worktree to lock",
},
agentId: {
type: "string",
description: "ID of the agent locking the worktree",
},
message: {
type: "string",
description: "Reason for locking",
},
expiryMinutes: {
type: "number",
description: "Lock expiry time in minutes",
},
},
required: ["name"],
},
},
{
name: "treehouse_unlock",
description: "Unlock a worktree to allow other agents to use it",
inputSchema: {
type: "object",
properties: {
name: {
type: "string",
description: "Name of the worktree to unlock",
},
},
required: ["name"],
},
},
{
name: "treehouse_conflicts",
description: "Check for merge conflicts in a worktree or the main repository",
inputSchema: {
type: "object",
properties: {
name: {
type: "string",
description: "Name of the worktree (optional)",
},
},
required: [],
},
},
{
name: "treehouse_resolve",
description: "Resolve merge conflicts using a specific strategy",
inputSchema: {
type: "object",
properties: {
name: {
type: "string",
description: "Name of the worktree (optional)",
},
strategy: {
type: "string",
enum: ["ours", "theirs"],
description: "Conflict resolution strategy",
},
},
required: ["strategy"],
},
},
{
name: "treehouse_abort",
description: "Abort a merge operation in progress",
inputSchema: {
type: "object",
properties: {
name: {
type: "string",
description: "Name of the worktree (optional)",
},
},
required: [],
},
},
{
name: "treehouse_prune",
description: "Clean up orphaned worktree entries",
inputSchema: {
type: "object",
properties: {},
required: [],
},
},
{
name: "treehouse_clean",
description: "Remove old worktrees based on retention policy",
inputSchema: {
type: "object",
properties: {
dryRun: {
type: "boolean",
description: "Preview what would be removed without actually removing",
},
},
required: [],
},
},
],
};
});
// Handle tool calls
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
try {
let result;
switch (name) {
case "treehouse_init":
result = await treehouse.init();
break;
case "treehouse_list":
result = await treehouse.list();
break;
case "treehouse_create":
result = await treehouse.create({
name: args?.name as string,
branch: args?.branch as string | undefined,
agentId: args?.agentId as string | undefined,
lockMessage: args?.lockMessage as string | undefined,
});
break;
case "treehouse_status":
result = await treehouse.status(args?.name as string | undefined);
break;
case "treehouse_complete":
result = await treehouse.complete({
name: args?.name as string,
merge: args?.merge as boolean | undefined,
squash: args?.squash as boolean | undefined,
targetBranch: args?.targetBranch as string | undefined,
deleteBranch: args?.deleteBranch as boolean | undefined,
message: args?.message as string | undefined,
});
break;
case "treehouse_remove":
result = await treehouse.remove(
args?.name as string,
args?.force as boolean | undefined
);
break;
case "treehouse_lock":
result = await treehouse.lock({
name: args?.name as string,
agentId: args?.agentId as string | undefined,
message: args?.message as string | undefined,
expiryMinutes: args?.expiryMinutes as number | undefined,
});
break;
case "treehouse_unlock":
result = await treehouse.unlock(args?.name as string);
break;
case "treehouse_conflicts":
result = await treehouse.conflicts(args?.name as string | undefined);
break;
case "treehouse_resolve":
result = await treehouse.resolveConflicts({
name: args?.name as string | undefined,
strategy: args?.strategy as "ours" | "theirs",
});
break;
case "treehouse_abort":
result = await treehouse.abort(args?.name as string | undefined);
break;
case "treehouse_prune":
result = await treehouse.prune();
break;
case "treehouse_clean":
result = await treehouse.clean({
dryRun: args?.dryRun as boolean | undefined,
});
break;
default:
throw new Error(`Unknown tool: ${name}`);
}
return {
content: [
{
type: "text",
text: JSON.stringify(result, null, 2),
},
],
};
} catch (error: unknown) {
const err = error as Error;
return {
content: [
{
type: "text",
text: JSON.stringify({
success: false,
message: err.message,
}),
},
],
isError: true,
};
}
});
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
console.error("Treehouse MCP server running on stdio");
}
main().catch(console.error);