Architecture: MCP Framework & Yandex Tracker Server

December 9, 2025 · View on GitHub

Monorepo Architecture Overview


🎯 Monorepo Principles

1. Package Independence

Каждый пакет может быть опубликован и использован независимо.

2. Clear Dependency Graph

Строгая иерархия зависимостей без циклов.

3. Shared Infrastructure

Общие компоненты (infrastructure, core) переиспользуются.

4. Topological Build Order

Сборка автоматически учитывает зависимости пакетов.


📦 Monorepo Structure

packages/
├── framework/
│   ├── infrastructure/     → @fractalizer/mcp-infrastructure
│   │   ├── http/, cache/, async/, logging/
│   │   └── 0 dependencies
│   ├── cli/               → @fractalizer/mcp-cli
│   │   └── depends on: infrastructure
│   ├── core/              → @fractalizer/mcp-core
│   │   ├── tools/base/, utils/, tool-registry
│   │   └── depends on: infrastructure
│   └── search/            → @fractalizer/mcp-search
│       ├── engine/, strategies/, tools/
│       └── depends on: core
└── servers/
    └── yandex-tracker/    → mcp-server-yandex-tracker
        ├── api_operations/, entities/, tools/, composition-root/
        └── depends on: infrastructure, cli, core, search

🔗 Dependency Graph

┌─────────────────┐
│ infrastructure  │ ← Base layer (HTTP, logging, cache, async)
└────────┬────────┘


┌─────────────────┐
│      core       │ ← Framework core (BaseTool, registry, utilities)
└────────┬────────┘


┌─────────────────┐
│     search      │ ← Tool discovery (search engine, strategies)
└────────┬────────┘


┌─────────────────┐
│ yandex-tracker  │ ← Application (Yandex.Tracker integration)
└─────────────────┘

Rules:

  • ❌ No reverse dependencies (core → infrastructure)
  • ❌ No imports from yandex-tracker to framework packages
  • ✅ Dependencies flow top-down only

Validation:

npm run depcruise  # Validates dependency graph

📦 Package Details

@fractalizer/mcp-infrastructure

Purpose: Reusable infrastructure layer (domain-agnostic)

Components:

  • HTTP Layer: HttpClient (Axios wrapper), RetryHandler, ErrorMapper
  • Caching: CacheManager interface, NoOpCache
  • Async: ParallelExecutor (batch throttling)
  • Logging: Pino with rotating-file-stream
  • Config: Environment variable loading and validation

Key Principle: Infrastructure does NOT know about domain (Yandex.Tracker, MCP)

Details: packages/framework/infrastructure/README.md

@fractalizer/mcp-core

Purpose: Core framework for building MCP tools

Components:

  • Base Classes: BaseTool, BaseDefinition
  • Tool Registry: ToolRegistry (lazy initialization)
  • Utilities: ResponseFieldFilter, BatchResultProcessor, ResultLogger
  • Schemas: Common Zod schemas (fields, expand, issue-key)

Key Principle: Generic BaseTool<TFacade> — facade-agnostic design

Details: packages/framework/core/README.md

Purpose: Advanced tool discovery with compile-time indexing

Components:

  • Engine: ToolSearchEngine (LRU cache)
  • Strategies: Name, Description, Category, Fuzzy, WeightedCombined
  • Tools: SearchToolsTool (MCP tool for Claude)
  • Index: generated-index.ts (auto-generated at build)

Key Principle: Compile-time indexing (zero runtime overhead)

Details: packages/framework/search/README.md

mcp-server-yandex-tracker

Purpose: Complete MCP server for Yandex.Tracker API v3

Components:

  • API Operations: Batch operations for issues, users, comments
  • Entities: Domain types (Issue, User, Queue, etc.)
  • DTO: Data Transfer Objects (create, update requests)
  • MCP Tools: API tools + helpers
  • DI: InversifyJS composition root

Key Principle: Built on framework packages (infrastructure, core, search)

Details: packages/servers/yandex-tracker/README.md, packages/servers/yandex-tracker/CLAUDE.md


🏗️ Architectural Principles (Shared)

1. Feature-by-Folder

Группируем файлы по функциональности, а не по типу.

✅ Правильно:

api/http/retry/
├── retry-handler.ts
├── retry-strategy.interface.ts
└── exponential-backoff.strategy.ts

❌ Неправильно:

strategies/
└── exponential-backoff.ts
handlers/
└── retry-handler.ts

2. Single Responsibility Principle (SRP)

Каждый класс/файл отвечает ТОЛЬКО за одну задачу.

3. Dependency Injection

