Permission API
OpenWork implements a sophisticated permission system that bridges the Model Context Protocol (MCP) server with the Electron UI. This system provides secure file access control while maintaining user oversight and control over all file operations.
Architecture Overview
The Permission API consists of three main components:
- MCP Server: Requests permissions for file operations
- Permission API Server: HTTP endpoint that bridges requests to the UI
- Electron UI: Displays permission dialogs and collects user decisions
System Flow
graph TD
A[MCP Server Process] -->|HTTP POST| B[Permission API Server<br/>Port 9226]
B -->|IPC Event| C[Electron Main Process]
C -->|IPC Event| D[Renderer Process - UI]
D -->|User Response| E[Permission Resolution]
E -->|HTTP Response| B
B -->|Success/Failure| A
Implementation
Source: /Users/nateb/openwork-repo/apps/desktop/src/main/permission-api.ts
Core Components
Permission Request Structure
interface PermissionRequest {
id: string;
taskId: string;
type: 'file';
fileOperation: FileOperation;
filePath: string;
targetPath?: string;
contentPreview?: string;
createdAt: string;
}
Pending Permission Management
interface PendingPermission {
resolve: (allowed: boolean) => void;
timeoutId: NodeJS.Timeout;
}
const pendingPermissions = new Map<string, PendingPermission>();
Server Initialization
/**
* Initialize the permission API with dependencies
*/
export function initPermissionApi(
window: BrowserWindow,
taskIdGetter: () => string | null
): void {
mainWindow = window;
getActiveTaskId = taskIdGetter;
}
HTTP Server Implementation
export function startPermissionApiServer(): http.Server {
const server = http.createServer(async (req, res) => {
// CORS headers for local requests
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Methods', 'POST, OPTIONS');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
// Handle preflight
if (req.method === 'OPTIONS') {
res.writeHead(200);
res.end();
return;
}
// Only handle POST /permission
if (req.method !== 'POST' || req.url !== '/permission') {
res.writeHead(404, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ error: 'Not found' }));
return;
}
// ... Parse and validate request ...
});
server.listen(PERMISSION_API_PORT, '127.0.0.1', () => {
console.log(`[Permission API] Server listening on port ${PERMISSION_API_PORT}`);
});
return server;
}
Supported Operations
File Operations
| Operation | Description | Safety Level |
|———–|————-|————–|
| create | Create new files | Medium risk |
| delete | Delete files | High risk |
| rename | Rename/move files | Medium risk |
| move | Move files to new location | Medium risk |
| modify | Modify existing files | Medium risk |
| overwrite | Overwrite existing files | High risk |
Operation Validation
const validOperations = ['create', 'delete', 'rename', 'move', 'modify', 'overwrite'];
if (!validOperations.includes(data.operation)) {
res.writeHead(400, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({
error: `Invalid operation. Must be one of: ${validOperations.join(', ')}`
}));
return;
}
User Experience Flow
1. Permission Request
When the MCP server needs to access a file:
// Example request from MCP server
{
"operation": "modify",
"filePath": "/Users/user/documents/report.txt",
"contentPreview": "This is a preview of the file content..."
}
2. UI Display
The renderer process shows a permission dialog:
// IPC event sent to renderer
mainWindow.webContents.send('permission:request', permissionRequest);
3. User Decision
User can choose to:
- Allow: Grant permission for this specific operation
- Deny: Block the operation
- Remember: Store preference for future operations (planned feature)
4. Response Handling
export function resolvePermission(requestId: string, allowed: boolean): boolean {
const pending = pendingPermissions.get(requestId);
if (!pending) {
return false;
}
clearTimeout(pending.timeoutId);
pending.resolve(allowed);
pendingPermissions.delete(requestId);
return true;
}
Security Features
Timeout Protection
const PERMISSION_TIMEOUT_MS = 5 * 60 * 1000; // 5 minutes
try {
const allowed = await new Promise<boolean>((resolve, reject) => {
const timeoutId = setTimeout(() => {
pendingPermissions.delete(requestId);
reject(new Error('Permission request timed out'));
}, PERMISSION_TIMEOUT_MS);
pendingPermissions.set(requestId, { resolve, timeoutId });
});
} catch (error) {
// Handle timeout
}
Request Validation
- Validates required fields (
operation,filePath) - Checks operation type against allowed list
- Validates request format and data types
- Implements CORS headers for local development
Error Handling
// Validation errors
res.writeHead(400, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ error: 'Invalid JSON' }));
// Service unavailable
res.writeHead(503, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ error: 'Permission API not initialized' }));
// Timeout response
res.writeHead(408, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ error: 'Request timed out', allowed: false }));
Integration with OpenCode CLI
Task Manager Integration
The Permission API integrates with the OpenCode CLI through the task manager:
// Initialize with current task context
initPermissionApi(mainWindow, () => getActiveTaskId());
Real-time Permission Handling
// During task execution, MCP requests flow through:
1. OpenCode CLI → MCP Server
2. MCP Server → Permission API
3. Permission API → Electron UI
4. User Decision → Permission API
5. Permission API → MCP Server
6. MCP Server → OpenCode CLI
Configuration
Port Configuration
export const PERMISSION_API_PORT = 9226;
Server Options
- Binding:
127.0.0.1(localhost only) - Protocol: HTTP
- CORS: Enabled for local development
- Timeout: 5 minutes per request
Environment Considerations
- Development: Server starts automatically with the app
- Production: Server starts with proper error handling
- Error cases: Graceful degradation when server fails to start
Debugging and Monitoring
Server Status
server.on('error', (error: NodeJS.ErrnoException) => {
if (error.code === 'EADDRINUSE') {
console.warn(`[Permission API] Port ${PERMISSION_API_PORT} already in use, skipping server start`);
} else {
console.error('[Permission API] Server error:', error);
}
});
Request Logging
console.log('[Main] Received protocol URL:', url);
console.log(`[Permission API] Server listening on port ${PERMISSION_API_PORT}`);
Debug Tools
- Check server logs for permission requests
- Monitor IPC events between main and renderer processes
- Use browser dev tools for HTTP request inspection
Future Enhancements
Planned Features
- Permission Persistence: Remember user choices for specific file operations
- Whitelist/Denylist: User-configurable permission rules
- Preview Mode: Show file changes before applying them
- Bulk Operations: Handle multiple file operations in a single dialog
- Detailed Logging: Audit trail of all permission decisions
Security Improvements
- Request Signing: Add cryptographic signatures to permission requests
- Rate Limiting: Prevent abuse of the permission API
- Context Awareness: Consider file type, size, and operation risk
- Time-based Permissions: Temporary permissions with automatic expiration
Troubleshooting
Common Issues
Permission Request Timeout
- Cause: User didn’t respond within 5 minutes
- Solution: Restart the permission request or check if app is responsive
Server Already in Use
- Cause: Multiple instances trying to use same port
- Solution: Ensure single instance is running or check for conflicting processes
Invalid Request Format
- Cause: Malformed JSON or missing required fields
- Solution: Check MCP server implementation for proper request formatting
Missing Dependencies
- Cause: Permission API not properly initialized
- Solution: Ensure task manager is running and main window is available
Testing Permission Requests
# Manual test using curl
curl -X POST http://localhost:9226/permission \
-H "Content-Type: application/json" \
-d '{
"operation": "create",
"filePath": "/tmp/test.txt",
"contentPreview": "Hello World"
}'
The Permission API provides a robust security layer that ensures users maintain control over file operations while enabling the powerful capabilities of the MCP server ecosystem.