Skip to content

Latest commit

 

History

History
321 lines (262 loc) · 8.64 KB

architecture.md

File metadata and controls

321 lines (262 loc) · 8.64 KB

Linear MCP Architecture

This document outlines the architecture of the Linear MCP (Model Context Protocol) implementation.

Overview

The Linear MCP provides a type-safe, modular interface to the Linear API. It abstracts away the complexity of GraphQL operations while providing a clean, domain-driven API surface through MCP tools.

Core Concepts

Domain-Driven Design

The codebase is organized around business domains:

  • Authentication
  • Issues
  • Projects
  • Teams
  • Users

Each domain has its own set of:

  • Handlers (for MCP tool operations)
  • Types
  • Tests

Layered Architecture

The codebase follows a layered architecture pattern:

src/
├── core/               # Core infrastructure
│   ├── handlers/      # Base handler and factory
│   │   ├── base.handler.ts
│   │   └── handler.factory.ts
│   ├── types/         # Shared type definitions
│   │   ├── tool.types.ts     # MCP tool schemas
│   │   └── common.types.ts
│   └── interfaces/    # Core interfaces
│       └── tool-handler.interface.ts
│
├── features/          # Feature modules by domain
│   ├── auth/         # Authentication
│   │   └── handlers/
│   ├── issues/       # Issue management
│   │   └── handlers/
│   ├── projects/     # Project operations
│   │   └── handlers/
│   ├── teams/        # Team operations
│   │   └── handlers/
│   └── users/        # User operations
│       └── handlers/
│
├── infrastructure/    # Infrastructure concerns
│   ├── graphql/      # GraphQL implementation
│   │   ├── operations/   # GraphQL operations by domain
│   │   └── fragments/    # Shared GraphQL fragments
│   └── http/         # HTTP client
│
└── utils/            # Shared utilities
    ├── logger.ts     # Logging system
    └── config.ts     # Configuration management

Key Components

Handler Architecture

The handler system provides a clean separation of concerns for MCP tool operations:

// Base handler with shared functionality
abstract class BaseHandler {
  protected verifyAuth(): LinearGraphQLClient;
  protected createResponse(text: string): BaseToolResponse;
  protected createJsonResponse(data: unknown): BaseToolResponse;
  protected handleError(error: unknown, operation: string): never;
  protected validateRequiredParams(params: Record<string, unknown>, required: string[]): void;
}

// Feature-specific handlers extend the base
class IssueHandler extends BaseHandler {
  handleCreateIssue(args: any): Promise<BaseToolResponse>;
  handleSearchIssues(args: any): Promise<BaseToolResponse>;
  // ... other issue operations
}

// Factory for managing handlers
class HandlerFactory {
  private authHandler: AuthHandler;
  private issueHandler: IssueHandler;
  // ... other handlers

  getHandlerForTool(toolName: string): { handler: BaseHandler; method: string };
}

Authentication Layer

The authentication system supports both PAT and OAuth flows:

class AuthHandler extends BaseHandler {
  handleAuth(args: any): Promise<BaseToolResponse>;
  handleAuthCallback(args: any): Promise<BaseToolResponse>;
}

interface AuthConfig {
  type: 'pat' | 'oauth';
  accessToken?: string;
  clientId?: string;
  clientSecret?: string;
  redirectUri?: string;
}

GraphQL Layer

The GraphQL layer provides domain-specific operations with atomic and composite patterns:

class LinearGraphQLClient {
  // Execute GraphQL operations
  async execute<T>(document: DocumentNode, variables?: any): Promise<T>;
  
  // Atomic operations
  async createProject(input: ProjectInput): Promise<ProjectResponse>;
  async createBatchIssues(issues: CreateIssueInput[]): Promise<IssueBatchResponse>;
  
  // Composite operations (built from atomic operations)
  async createProjectWithIssues(
    projectInput: ProjectInput, 
    issues: CreateIssueInput[]
  ): Promise<ProjectResponse> {
    // Creates project first, then creates issues with project reference
    const project = await this.createProject(projectInput);
    const issuesWithProject = issues.map(issue => ({
      ...issue,
      projectId: project.projectCreate.project.id
    }));
    const batchResult = await this.createBatchIssues(issuesWithProject);
    return { projectCreate: project.projectCreate, issueBatchCreate: batchResult.issueBatchCreate };
  }
}