Все зависимости через конструктор (InversifyJS в yandex-tracker).

4. Interface Segregation

Минимальные, специфичные интерфейсы.

5. Open/Closed Principle

Открыто для расширения, закрыто для модификации.


📥 Module System: Node.js Subpath Imports

Решение (2025): Проект использует Node.js Subpath Imports вместо TypeScript path aliases.

Почему Subpath Imports?

  1. Нативная поддержка Node.js (12.19.0+) - не требует tsc-alias для внутренних импортов
  2. Рекомендация Turborepo team для monorepo проектов
  3. Одна точка конфигурации - только package.json (не нужны tsconfig paths)
  4. Полная поддержка TypeScript 5.4+ - автокомплит, LSP, навигация
  5. Избежание конфликтов - префикс # не конфликтует с npm scoped packages

Конфигурация

package.json (yandex-tracker):

{
  "imports": {
    "#tracker_api/*": "./src/tracker_api/*",
    "#tools/*": "./src/tools/*",
    "#composition-root/*": "./src/composition-root/*",
    "#cli/*": "./src/cli/*",
    "#constants": "./src/constants.ts",
    "#common/*": "./src/common/*",
    "#integration/*": "./tests/integration/*",
    "#helpers/*": "./tests/helpers/*"
  }
}

Правила импортов

1. Междупакетные (npm package names):

import { BaseTool } from '@fractalizer/mcp-core';
import { HttpClient } from '@fractalizer/mcp-infrastructure';

2. Внутрипакетные короткие (≤2 уровня - относительные пути):

import { validateInput } from './utils.js';
import { BaseOperation } from '../base-operation.js';

3. Внутрипакетные глубокие (≥3 уровня - subpath imports):

import { MCP_TOOL_PREFIX } from '#constants';
import { YandexTrackerFacade } from '#tracker_api/facade/yandex-tracker.facade.js';
import { createFixture } from '#helpers/queue.fixture.js';

Миграция с TypeScript Path Aliases

Было (TypeScript paths):

{
  "compilerOptions": {
    "paths": {
      "@tracker_api/*": ["./src/tracker_api/*"],
      "@tools/*": ["./src/tools/*"]
    }
  }
}

Стало (Node.js subpath imports):

{
  "imports": {
    "#tracker_api/*": "./src/tracker_api/*",
    "#tools/*": "./src/tools/*"
  }
}

Детали миграции: См. .agentic-planning/plan_migrate_to_subpath_imports/


🔄 Data Flow (Yandex Tracker Server)

Request Chain:

1. Claude Desktop (MCP Client)
   ↓ JSON-RPC via stdio
2. MCP Server (index.ts)
   ↓ tools/call
3. ToolRegistry
   ↓ route to tool
4. Concrete Tool (e.g., GetIssuesTool)
   ↓ validate params (Zod)
5. YandexTrackerFacade
   ↓ delegate to operation
6. Operation (e.g., GetIssuesOperation)
   ↓ business logic
7. HttpClient (with retry)
   ↓ HTTPS request
8. Yandex.Tracker API v3
   ↓ response
9. IssueWithUnknownFields (preserves unknown fields)
   ↓ filter fields
10. ResponseFieldFilter
   ↓ format for Claude
11. Tool returns result

Layer Responsibilities:

  • Tools — validation, formatting for Claude
  • Facade — high-level API for tools
  • Operations — business logic
  • Infrastructure — HTTP, retry, cache, logging

Batch Operations Flow

All read and write operations support batch mode for working with multiple issues efficiently.

GET operations (parallel data fetching):

Tool (issueIds[]) → Operation.executeMany()
  → ParallelExecutor (respects maxConcurrentRequests from ServerConfig)
    → N × API calls (parallel, with throttling)
  → BatchResult<string, Data>
  → BatchResultProcessor.process()
    → Unified format: { total, successful, failed, fieldsReturned }

POST/DELETE operations (parallel modification):

Tool (items[]) → Operation.executeMany()
  → ParallelExecutor (throttles to maxConcurrentRequests)
    → N × API calls (each with individual params)
  → BatchResult<string, Response>
  → BatchResultProcessor.process()
    → Unified format: { total, successful, failed }

Key components:

  • ParallelExecutor — enforces maxConcurrentRequests from ServerConfig
  • BatchResultProcessor — unifies result processing for all batch operations
  • Unified Batch Format — consistent response structure across all operations

Two patterns:

  1. GET batch (shared parameters): Single set of parameters (perPage, expand) applied to all issues
    • Schema: issueIds: IssueKeysSchema (array, min 1)
  2. POST/DELETE batch (individual parameters): Each issue has its own parameters
    • Schema: array of objects [{ issueId, ...params }]

