claudes-office
Version:
CLI tool to initialize Claude's office in your project
746 lines (631 loc) • 23.6 kB
Markdown
# 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.