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:

  1. MCP Server: Requests permissions for file operations
  2. Permission API Server: HTTP endpoint that bridges requests to the UI
  3. 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

  1. Permission Persistence: Remember user choices for specific file operations
  2. Whitelist/Denylist: User-configurable permission rules
  3. Preview Mode: Show file changes before applying them
  4. Bulk Operations: Handle multiple file operations in a single dialog
  5. Detailed Logging: Audit trail of all permission decisions

Security Improvements

  1. Request Signing: Add cryptographic signatures to permission requests
  2. Rate Limiting: Prevent abuse of the permission API
  3. Context Awareness: Consider file type, size, and operation risk
  4. 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.


Back to top

OpenWork Documentation - Community documentation for accomplish-ai/openwork