Examples: get-comments.tool.ts, add-comment.tool.ts, delete-link.tool.ts


🔄 Schema-to-Definition Generator

Problem: Manual creation of MCP definitions leads to schema-definition mismatch bugs.

Solution: Automatic generation of MCP definition from Zod schema.

Architecture

Zod Schema (*.schema.ts)

generateDefinitionFromSchema()

MCP Definition (runtime)

Implementation

Tool class:

export class GetIssuesTool extends BaseTool<typeof GetIssuesSchema> {
  getDefinition(): ToolDefinition {
    return generateDefinitionFromSchema(this.metadata, GetIssuesSchema);
  }
}

Generator (@fractalizer/mcp-core):

  • Uses Zod v4 native toJSONSchema() API
  • Converts JSON Schema to MCP Definition format
  • Extracts descriptions from .describe() calls
  • Validates required vs optional fields

Benefits

  • DRY Principle — single source of truth (schema)
  • No Mismatch — physically impossible to create inconsistency
  • Simpler Tools — no separate *.definition.ts files
  • Auto-sync — schema changes automatically reflected in definition

Migration

Old: Separate *.schema.ts + *.definition.ts files (removed) New: Only *.schema.ts with generateDefinitionFromSchema()

Details: .agentic-planning/plan_prevent_schema_definition_mismatch_bugs/


📦 Entities & DTO: Forward Compatibility

Pattern: Separate types by data flow direction

Incoming (from API): *WithUnknownFields

// packages/servers/yandex-tracker/src/entities/issue.entity.ts
export interface Issue { /* known fields */ }
export type IssueWithUnknownFields = WithUnknownFields<Issue>;

Purpose: Preserve unknown fields added by Yandex.Tracker

Outgoing (to API): Strict DTO

// packages/servers/yandex-tracker/src/dto/issue/update-issue.dto.ts
export interface UpdateIssueDto {
  summary?: string;
  description?: string;
  // NO index signature (type-safe)
}

Purpose: Type-safe requests

Details: packages/servers/yandex-tracker/src/entities/README.md, packages/servers/yandex-tracker/src/dto/README.md


🚀 Batch Operations (Yandex Tracker)

Principle: All collection operations use batch approach

Pattern:

  • getIssues(keys[]) — batch get
  • createIssues(requests[]) — batch create
  • updateIssues(items[]) — batch update

Why:

  • Universality (1 or N items)
  • Automatic throttling (ParallelExecutor)
  • Simplified architecture (no code duplication)

Implementation:

// ParallelExecutor with 2 independent limits
const executor = new ParallelExecutor(config);
const results = await executor.execute(
  keys,
  (key) => httpClient.get<Issue>(`/v3/issues/${key}`)
);
// results: BatchResult<string, Issue>

Limits:

  1. MAX_BATCH_SIZE (business): 200 items per chunk
  2. MAX_CONCURRENT_REQUESTS (technical): 5 concurrent requests

Result Type: BatchResult<T> (discriminated union: fulfilled | rejected)

Details: packages/framework/infrastructure/README.md


🔧 Dependency Injection (Yandex Tracker)

Approach: InversifyJS v7 with Symbol-based tokens

Structure:

packages/servers/yandex-tracker/src/composition-root/
├── types.ts           # Symbol tokens (TYPES.HttpClient, etc.)
├── container.ts       # Container configuration
└── definitions/       # Declarative definitions
    ├── tool-definitions.ts
    └── operation-definitions.ts

Benefits:

  • Works with interfaces
  • Easy testing (rebind)
  • Explicit contracts

Details: packages/servers/yandex-tracker/src/composition-root/README.md


🔍 Tool Search System

Architecture:

  1. Compile-time Indexing:

    npm run build
    # → runs scripts/generate-tool-index.ts
    # → generates packages/search/src/generated-index.ts
    
  2. Runtime Search:

    const engine = new ToolSearchEngine(TOOL_INDEX);
    const results = engine.search('find issues');
    
  3. 5 Search Strategies:

    • NameSearchStrategy (exact/partial match)
    • DescriptionSearchStrategy (word matching)
    • CategorySearchStrategy (category filter)
    • FuzzySearchStrategy (Levenshtein distance)
    • WeightedCombinedStrategy (combine all)
  4. LRU Cache:

    • Max 100 entries
    • Key: ${query}_${strategy}

Details: packages/framework/search/README.md


🔒 Architecture Validation (dependency-cruiser)

