Skip to content

System Architecture

overthelex edited this page May 17, 2026 · 3 revisions

System Architecture

The SecondLayer platform is a Ukrainian legal tech monorepo providing AI-powered legal document analysis, semantic search over court decisions (EDRSR), legislation retrieval, parliament data, business registry lookups, consultations, and payments. It employs a Triple Transport System for its Model Context Protocol (MCP) servers, enabling communication via MCP stdio, HTTP REST API, and Server-Sent Events (SSE). The architecture is built on a modular, service-oriented foundation with a clean separation between domain ports, adapters, and infrastructure.

Workspace Structure

SecondLayer/
├── mcp_backend/           # Primary MCP server - court cases, legal docs, legislation, OSINT, due diligence
├── mcp_rada/              # Parliament data server (deputies, bills, legislation, voting)
├── mcp_openreyestr/       # State Register server (legal entities, beneficiaries, debtors, notaries)
├── lexwebapp/             # Web frontend (React 19, Vite 8, TailwindCSS, Zustand 5)
├── packages/shared/       # Shared TypeScript package (@secondlayer/shared)
├── platform/              # Platform service (separate frontend, Vite + TailwindCSS)
├── mobile/                # Mobile app (Flutter/Dart)
├── services/
│   ├── edrsr-fulltext-worker/   # Python worker for EDRSR full-text indexing
│   └── opendata-importers/      # Python importers for government open data
├── opendata-sync/         # Scheduled sync service for Ukrainian open data sources
├── terminal-service/      # Terminal service
├── deployment/            # Docker configs, compose files, nginx, manage-gateway.sh
├── scripts/               # Utility scripts (deploy, RADA sync, EDRSR import, testing)
├── tests/                 # E2E tests (Playwright)
├── docs/                  # Research papers, API docs, reports
├── config/                # MCP client configs
└── legacy/                # Archived code

Core Backend Infrastructure

Triple Transport System

Each MCP server supports three communication protocols, enabling flexible integration across different client types:

  • MCP stdio: Standard MCP protocol for local AI client integration (Claude Desktop, IDE extensions).
  • HTTP API: REST endpoints (POST /api/tools/:toolName) for web application integration, with batch support (POST /api/tools/batch).
  • SSE (Server-Sent Events): Streaming endpoints (POST /api/tools/:toolName/stream) for long-running operations, plus a dedicated MCP-over-SSE endpoint (/sse) for remote MCP clients.

Service Decomposition

Service Package Port Description
mcp_backend secondlayer-mcp 3000 Primary server: court decisions (EDRSR), legislation, semantic search, document analysis, ECHR practice, OSINT, due diligence, workflows
mcp_rada rada-mcp 3001 Ukrainian parliament: deputies, factions, committees, bills, legislation texts, voting records
mcp_openreyestr openreyestr-mcp 3005 NAIS state registries: legal entities, beneficiaries, debtors, notaries
Document Service - 3002 OCR and document processing (Google Cloud Vision)
EDRSR FTS Worker - - Python worker for full-text search indexing of court decisions
OpenData Sync opendata-sync - Scheduled sync of government open data sources

Unified Gateway

In production, a unified gateway (ENABLE_UNIFIED_GATEWAY=true) aggregates all services behind a single endpoint. Tool routing is handled by the ToolRegistry which maps tool names to either local BaseToolHandler instances or remote services via RemoteServiceClient.

flowchart TD
    Client[Client Request] --> Gateway[Unified Gateway / ToolRegistry]
    Gateway --> |"no prefix"| Backend[mcp_backend tools]
    Gateway --> |"rada_*"| Rada[mcp_rada tools]
    Gateway --> |"openreyestr_*"| OpenReyestr[mcp_openreyestr tools]

    Backend --> DB[(PostgreSQL + PgBouncer)]
    Backend --> Vector[(Qdrant)]
    Backend --> Cache[(Redis)]
    Backend --> MinIO[(MinIO / S3)]

    Rada --> RadaDB[(PostgreSQL - rada schema)]
    OpenReyestr --> OpenDB[(PostgreSQL - openreyestr)]
