UNPKG

claudes-office

Version:

CLI tool to initialize Claude's office in your project

746 lines (631 loc) 23.6 kB
# Claude's Office MCP Server Specification ## Overview The Claude's Office MCP Server transforms the existing file-based Claude's Office CLI into a dynamic, API-driven environment where the office structure is maintained as a living system rather than static files. This enables LLMs to directly interact with the office environment through standardized MCP tools, with a "Secretary" model providing behind-the-scenes assistance. ## Project Structure We will create a new package called `claudes-office-mcp-server` that depends on the existing `claudes-office-cli` package: ``` claudes-office-mcp-server/ ├── src/ │ ├── index.ts # Main entry point │ ├── secretary/ # Secretary model integration │ │ ├── index.ts # Main export │ │ ├── openrouter.ts # OpenRouter API client │ │ └── handlers/ # Task-specific handlers │ ├── server/ # MCP server implementation │ │ ├── index.ts # Server initialization │ │ ├── tools.ts # Tool definitions and handlers │ │ ├── resources.ts # Resource definitions and handlers │ │ └── prompts.ts # Prompt definitions and handlers │ ├── office/ # Office data management │ │ ├── index.ts # Main office functionality │ │ ├── store.ts # Storage implementation (in-memory/DB) │ │ ├── roles.ts # Role management │ │ └── documents.ts # Document management │ └── utils/ # Utility functions ├── tests/ # Test suite ├── package.json # Dependencies and scripts ├── tsconfig.json # TypeScript configuration └── README.md # Documentation ``` ## Architecture ### Component Diagram ``` ┌─────────────────────────┐ ┌───────────────────────┐ │ │ │ │ │ Claude or other │◄───────►│ MCP Office Server │ │ primary LLM │ │ │ │ │ └───────────┬───────────┘ └─────────────────────────┘ │ │ ▼ ┌─────────────────────────┐ │ │ │ Office Data Manager │ │ │ └──────────┬──────────────┘ │ │ ┌──────────────────────────────┴─────────────────────────────┐ │ │ ▼ ▼ ┌─────────────────────────┐ ┌─────────────────────────┐ │ │ │ │ │ Secretary Model │◄────────────────────────────────│ Template Files │ │ (via OpenRouter) │ │ (from claudes-office) │ │ │ │ │ └─────────────────────────┘ └─────────────────────────┘ ``` ### Core Components 1. **MCP Server** - Exposes the MCP protocol endpoints (tools, resources, prompts) - Handles client connections and message routing - Delegates complex operations to the Office Data Manager 2. **Office Data Manager** - Maintains the office structure (roles, documents, sessions) - Provides CRUD operations for office components - Abstracts storage details (in-memory or database) 3. **Secretary Model** - Processes complex requests through OpenRouter API - Handles role selection, document retrieval, content generation - Acts as an intelligent assistant for office operations 4. **Template Files** - Imported from the `claudes-office-cli` package - Provide baseline role definitions and structure - Used for initial population and as reference ## MCP Implementation ### Tools The server will expose the following tools: 1. **Office Navigation Tools** ```typescript // Office structure exploration tool server.tool( "explore-office", { path: z.string().optional().describe("Office path to explore, defaults to root") }, async ({ path }) => { const structure = await officeManager.exploreStructure(path || "/"); return { content: [{ type: "text", text: JSON.stringify(structure, null, 2) }] }; } ); // Document finding tool server.tool( "find-document", { query: z.string().describe("Search term or criteria"), type: z.enum(["role", "document", "any"]).optional().describe("Type of document to find") }, async ({ query, type }) => { // Delegate complex search to Secretary model const results = await secretary.findDocuments(query, type); return { content: [{ type: "text", text: JSON.stringify(results, null, 2) }] }; } ); ``` 2. **Role Management Tools** ```typescript // Adopt a role based on requirements server.tool( "adopt-role", { domain: z.string().optional().describe("Domain expertise needed"), skills: z.array(z.string()).optional().describe("Specific skills required"), task: z.string().optional().describe("Description of the task") }, async ({ domain, skills, task }) => { const roleContent = await secretary.findBestRole(domain, skills, task); return { content: [{ type: "text", text: roleContent }] }; } ); // Create a new role server.tool( "create-role", { name: z.string().describe("Name for the new role"), description: z.string().describe("Short description of the role"), skills: z.array(z.string()).describe("Skills this role should have"), domain: z.string().describe("Domain this role belongs to") }, async ({ name, description, skills, domain }) => { const newRole = await secretary.createRole(name, description, skills, domain); return { content: [{ type: "text", text: newRole }] }; } ); ``` 3. **Document Management Tools** ```typescript // Read a document by path server.tool( "read-document", { path: z.string().describe("Path to the document") }, async ({ path }) => { const content = await officeManager.readDocument(path); return { content: [{ type: "text", text: content }] }; } ); // Create or update a document server.tool( "update-document", { path: z.string().describe("Path to the document"), content: z.string().describe("New content for the document"), createIfMissing: z.boolean().optional().default(true).describe("Create document if it doesn't exist") }, async ({ path, content, createIfMissing }) => { const result = await officeManager.updateDocument(path, content, createIfMissing); return { content: [{ type: "text", text: `Document at ${path} ${result.created ? 'created' : 'updated'} successfully` }] }; } ); ``` 4. **Workflow Tools** ```typescript // Create a new work session server.tool( "create-session", { name: z.string().describe("Name for this work session"), description: z.string().describe("Description of work to be done"), context: z.array(z.string()).optional().describe("Relevant context resources") }, async ({ name, description, context }) => { const session = await officeManager.createSession(name, description, context); return { content: [{ type: "text", text: `Session "${name}" created with ID: ${session.id}` }] }; } ); // Generate a project plan server.tool( "plan-project", { name: z.string().describe("Project name"), description: z.string().describe("Project description"), technologies: z.array(z.string()).describe("Technologies to be used") }, async ({ name, description, technologies }) => { // This is a complex task delegated to the Secretary model const plan = await secretary.generateProjectPlan(name, description, technologies); return { content: [{ type: "text", text: plan }] }; } ); ``` ### Resources The server will expose these key resources: ```typescript // Office structure as a resource server.resource( "office-structure", "office://structure", async (uri) => ({ contents: [{ uri: uri.href, text: JSON.stringify(await officeManager.getFullStructure(), null, 2) }] }) ); // Current session information server.resource( "current-session", "office://session/current", async (uri) => ({ contents: [{ uri: uri.href, text: JSON.stringify(await officeManager.getCurrentSession(), null, 2) }] }) ); // Available roles resource server.resource( "available-roles", new ResourceTemplate("office://roles/{domain?}", { list: undefined }), async (uri, { domain }) => ({ contents: [{ uri: uri.href, text: JSON.stringify(await officeManager.listRoles(domain), null, 2) }] }) ); // Specific role content server.resource( "role-content", new ResourceTemplate("office://role/{rolePath}", { list: undefined }), async (uri, { rolePath }) => ({ contents: [{ uri: uri.href, text: await officeManager.getRoleContent(rolePath) }] }) ); ``` ### Prompts The server will expose helpful prompt templates: ```typescript server.prompt( "adopt-expert-role", { domain: z.string().describe("Domain of expertise"), task: z.string().describe("Task description") }, async ({ domain, task }) => { const roleContent = await secretary.findBestRole(domain, [], task); return { messages: [ { role: "user", content: { type: "text", text: `I need your help with a task in the ${domain} domain. The task is: ${task}\n\nPlease adopt the following role and expertise:\n\n${roleContent}` } } ] }; } ); server.prompt( "plan-implementation", { feature: z.string().describe("Feature to implement"), technologies: z.array(z.string()).describe("Technologies to use") }, async ({ feature, technologies }) => { const techStr = technologies.join(", "); return { messages: [ { role: "user", content: { type: "text", text: `Please help me plan the implementation of this feature: "${feature}". The implementation should use these technologies: ${techStr}.\n\nProvide a step-by-step implementation plan, listing key components, their responsibilities, and how they interact.` } } ] }; } ); ``` ## Secretary Model Integration The Secretary model will be integrated using the OpenRouter API: ```typescript export class Secretary { private openRouterClient: OpenRouterClient; private officeManager: OfficeManager; constructor(apiKey: string, officeManager: OfficeManager) { this.openRouterClient = new OpenRouterClient(apiKey); this.officeManager = officeManager; } async findBestRole(domain?: string, skills?: string[], task?: string): Promise<string> { // 1. Get available roles matching criteria const roles = await this.officeManager.findRoles(domain, skills); // 2. Create a prompt for the secretary model const prompt = ` I need to find the best role for this task: "${task || 'general assistance'}". Domain: ${domain || 'any'} Required skills: ${skills?.join(', ') || 'any'} Available roles: ${roles.map(r => `- ${r.path}: ${r.description}`).join('\n')} Please select the most appropriate role and explain why it's a good fit. Then return the full role content for the selected role. `; // 3. Call the secretary model const response = await this.openRouterClient.complete({ model: "anthropic/claude-instant-1.2", prompt, max_tokens: 1500 }); // 4. Parse the response to extract role content // ... Implementation details ... return roleContent; } // Other secretary methods like createRole, findDocuments, etc. } ``` ## Office Data Manager The Office Data Manager will provide an abstraction over the office data: ```typescript export class OfficeManager { private store: OfficeStore; private templateProvider: TemplateProvider; constructor(store: OfficeStore, templateProvider: TemplateProvider) { this.store = store; this.templateProvider = templateProvider; } async initialize(): Promise<void> { // Load initial templates from claudes-office-cli const templates = await this.templateProvider.getTemplates(); await this.store.initializeWithTemplates(templates); } // Structure exploration async exploreStructure(path: string): Promise<OfficeStructure> { return this.store.getStructureAt(path); } // Role management async listRoles(domain?: string): Promise<RoleSummary[]> { return this.store.findRoles(domain); } async getRoleContent(path: string): Promise<string> { return this.store.getDocumentContent(path); } async findRoles(domain?: string, skills?: string[]): Promise<RoleSummary[]> { return this.store.findRolesByAttributes(domain, skills); } // Document management async readDocument(path: string): Promise<string> { return this.store.getDocumentContent(path); } async updateDocument( path: string, content: string, createIfMissing: boolean ): Promise<{created: boolean}> { const exists = await this.store.documentExists(path); if (!exists && !createIfMissing) { throw new Error(`Document at ${path} does not exist`); } await this.store.setDocumentContent(path, content); return { created: !exists }; } // Session management async createSession( name: string, description: string, context?: string[] ): Promise<Session> { const session = { id: generateId(), name, description, context: context || [], created: new Date().toISOString() }; await this.store.saveSession(session); await this.store.setCurrentSession(session.id); return session; } async getCurrentSession(): Promise<Session | null> { const currentSessionId = await this.store.getCurrentSessionId(); if (!currentSessionId) return null; return this.store.getSession(currentSessionId); } } ``` ## Storage Implementation We'll provide multiple storage backends, starting with in-memory: ```typescript export interface OfficeStore { // Structure operations getStructureAt(path: string): Promise<OfficeStructure>; // Document operations documentExists(path: string): Promise<boolean>; getDocumentContent(path: string): Promise<string>; setDocumentContent(path: string, content: string): Promise<void>; // Role operations findRoles(domain?: string): Promise<RoleSummary[]>; findRolesByAttributes(domain?: string, skills?: string[]): Promise<RoleSummary[]>; // Session operations saveSession(session: Session): Promise<void>; getSession(id: string): Promise<Session | null>; getCurrentSessionId(): Promise<string | null>; setCurrentSession(id: string): Promise<void>; // Initialization initializeWithTemplates(templates: Template[]): Promise<void>; } // In-memory implementation export class InMemoryOfficeStore implements OfficeStore { private structure: Record<string, any> = {}; private documents: Record<string, string> = {}; private sessions: Record<string, Session> = {}; private currentSessionId: string | null = null; // Implementation details... } ``` ## Template Provider The template provider integrates with the existing CLI package: ```typescript export interface TemplateProvider { getTemplates(): Promise<Template[]>; } export class CliTemplateProvider implements TemplateProvider { private templateDir: string; constructor() { // Find the node_modules path to claudes-office-cli this.templateDir = path.resolve( require.resolve('claudes-office-cli'), '../../template' ); } async getTemplates(): Promise<Template[]> { // Read template files from the CLI package // Transform them into our internal format // ... return templates; } } ``` ## Server Initialization Here's how we'll initialize the server: ```typescript import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { InMemoryOfficeStore } from "./office/store.js"; import { CliTemplateProvider } from "./office/templates.js"; import { OfficeManager } from "./office/index.js"; import { Secretary } from "./secretary/index.js"; import { registerTools } from "./server/tools.js"; import { registerResources } from "./server/resources.js"; import { registerPrompts } from "./server/prompts.js"; export async function createServer(config: { openRouterApiKey: string, useStdio?: boolean }) { // Create the MCP server const server = new McpServer({ name: "claudes-office", version: "1.0.0" }); // Initialize the office stack const store = new InMemoryOfficeStore(); const templateProvider = new CliTemplateProvider(); const officeManager = new OfficeManager(store, templateProvider); const secretary = new Secretary(config.openRouterApiKey, officeManager); // Initialize the office data await officeManager.initialize(); // Register MCP capabilities registerTools(server, officeManager, secretary); registerResources(server, officeManager); registerPrompts(server, officeManager, secretary); // Connect transport if (config.useStdio) { const transport = new StdioServerTransport(); await server.connect(transport); } return { server, officeManager, secretary }; } ``` ## Package Configuration ```json { "name": "claudes-office-mcp-server", "version": "1.0.0", "description": "MCP server implementation of Claude's Office", "main": "dist/index.js", "types": "dist/index.d.ts", "scripts": { "build": "tsc", "start": "node dist/index.js", "test": "jest", "dev": "ts-node-dev --respawn src/index.ts" }, "bin": { "claudes-office-mcp": "./bin/claudes-office-mcp" }, "dependencies": { "@modelcontextprotocol/sdk": "^0.2.0", "claudes-office-cli": "^1.0.0", "dotenv": "^16.0.3", "express": "^4.18.2", "nanoid": "^3.3.6", "openrouter": "^2.0.0", "zod": "^3.21.4" }, "devDependencies": { "@types/express": "^4.17.17", "@types/jest": "^29.5.0", "@types/node": "^18.15.11", "jest": "^29.5.0", "ts-jest": "^29.1.0", "ts-node-dev": "^2.0.0", "typescript": "^5.0.4" }, "engines": { "node": ">=16.0.0" } } ``` ## CLI Interface ```typescript #!/usr/bin/env node import { program } from 'commander'; import dotenv from 'dotenv'; import { createServer } from '../dist/index.js'; // Load environment variables dotenv.config(); program .name('claudes-office-mcp') .description('MCP server for Claude\'s Office') .version('1.0.0'); program .command('start') .description('Start the MCP server') .option('-p, --port <port>', 'Port to run on (for HTTP mode)', '3000') .option('-m, --mode <mode>', 'Transport mode (stdio or http)', 'stdio') .option('-k, --key <key>', 'OpenRouter API key (override env var)') .action(async (options) => { const apiKey = options.key || process.env.OPENROUTER_API_KEY; if (!apiKey) { console.error('Error: OpenRouter API key is required. Set OPENROUTER_API_KEY env var or use --key option.'); process.exit(1); } try { if (options.mode === 'stdio') { await createServer({ openRouterApiKey: apiKey, useStdio: true }); // Server is now running on stdio } else if (options.mode === 'http') { const express = require('express'); const app = express(); // Set up HTTP endpoints // ... app.listen(options.port, () => { console.log(`Server running on http://localhost:${options.port}`); }); } } catch (error) { console.error('Error starting server:', error); process.exit(1); } }); program.parse(); ``` ## Development Roadmap ### Phase 1: Core Implementation (2 weeks) - Set up project structure and build system - Implement basic MCP server with tool and resource definitions - Create in-memory office store - Integrate template loading from CLI package - Implement basic office manager functionality ### Phase 2: Secretary Integration (2 weeks) - Set up OpenRouter API client - Implement core Secretary functionality - Create basic role matching and document finding - Add role content generation capabilities - Implement session management ### Phase 3: Advanced Features (2 weeks) - Add HTTP/SSE transport support - Implement persistent storage option - Add role composition and specialization - Create workflow management features - Build project planning capabilities ### Phase 4: Polish and Deploy (1 week) - Comprehensive testing - Documentation - Performance optimization - Publish to npm registry ## Testing Strategy 1. **Unit Tests** - Office manager component tests - Secretary model service tests - Storage implementations tests - Tool and resource handler tests 2. **Integration Tests** - MCP server protocol compliance - End-to-end workflows using test clients - Secretary model integration tests 3. **Performance Tests** - Load testing with multiple simultaneous clients - Secretary model response time benchmarks - Memory usage profiling ## Conclusion The Claude's Office MCP Server transforms the static file-based office structure into a dynamic, intelligent environment. By combining the structured organization of the CLI with the power of an MCP server and a dedicated Secretary model, we create a system that: 1. Maintains the benefits of role-based contextual AI assistance 2. Enhances the experience with dynamic role selection and generation 3. Provides a standardized interface for LLM interaction 4. Creates a more intelligent workspace that evolves with use This implementation maintains backward compatibility with the existing CLI package while offering a significantly enhanced experience for users working with Claude and other MCP-compatible LLMs.