Rules:

  1. Layered Architecture

    • yandex-tracker не импортирует в framework пакеты
    • infrastructure не импортирует domain слои
  2. Package Boundaries

    • Импорты между пакетами только через npm package names
    • Нет относительных путов между пакетами
  3. MCP Isolation (yandex-tracker)

    • Tools используют только Facade, не Operations напрямую
    • Разрешены импорты entities/dto для типов
  4. No Circular Dependencies

    • Запрещены циклические зависимости

Validation:

npm run depcruise           # Check all rules
npm run depcruise:graph     # Generate dependency graph

Config: .dependency-cruiser.cjs

Integration: Rules checked in npm run validate


🧪 Testing Strategy

Unit Tests

Structure: packages/*/tests/ mirrors packages/*/src/

Framework: Vitest (ESM + TypeScript)

Coverage: ≥80% for all packages

Patterns:

  • AAA (Arrange, Act, Assert)
  • Mocks for external dependencies
  • Test both happy path and error cases

Commands:

npm run test                    # All packages
npm run test:coverage           # With coverage
npm run test --workspace=@fractalizer/mcp-core  # Single package

Details: packages/servers/yandex-tracker/tests/README.md


📋 Adding New Functionality

Adding Framework Package

  1. Create packages/new-package/
  2. Add package.json with correct dependencies
  3. Add tsconfig.json with project references
  4. Update root package.json workspaces
  5. Update root tsconfig.json references
  6. Update .dependency-cruiser.cjs rules
  7. Create README.md
  8. npm install && npm run build

Adding MCP Tool (in yandex-tracker)

  1. Create structure:

    packages/servers/yandex-tracker/src/mcp/tools/{api|helpers}/{feature}/{action}/
    ├── {name}.schema.ts
    ├── {name}.definition.ts
    ├── {name}.tool.ts
    └── index.ts
    
  2. Add to registry:

    // packages/servers/yandex-tracker/src/composition-root/definitions/tool-definitions.ts
    export const TOOL_CLASSES = [
      // ...
      NewTool,
    ] as const;
    
  3. Tests + npm run validate

Details: packages/servers/yandex-tracker/src/mcp/README.md

Adding API Operation (in yandex-tracker)

  1. Create packages/servers/yandex-tracker/src/api_operations/{feature}/{action}/{name}.operation.ts
  2. Extend BaseOperation
  3. Add facade method
  4. Register in packages/servers/yandex-tracker/src/composition-root/definitions/operation-definitions.ts
  5. Tests + npm run validate

Details: packages/servers/yandex-tracker/src/api_operations/README.md


🚀 Build & Release Process

Build Order (Topological)

npm run build
# Builds in order:
# 1. infrastructure
# 2. core (depends on infrastructure)
# 3. search (depends on core)
# 4. yandex-tracker (depends on all)

Version Management

Tool: Changesets (https://github.com/changesets/changesets)

Workflow:

  1. npx changeset add — describe changes
  2. npx changeset version — bump versions
  3. git commit && git push
  4. GitHub Actions publishes to npm

Manual publish:

npm run publish:all

🔍 Code Quality Tools

Linting & Formatting

  • ESLint — code quality (max-params, complexity)
  • Prettier — code formatting (via pre-commit hook)
  • TypeScript — type checking (strict mode)

Security

  • Socket.dev — supply-chain analysis
  • Gitleaks — secret scanning (pre-commit hook)

Dead Code Detection

  • Knip — unused files/exports/dependencies

Lockfile Validation

  • Ensures package-lock.json is in sync

Run all:

npm run validate

📚 Documentation Structure

Monorepo Root

Framework Packages

Yandex Tracker


🎯 Design Patterns Used

Framework Level

  • Strategy Pattern — Search strategies, retry strategies
  • Null Object — NoOpCache
  • Factory Pattern — Tool creation in registry
  • Template Method — BaseTool, BaseDefinition

Application Level (Yandex Tracker)

  • Facade Pattern — YandexTrackerFacade
  • Registry Pattern — ToolRegistry
  • Dependency Injection — InversifyJS container
  • Repository Pattern — Operations as repositories

📊 Performance Considerations

Compile-time Optimization

  • Tool index generated at build (not runtime)
  • TypeScript compilation with project references
  • Incremental builds

Runtime Optimization

  • Lazy tool initialization (ToolRegistry)
  • LRU cache (tool search)
  • Batch operations (parallel execution)
  • Field filtering (80-90% response size reduction)

Bundle Size

  • Tree-shaking friendly (ESM modules)
  • Separate packages (install only what you need)
  • No dynamic requires

🔗 External Resources