Loading

Tool categories registered in mcp_backend:

  • Court decisions (search, extended, unified, hybrid, semantic, FTS)
  • Legislation (lookup, monitoring, cross-references)
  • ECHR practice, court sessions, legal acts
  • Due diligence, OSINT proxy, spending data
  • Document analysis, vault, workflows
  • Open data registries, analyze-data, decision layer
  • Nextcloud integration

Shared Package (@secondlayer/shared)

The shared package (packages/shared/) provides reusable infrastructure used across all three MCP servers. It must be built before other services (cd packages/shared && npm run build).

Key Exports

Module Exports Purpose
utils/logger createLogger, logger Winston-based structured logging
utils/model-selector ModelSelector Budget-aware model selection across providers
utils/openai-client OpenAIClientManager, getOpenAIManager OpenAI API client with cost tracking
utils/anthropic-client AnthropicClientManager, getAnthropicManager Anthropic API client
utils/bedrock-client BedrockClientManager, getBedrockManager AWS Bedrock client (eu-central-1)
utils/llm-client-manager LLMClientManager, getLLMManager Unified multi-provider LLM interface with streaming, fallback, budget routing
database/base-database BaseDatabase PostgreSQL connection pool management
http/sse-handler SSEHandler Server-Sent Events streaming helper
http/base-http-server BaseHTTPServer Express-based HTTP server with CORS, auth, health checks
services/base-cost-tracker BaseCostTracker Per-request API cost tracking
types shared TypeScript types AuthenticatedRequest, health check types, tool call types

Multi-Provider LLM Strategy

The ModelSelector supports three providers with budget-based model routing:

Budget OpenAI Bedrock Anthropic (direct)
Quick gpt-5-nano claude-haiku-4-5 claude-haiku-4-5
Standard gpt-5-mini claude-sonnet-4-6 claude-sonnet-4-6
Deep gpt-5.1 claude-opus-4-6 claude-opus-4-6

Provider strategy is configured via LLM_PROVIDER_STRATEGY (openai-first or bedrock-first). Bedrock fallback models provide automatic failover.

Embeddings use Voyage AI (voyage-multilingual-2) by default.

Factory Pattern

Services are composed through factory functions that wire up dependencies at startup. This avoids service locators and enables testability.

mcp_backend Factories

mcp_backend/src/factories/
├── core-services.ts      # createBackendCoreServices() - DB, adapters, query planner, sectionizer, embeddings
├── app-services.ts       # createAppServices() - conversations, billing, auth, chat, workflows, consultations
├── billing-services.ts   # createBillingServices() - subscriptions, credits, payments
├── tool-services.ts      # createToolServices() - ToolRegistry, tool handlers, document processing
└── core-loader.ts        # Lazy loading / initialization orchestration

createBackendCoreServices() composes:

  • Database (PostgreSQL connection pool)
  • DocumentService (document retrieval and caching)
  • EdsrLocalAdapter (multiple domain-specific instances: court decisions, practice, sessions, legal acts, ECHR)
  • QueryPlanner, SemanticSectionizer, EmbeddingService
  • LegalPatternStore, CitationValidator, ShepardizationService
  • LegislationService, LegislationTools
  • ReyestrDownloadService, ImportTaskService

createToolServices() registers all tool handlers into the ToolRegistry:

  • CourtDecisionTools, EdsrExtendedTools, EdsrUnifiedSearchTool
  • LegalAdviceTools, ProceduralTools, CourtSessionTools
  • LegalActsTools, ECHRPracticeTools, CourtStatusTools
  • DueDiligenceTools, OsintProxyTools, SpendingTools
  • OpenDataTools, OpenDataRegistriesTools, Tier1OpenDataTools
  • DocumentAnalysisTools, BatchDocumentTools, VaultTools
  • NextcloudTools, DecisionLayerTools, ImportTaskTools
  • WorkflowMemoryTools, AnalyzeDataTool, RegistrySearchTool

