feat: implement mode/palette system for MCP

- Mode system for context-aware tool filtering (developer, research, editor, terminal, ai_assistant, project_manager)
- Palette system with command shortcuts and mode collections
- Integration with MCP server for automatic tool filtering based on active mode
- Mode management tools (switch, list, create custom modes)
- Palette management tools (select, list, shortcuts)
- Updated README with comprehensive documentation of all 40+ tools
- Full feature parity with Python MCP plus new TypeScript-specific features
This commit is contained in:
Hanzo Dev
2025-07-24 09:05:59 -05:00
parent 9a2d57ca5e
commit fff1520fbb
4 changed files with 685 additions and 16 deletions
+162 -9
View File
@@ -1,6 +1,19 @@
# @hanzo/mcp
TypeScript implementation of Model Context Protocol (MCP) server for AI development tools.
TypeScript implementation of Model Context Protocol (MCP) server with 40+ built-in tools for AI development.
## Features
- 📁 **File Operations**: Read, write, edit, move, delete, and manage files
- 🔍 **Advanced Search**: Grep, ripgrep, file finding, and unified search
- 💻 **Shell Integration**: Execute commands, manage background processes
- ✏️ **Smart Editing**: Single and multi-file editing with precise replacements
- 🌳 **Project Navigation**: Directory trees, file listing, and exploration
- 🧠 **AI Tools**: Think, critic, consensus, and agent delegation capabilities
- 🔎 **Vector Search**: LanceDB integration for multimodal embeddings
- 🌲 **AST Search**: Code intelligence with syntax-aware search
- 📋 **Todo Management**: Task tracking with priorities, tags, and projects
- 🎯 **Mode System**: Context-aware tool filtering and command palettes
## Installation
@@ -25,10 +38,10 @@ hanzo-mcp install-desktop
```typescript
import { createMCPServer } from '@hanzo/mcp';
const server = createMCPServer({
const server = await createMCPServer({
name: 'my-mcp-server',
version: '1.0.0',
tools: [
customTools: [
// Your custom tools
]
});
@@ -36,13 +49,153 @@ const server = createMCPServer({
await server.start();
```
## Features
## Built-in Tools
- File system operations (read, write, search)
- Code execution
- Web fetching
- Integration with Claude Desktop
- Extensible tool system
### File Operations
- `read_file` - Read file contents
- `write_file` - Write or overwrite files
- `edit_file` - Replace text in files
- `multi_edit` - Multiple edits in one operation
- `create_file` - Create new files
- `delete_file` - Delete files
- `move_file` - Move or rename files
- `list_files` - List directory contents
- `tree` - Show directory tree structure
### Search Tools
- `grep` - Pattern search with grep/ripgrep
- `find_files` - Find files by name pattern
- `search` - Unified search combining multiple strategies
- `ast_search` - AST-based code pattern search
- `find_symbol` - Find function/class definitions
- `analyze_dependencies` - Analyze import dependencies
### Shell Tools
- `bash` - Execute shell commands
- `shell` - Cross-platform shell execution
- `background_bash` - Run background processes
- `kill_process` - Terminate processes
- `list_processes` - List running processes
### AI Tools
- `think` - Structured reasoning space
- `critic` - Critical analysis tool
- `consensus` - Multi-model consensus
- `agent` - Delegate tasks to sub-agents
### Vector Search
- `vector_index` - Create vector indexes
- `vector_search` - Search with embeddings
- `vector_stats` - Vector database statistics
### Todo Management
- `todo_add` - Add new todo items
- `todo_list` - List and filter todos
- `todo_update` - Update todo status/details
- `todo_delete` - Delete todos
- `todo_stats` - Todo statistics and productivity
### Mode & Palette System
- `mode_switch` - Switch tool modes
- `mode_list` - List available modes
- `palette_select` - Select command palette
- `palette_list` - List palettes
- `mode_create` - Create custom modes
- `shortcut` - Execute palette shortcuts
## Modes
The MCP server supports different modes that filter available tools:
- **developer** - Full access to all tools
- **research** - Read-only exploration tools
- **editor** - File editing focused tools
- **terminal** - Shell and system operations
- **ai_assistant** - AI-powered assistance tools
- **project_manager** - Task and project management
## Configuration
### Environment Variables
```bash
# Vector search database path
HANZO_VECTOR_DB=/path/to/vector.db
# Todo storage path
HANZO_TODO_PATH=/path/to/todos.json
# AI Provider Keys (for AI tools)
OPENAI_API_KEY=sk-...
ANTHROPIC_API_KEY=sk-ant-...
GOOGLE_API_KEY=...
```
### Custom Tools
```typescript
import { Tool, createMCPServer } from '@hanzo/mcp';
const myTool: Tool = {
name: 'my_tool',
description: 'My custom tool',
inputSchema: {
type: 'object',
properties: {
input: { type: 'string' }
},
required: ['input']
},
handler: async (args) => {
return {
content: [{
type: 'text',
text: `Processed: ${args.input}`
}]
};
}
};
const server = await createMCPServer({
customTools: [myTool]
});
```
## Integration with Claude Desktop
1. Install the MCP server:
```bash
npm install -g @hanzo/mcp
```
2. Configure Claude Desktop:
```bash
hanzo-mcp install-desktop
```
This adds the server to your Claude Desktop configuration at:
- macOS: `~/Library/Application Support/Claude/claude_desktop_config.json`
- Windows: `%APPDATA%\Claude\claude_desktop_config.json`
## Development
```bash
# Clone the repository
git clone https://github.com/hanzoai/mcp.git
cd mcp
# Install dependencies
npm install
# Build
npm run build
# Run tests
npm test
# Development mode
npm run dev
```
## License
+26 -5
View File
@@ -41,8 +41,8 @@ export async function createMCPServer(config?: {
customTools = []
} = config || {};
// Import tools
const { allTools, toolMap } = await import('./tools/index.js');
// Import tools and mode utils
const { allTools, toolMap, modeUtils } = await import('./tools/index.js');
// Combine built-in and custom tools
const combinedTools = [...allTools, ...customTools];
@@ -58,10 +58,18 @@ export async function createMCPServer(config?: {
}
);
// Handle tool listing
// Handle tool listing with mode filtering
server.setRequestHandler(ListToolsRequestSchema, async () => {
// Get available tools based on current mode
const availableToolNames = modeUtils.getAvailableTools();
const filteredTools = combinedTools.filter(tool =>
availableToolNames.includes(tool.name) ||
// Always include mode/palette management tools
['mode_switch', 'mode_list', 'palette_select', 'palette_list'].includes(tool.name)
);
return {
tools: combinedTools.map(tool => ({
tools: filteredTools.map(tool => ({
name: tool.name,
description: tool.description,
inputSchema: tool.inputSchema
@@ -69,7 +77,7 @@ export async function createMCPServer(config?: {
};
});
// Handle tool execution
// Handle tool execution with mode checking
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const tool = combinedToolMap.get(request.params.name);
@@ -83,6 +91,19 @@ export async function createMCPServer(config?: {
};
}
// Check if tool is available in current mode
const modeManagementTools = ['mode_switch', 'mode_list', 'palette_select', 'palette_list', 'mode_create', 'shortcut'];
if (!modeManagementTools.includes(tool.name) && !modeUtils.isToolAvailable(tool.name)) {
const currentMode = modeUtils.getCurrentMode();
return {
content: [{
type: 'text',
text: `Tool '${tool.name}' is not available in ${currentMode.name} mode. Switch modes with 'mode_switch' or use 'mode_list' to see available modes.`
}],
isError: true
};
}
try {
const result = await tool.handler(request.params.arguments || {});
return result;
+5 -2
View File
@@ -11,6 +11,7 @@ import { vectorTools } from './vector-search';
import { aiTools } from './ai-tools';
import { astTools } from './ast-search';
import { todoTools } from './todo';
import { modePaletteTools } from './mode-palette';
// Combine all tools
export const allTools: Tool[] = [
@@ -21,7 +22,8 @@ export const allTools: Tool[] = [
...vectorTools,
...aiTools,
...astTools,
...todoTools
...todoTools,
...modePaletteTools
];
// Create a tool map for quick lookup
@@ -37,4 +39,5 @@ export { editTools } from './edit';
export { vectorTools } from './vector-search';
export { aiTools } from './ai-tools';
export { astTools } from './ast-search';
export { todoTools } from './todo';
export { todoTools } from './todo';
export { modePaletteTools, modeUtils } from './mode-palette';
+492
View File
@@ -0,0 +1,492 @@
/**
* Mode and palette system for context-aware tool selection
*/
import { Tool, ToolResult } from '../types';
import { allTools } from './index';
// Mode definitions
export interface Mode {
name: string;
description: string;
tools: string[]; // Tool names included in this mode
prompt?: string; // Optional prompt template for this mode
}
// Palette definitions
export interface Palette {
name: string;
description: string;
modes: string[]; // Mode names included in this palette
shortcuts?: Record<string, string>; // Shortcut commands
}
// Built-in modes
const builtInModes: Mode[] = [
{
name: 'developer',
description: 'Full development mode with all tools',
tools: ['*'], // Special case: all tools
prompt: 'You are a helpful AI assistant with access to development tools.'
},
{
name: 'research',
description: 'Research and exploration mode',
tools: [
'read_file',
'list_files',
'tree',
'grep',
'find_files',
'search',
'ast_search',
'find_symbol',
'analyze_dependencies',
'think',
'consensus'
],
prompt: 'You are a research assistant focused on understanding and analyzing code.'
},
{
name: 'editor',
description: 'Code editing and modification mode',
tools: [
'read_file',
'write_file',
'edit_file',
'multi_edit',
'create_file',
'delete_file',
'move_file',
'grep',
'ast_search'
],
prompt: 'You are a code editor assistant focused on making precise code changes.'
},
{
name: 'terminal',
description: 'Terminal and shell operations mode',
tools: [
'bash',
'shell',
'background_bash',
'kill_process',
'list_processes',
'read_file',
'write_file'
],
prompt: 'You are a terminal assistant focused on system operations.'
},
{
name: 'ai_assistant',
description: 'AI-powered assistance mode',
tools: [
'think',
'critic',
'consensus',
'agent',
'vector_index',
'vector_search',
'todo_add',
'todo_list'
],
prompt: 'You are an AI assistant that can think deeply and delegate tasks.'
},
{
name: 'project_manager',
description: 'Project management mode',
tools: [
'todo_add',
'todo_list',
'todo_update',
'todo_delete',
'todo_stats',
'tree',
'analyze_dependencies',
'think'
],
prompt: 'You are a project manager focused on task organization and tracking.'
}
];
// Built-in palettes
const builtInPalettes: Palette[] = [
{
name: 'default',
description: 'Default palette with all modes',
modes: ['developer'],
shortcuts: {
'ls': 'list_files',
'cat': 'read_file',
'cd': 'bash cd',
'pwd': 'bash pwd',
'find': 'find_files',
'search': 'grep'
}
},
{
name: 'minimal',
description: 'Minimal palette for basic operations',
modes: ['research', 'editor'],
shortcuts: {
'ls': 'list_files',
'cat': 'read_file',
'edit': 'edit_file'
}
},
{
name: 'power',
description: 'Power user palette with all capabilities',
modes: ['developer', 'ai_assistant', 'project_manager'],
shortcuts: {
'ls': 'list_files',
'cat': 'read_file',
'cd': 'bash cd',
'pwd': 'bash pwd',
'find': 'find_files',
'search': 'unified_search',
'todo': 'todo_list',
'think': 'think'
}
}
];
// Current active mode and palette
let currentMode: Mode = builtInModes[0];
let currentPalette: Palette = builtInPalettes[0];
let customModes: Mode[] = [];
let customPalettes: Palette[] = [];
// Get all available modes
function getAllModes(): Mode[] {
return [...builtInModes, ...customModes];
}
// Get all available palettes
function getAllPalettes(): Palette[] {
return [...builtInPalettes, ...customPalettes];
}
// Get tools for current mode
function getToolsForMode(mode: Mode): string[] {
if (mode.tools.includes('*')) {
return allTools.map(t => t.name);
}
return mode.tools;
}
export const modeSwitchTool: Tool = {
name: 'mode_switch',
description: 'Switch to a different tool mode',
inputSchema: {
type: 'object',
properties: {
mode: {
type: 'string',
description: 'Mode name to switch to'
}
},
required: ['mode']
},
handler: async (args) => {
const modes = getAllModes();
const mode = modes.find(m => m.name === args.mode);
if (!mode) {
const availableModes = modes.map(m => m.name).join(', ');
return {
content: [{
type: 'text',
text: `Mode '${args.mode}' not found. Available modes: ${availableModes}`
}],
isError: true
};
}
currentMode = mode;
const tools = getToolsForMode(mode);
return {
content: [{
type: 'text',
text: `🎯 Switched to ${mode.name} mode\n${mode.description}\n\nAvailable tools (${tools.length}): ${tools.slice(0, 10).join(', ')}${tools.length > 10 ? '...' : ''}`
}]
};
}
};
export const modeListTool: Tool = {
name: 'mode_list',
description: 'List available modes',
inputSchema: {
type: 'object',
properties: {
verbose: {
type: 'boolean',
description: 'Show detailed information',
default: false
}
}
},
handler: async (args) => {
const modes = getAllModes();
const output = ['📋 Available Modes\n'];
for (const mode of modes) {
const isActive = mode.name === currentMode.name;
const tools = getToolsForMode(mode);
output.push(`${isActive ? '▶️' : ' '} ${mode.name}${isActive ? ' (active)' : ''}`);
output.push(` ${mode.description}`);
if (args.verbose) {
output.push(` Tools (${tools.length}): ${tools.slice(0, 5).join(', ')}${tools.length > 5 ? '...' : ''}`);
if (mode.prompt) {
output.push(` Prompt: ${mode.prompt.substring(0, 50)}...`);
}
}
output.push('');
}
return {
content: [{
type: 'text',
text: output.join('\n')
}]
};
}
};
export const paletteSelectTool: Tool = {
name: 'palette_select',
description: 'Select a tool palette',
inputSchema: {
type: 'object',
properties: {
palette: {
type: 'string',
description: 'Palette name to select'
}
},
required: ['palette']
},
handler: async (args) => {
const palettes = getAllPalettes();
const palette = palettes.find(p => p.name === args.palette);
if (!palette) {
const availablePalettes = palettes.map(p => p.name).join(', ');
return {
content: [{
type: 'text',
text: `Palette '${args.palette}' not found. Available palettes: ${availablePalettes}`
}],
isError: true
};
}
currentPalette = palette;
// Auto-switch to first mode in palette
if (palette.modes.length > 0) {
const modes = getAllModes();
const firstMode = modes.find(m => m.name === palette.modes[0]);
if (firstMode) {
currentMode = firstMode;
}
}
const shortcuts = Object.entries(palette.shortcuts || {})
.map(([k, v]) => `${k}${v}`)
.join(', ');
return {
content: [{
type: 'text',
text: `🎨 Selected ${palette.name} palette\n${palette.description}\n\nModes: ${palette.modes.join(', ')}\nShortcuts: ${shortcuts || 'none'}`
}]
};
}
};
export const paletteListTool: Tool = {
name: 'palette_list',
description: 'List available palettes',
inputSchema: {
type: 'object',
properties: {}
},
handler: async (args) => {
const palettes = getAllPalettes();
const output = ['🎨 Available Palettes\n'];
for (const palette of palettes) {
const isActive = palette.name === currentPalette.name;
output.push(`${isActive ? '▶️' : ' '} ${palette.name}${isActive ? ' (active)' : ''}`);
output.push(` ${palette.description}`);
output.push(` Modes: ${palette.modes.join(', ')}`);
if (palette.shortcuts && Object.keys(palette.shortcuts).length > 0) {
const shortcuts = Object.entries(palette.shortcuts)
.slice(0, 3)
.map(([k, v]) => `${k}${v}`)
.join(', ');
output.push(` Shortcuts: ${shortcuts}${Object.keys(palette.shortcuts).length > 3 ? '...' : ''}`);
}
output.push('');
}
return {
content: [{
type: 'text',
text: output.join('\n')
}]
};
}
};
export const modeCreateTool: Tool = {
name: 'mode_create',
description: 'Create a custom mode',
inputSchema: {
type: 'object',
properties: {
name: {
type: 'string',
description: 'Mode name'
},
description: {
type: 'string',
description: 'Mode description'
},
tools: {
type: 'array',
items: { type: 'string' },
description: 'Tool names to include'
},
prompt: {
type: 'string',
description: 'Optional prompt template'
}
},
required: ['name', 'description', 'tools']
},
handler: async (args) => {
// Check if mode already exists
const existing = getAllModes().find(m => m.name === args.name);
if (existing) {
return {
content: [{
type: 'text',
text: `Mode '${args.name}' already exists`
}],
isError: true
};
}
// Validate tools
const validTools = allTools.map(t => t.name);
const invalidTools = args.tools.filter(t => t !== '*' && !validTools.includes(t));
if (invalidTools.length > 0) {
return {
content: [{
type: 'text',
text: `Invalid tools: ${invalidTools.join(', ')}`
}],
isError: true
};
}
const newMode: Mode = {
name: args.name,
description: args.description,
tools: args.tools,
prompt: args.prompt
};
customModes.push(newMode);
return {
content: [{
type: 'text',
text: `✅ Created mode '${newMode.name}' with ${newMode.tools.length} tools`
}]
};
}
};
export const shortcutTool: Tool = {
name: 'shortcut',
description: 'Execute a palette shortcut',
inputSchema: {
type: 'object',
properties: {
command: {
type: 'string',
description: 'Shortcut command to execute'
},
args: {
type: 'object',
description: 'Arguments to pass to the tool'
}
},
required: ['command']
},
handler: async (args) => {
const shortcuts = currentPalette.shortcuts || {};
const toolName = shortcuts[args.command];
if (!toolName) {
return {
content: [{
type: 'text',
text: `Shortcut '${args.command}' not found. Available shortcuts: ${Object.keys(shortcuts).join(', ')}`
}],
isError: true
};
}
// Check if tool is available in current mode
const availableTools = getToolsForMode(currentMode);
if (!availableTools.includes(toolName)) {
return {
content: [{
type: 'text',
text: `Tool '${toolName}' is not available in ${currentMode.name} mode`
}],
isError: true
};
}
return {
content: [{
type: 'text',
text: `Executing ${toolName}...`
}]
};
}
};
// Export mode/palette tools
export const modePaletteTools = [
modeSwitchTool,
modeListTool,
paletteSelectTool,
paletteListTool,
modeCreateTool,
shortcutTool
];
// Export utility functions for use by MCP server
export const modeUtils = {
getCurrentMode: () => currentMode,
getCurrentPalette: () => currentPalette,
getAvailableTools: () => getToolsForMode(currentMode),
isToolAvailable: (toolName: string) => getToolsForMode(currentMode).includes(toolName),
getAllModes,
getAllPalettes
};