This pattern ensures:

  • Clear separation between atomic and composite operations
  • Type safety through the entire operation chain
  • Proper error handling at each step
  • Reusable atomic operations

Error Handling

Errors are handled consistently through the MCP error system:

interface BaseToolResponse {
  content: Array<{
    type: string;
    text: string;
  }>;
}

interface ErrorToolResponse extends BaseToolResponse {
  isError: true;
}

Best Practices

  1. Handler Organization

    • Each domain has its own handler
    • Handlers extend BaseHandler
    • Keep handler methods focused and single-purpose
  2. Type Safety

    • Define tool schemas in tool.types.ts
    • Use interfaces for handler methods
    • Minimize use of 'any' type
  3. Error Handling

    • Use BaseHandler error methods
    • Provide clear error messages
    • Include operation context in errors
  4. Testing

    • Test each handler independently
    • Use integration tests for full flows
    • Mock GraphQL responses

Planned Improvements

Type Safety & Validation

  • Replace all 'any' types with proper interfaces
  • Generate types from GraphQL schema
  • Add runtime type checking
  • Implement JSON schema validation for inputs
  • Improve error messages for validation failures

Performance Optimizations

  • Implement true batch mutations for bulk operations
  • Pre-import and cache GraphQL operations
  • Add query batching for related operations
  • Implement proper error handling for GraphQL errors
  • Move to GraphQL code generation
  • Add operation validation

Handler Enhancements

  • Add comprehensive input validation
  • Implement response caching with invalidation
  • Add retry logic with backoff strategy
  • Add handler lifecycle hooks
  • Improve error context and debugging

OAuth Implementation

  • Complete OAuth flow with proper state management
  • Add token refresh with automatic retry
  • Implement secure token storage
  • Add proper error handling for OAuth flows
  • Support multiple OAuth scopes

GraphQL Operations

  • Implement true batching for bulk operations
  • Move to code generation for type safety
  • Add operation validation and optimization
  • Implement proper error handling
  • Add query complexity analysis

Authentication Refactoring

  • Split authentication into separate implementations:
    interface ILinearAuth {
      initialize(config: AuthConfig): void;
      isAuthenticated(): boolean;
      getClient(): LinearClient;
    }
    
    class OAuthLinearAuth implements ILinearAuth {
      // OAuth-specific implementation
    }
    
    class PatLinearAuth implements ILinearAuth {
      // PAT-specific implementation
    }

Caching Layer

  • Implement caching for frequently accessed data:
    interface CacheConfig {
      ttl: number;
      maxSize: number;
    }
    
    class CacheManager {
      private cache: Map<string, CacheEntry>;
      
      get<T>(key: string): T | undefined;
      set<T>(key: string, value: T, ttl?: number): void;
      invalidate(pattern: string): void;
    }

Rate Limiting

  • Add rate limiting middleware:
    class RateLimiter {
      private readonly limits: Map<string, number>;
      private readonly windowMs: number;
    
      async checkLimit(operation: string): Promise<boolean>;
      async waitForAvailability(operation: string): Promise<void>;
    }

Error Handling

  • Implement domain-specific error types:
    class LinearApiError extends Error {
      constructor(
        public code: string,
        public operation: string,
        message: string
      ) {
        super(message);
      }
    }

Contributing

When contributing to this codebase:

  1. Follow the handler pattern
  2. Maintain domain separation
  3. Add tests for new handlers
  4. Update tool schemas
  5. Keep handlers focused
  6. Document new tools

File Organization

Keep related code together:

features/issues/
├── handlers/          # Issue-related handlers
│   └── issue.handler.ts
├── types/            # Issue-specific types
│   └── issue.types.ts
└── __tests__/        # Tests
    ├── issue.test.ts
    └── issue.integration.test.ts

Dependency Management

  • Keep dependencies minimal
  • Use peer dependencies where appropriate
  • Lock dependency versions
  • Document breaking changes