mcp_rada Factory

mcp_rada/src/factories/
└── rada-services.ts      # createRadaCoreServices()

Composes: Database, RadaAPIAdapter, ZakonRadaAdapter, CostTracker, DeputyService, FactionService, CommitteeService, BillService, LegislationService, VotingService, CrossReferenceService, MCPRadaAPI.

Adapter Pattern

Adapters isolate external data sources and third-party APIs behind stable interfaces, enabling independent evolution.

Domain Adapters (mcp_backend/src/adapters/)

Adapter Purpose
EdsrLocalAdapter Court decision retrieval from local PostgreSQL/Qdrant (replaces deprecated ZakonOnline API). Instantiated per domain: court decisions, practice, sessions, legal acts, ECHR.
RadaLegislationAdapter Fetches legislation full text from Verkhovna Rada API (zakon.rada.gov.ua), parses HTML with Cheerio.
OsintProxyAdapter Proxies OSINT tool calls to an external OSINT API service via HTTP.

Infrastructure Adapters (mcp_backend/src/infrastructure/adapters/)

Adapter Purpose
LLMAdapter Adapts the shared LLMClientManager behind the ILLMPort domain interface.
CacheAdapter Adapts Redis client behind the ICachePort domain interface.

Domain Ports (mcp_backend/src/domain/ports/)

Clean architecture boundary - defines interfaces that infrastructure adapters implement:

  • IDatabase - database access contract
  • IEmbeddingPort - vector embedding and similarity search
  • ILLMPort - LLM completion (chat, streaming)
  • ICachePort - key-value caching

Tool Handler Pattern

All tools extend BaseToolHandler which defines:

abstract class BaseToolHandler {
  abstract getToolDefinitions(): ToolDefinition[];
  abstract executeTool(name: string, args: any): Promise<ToolResult | null>;
  async executeToolStream?(name: string, args: any, callback: StreamEventCallback): Promise<ToolResult | null>;
}

The ToolRegistry aggregates handlers and provides unified dispatch with per-tool timeout configuration. Remote service tools (RADA, OpenReyestr) are routed via RemoteServiceClient.

Frontend Architecture

The frontend (lexwebapp/) is a React 19 application built with Vite 8, TailwindCSS, Zustand 5 for state management, and TanStack Query for server-state caching.

Architecture Layers

  • Routing: React Router with protected routes and AuthGuard
  • Service Layer: Typed API clients (AuthService, ConversationService, MCPService, BillingService, ConsultationService, MatterService, WorkflowService, etc.)
  • State Management: Zustand stores (chat, UI, settings, upload, encryption, consultations, workflows, timers) + TanStack Query for server cache
  • Streaming: SSEClient for real-time tool execution progress
  • UI Components: Custom design system with TailwindCSS

Frontend Data Flow

sequenceDiagram
    participant UI as UI Component
    participant Hook as useMCPTool Hook
    participant Service as MCPService
    participant SSE as SSEClient
    participant API as Backend API

    UI->>Hook: executeTool(args)
    Hook->>Service: callStream(tool, args)
    Service->>SSE: connect(endpoint)
    SSE->>API: POST /api/tools/:name/stream
    API-->>SSE: SSE Event (Thinking)
    SSE-->>Hook: onProgress(step)
    Hook-->>UI: Update Rendering
    API-->>SSE: SSE Event (Complete)
    SSE-->>Service: Final JSON
    Service-->>Hook: Result
    Hook-->>UI: Final State
Loading

Storage Layer

Service PostgreSQL Vector Store Cache Object Storage
mcp_backend Port 5432 (+ PgBouncer) Qdrant (6333/6334) Redis 7 (6379) MinIO (S3-compatible)
mcp_rada Shared PG, separate schema (rada) - Shared Redis -
mcp_openreyestr Port 5435 (dedicated) - Redis (6382) -

Production adds:

  • PgBouncer for connection pooling
  • Prometheus + Grafana + cAdvisor for monitoring
  • Postgres/Redis exporters for metrics
  • Coturn for WebRTC (video calls)
  • Nextcloud for task management (Deck boards)

Deployment Architecture

Environments

Environment URL Notes
Local localhost:3000 / https://local.legal.org.ua Full stack in Docker Compose
Production https://legal.org.ua Blue-green deployment

There is no staging environment.

Blue-Green Production Deployment

Production uses blue-green deployment tracked via .active-colors file per service group:

flowchart LR
    Nginx[Nginx Reverse Proxy] --> |active color| Active[Active containers]
    Nginx -.-> |inactive color| Standby[Standby containers]

    subgraph Active
        AppBlue[app-prod]
        RadaBlue[rada-mcp-app-prod]
        OpenBlue[app-openreyestr-prod]
        LexBlue[lexwebapp-prod]
    end

    subgraph Standby
        AppGreen[app-prod-green]
        RadaGreen[rada-mcp-app-prod-green]
        OpenGreen[app-openreyestr-prod-green]
        LexGreen[lexwebapp-prod-green]
    end
Loading

New deployments build the inactive color, run migrations, start containers, then switch Nginx upstreams.

CI/CD Pipelines

  1. ci-local-deploy.yml (on push to main): detect changed services, build, test, deploy to local Docker, health check.
  2. deploy-prod.yml (after successful local CI or manual): pre-deploy tests, blue-green deploy via SSH, migration, preview, switch, semantic version tagging.

Both pipelines include self-healing via Claude Code agent (creates autofix PRs on failure).

Docker Artifacts

Dockerfile Service
Dockerfile.mono-backend mcp_backend
Dockerfile.mono-rada mcp_rada
Dockerfile.mono-openreyestr mcp_openreyestr
Dockerfile.document-service Document processing / OCR
Dockerfile.edrsr-fulltext-worker EDRSR full-text indexing worker
Dockerfile.opendata-sync Open data synchronization
Dockerfile.terminal-service Terminal service
Dockerfile.local-scraper Local data scraping
Dockerfile.offshore-import Offshore data import
Dockerfile.r-retrieval R-based retrieval experiments

Management Script

cd deployment
./manage-gateway.sh start local      # Start all services
./manage-gateway.sh deploy local     # Full rebuild (--no-cache)
./manage-gateway.sh logs local       # View logs
./manage-gateway.sh stop local       # Stop
./manage-gateway.sh status           # Container status
./manage-gateway.sh health           # Health checks

Compose invocation requires explicit env file:

docker compose -f docker-compose.local.yml --env-file .env.local up -d
docker compose -f docker-compose.prod.yml --env-file .env.prod up -d

Authentication

The platform supports multiple authentication methods:

  • Bearer token (SECONDARY_LAYER_KEYS) for API clients
  • JWT session tokens for web users
  • Google OAuth
  • Authentik (OIDC)
  • Diia (Ukrainian digital identity)
  • Password (local accounts)
  • WebAuthn (passkeys/FIDO2)

Technology Stack Summary

Layer Technology
Runtime Node.js 20+ (TypeScript 5.3)
AI Providers OpenAI (GPT-5 family), Anthropic (Claude via Bedrock + direct), Voyage AI (embeddings)
Databases PostgreSQL 15 (+ PgBouncer), Redis 7, Qdrant (vectors)
Object Storage MinIO (S3-compatible)
Frontend React 19, Vite 8, TailwindCSS, Zustand 5, TanStack Query
Mobile Flutter (Dart)
MCP SDK @modelcontextprotocol/sdk v1.27+
Infrastructure Docker Compose, Nginx, Prometheus, Grafana
CI/CD GitHub Actions (self-hosted runner)
Payments Monobank API (dual UAH/USD balance)
Task Management Nextcloud Deck, Plane
Relevant source files

Clone this wiki locally