This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Syrin is a runtime intelligence system for MCP (Model Context Protocol) servers. It helps developers validate, test, and reason about MCP execution before production deployment through static analysis, contract-based testing, and interactive development environments.
# Build the project (TypeScript compilation + path alias resolution + template copy)
npm run build
# Run Syrin CLI locally (rebuilds and executes)
npm run syrin <command>
# Example: Run dev mode locally
npm run syrin dev --exec# Run all tests (watch mode)
npm test
# Run tests once
npm run test:run
# Run tests with coverage
npm run test:coverage
# Run tests with UI
npm run test:ui
# Run specific test file
npm test -- src/runtime/dev/session.test.ts# Lint the codebase
npm run lint
# Lint and fix issues
npm run lint:fix
# Format code (Prettier)
npm run format
# Check formatting
npm run format:check
# Type check without building
npm run type-check# Prepare for publish (runs build automatically)
npm run prepublishOnlySyrin uses a layered, event-driven architecture with five primary layers:
Entry point using Commander.js. Commands map to handlers that orchestrate lower layers:
init: Initialize project configurationdoctor: Validate configuration and environmentdev: Interactive LLM-MCP development environmenttest: Contract-based tool testing with sandboxinganalyse: Static validation of MCP tool contractslist: Inspect tools, resources, and promptsconfig: Manage local/global configurationupdate/rollback: Version management
All commands use unified error handling via command-error-handler.ts.
Dual-layer configuration system:
- Local Config:
syrin.yamlin project root (all settings) - Global Config:
~/.syrin/syrin.yaml(LLM providers only)
Key files:
loader.ts: Loads local config (project-specific)global-loader.ts: Loads global config (user-wide)merger.ts: Implements precedence (local > global > CLI flags)schema.ts: Zod validation for both config typesenv-checker.ts: Environment variable validation
Precedence order: CLI Flags > Local Config > Global Config > Defaults
Core processing subsystems:
- Manages connections to MCP servers
manager.ts: HTTPMCPClientManager and StdioMCPClientManagerclient/base.ts: Base functionality for tool discovery/executionconnection.ts: Creates MCP client instances with transport setup
- Abstracts multiple LLM providers (OpenAI, Claude, Ollama)
factory.ts: Factory pattern for provider instantiationLLMProviderinterface with unifiedchat()method- Each provider normalizes tool calls to common format
- Interactive LLM-MCP development environment
session.ts: Orchestrates LLM-MCP interactions with state managementrepl.ts: Interactive command loop with historyevent-mapper.ts: Maps MCP/LLM events to runtime event typesdata-manager.ts: Manages session state and conversation history
- Static validation of MCP tools
analyser.ts: Main orchestrator running all rules- Pipeline: load → normalize → index → infer dependencies → run rules
- 36 validation rules (E000-E600 errors, W100-W301 warnings)
- Each rule extends
BaseRuleand implements semantic checks
- Contract-based testing with sandboxing
orchestrator.ts: Coordinates test executioncontract-loader.ts: Loads tool test specifications fromtools/*.yamlbehavior-observer.ts: Detects side effects, non-determinism, output explosionrunner.ts: Executes tools in isolated sandbox
- Process isolation for tool execution
executor.ts: Spawns and manages MCP server processesio-monitor.ts: Captures stdout/stderr with size/time limits- Memory and timeout enforcement for safety
Persistent, typed event streaming throughout the application:
Event Categories (36 types across 10 categories):
- A: Session lifecycle (5 events)
- B: Workflow/steps (5 events)
- C: LLM context (2 events)
- D: LLM proposals (3 events) - non-authoritative
- E: Validation (5 events) - runtime authority
- F: Tool execution (3 events) - ground truth
- G: Transport (4 events)
- H: Registry (4 events)
- I: Testing (2 events)
- J: Diagnostics (3 events)
Key components:
emitter.ts:RuntimeEventEmitterwith auto-incrementing sequence numbersstore.ts: Event persistence interfaceMemoryEventStore: In-process storageFileEventStore: JSONL files in.syrin/events/<sessionId>.jsonl
payloads/: Typed payload definitions for each category
Authority hierarchy: LLM Proposals (non-authoritative) → Validation (authoritative) → Tool Execution (ground truth)
Separates UI concerns from business logic:
dev-ui.ts: Formats tools, history, results for dev modeanalysis-ui.ts: Diagnostics and dependency graph formattingtest-ui.ts: Test result formattingconfig-ui.ts: Configuration display helpersdoctor-ui.ts: Health check outputlist-ui.ts: Tool/resource listing formatting
Used throughout for type safety of IDs:
type SessionID = Opaque<string, 'SessionID'>;
type EventID = Opaque<string, 'EventID'>;
// Factory functions prevent accidental string assignment
makeSessionID(value: string): SessionIDBenefits: Compile-time safety, prevents mixing IDs, self-documenting.
All validation rules extend BaseRule:
abstract class BaseRule {
abstract check(ctx: AnalysisContext): Diagnostic[]
}- Rules registered in
rules/index.tsasALL_RULESarray - Orchestrator loops through rules, catches failures
- Result: Decoupled, pluggable validation system
Centralizes dependency construction:
createLLMProvider(): Maps provider name → class instantiationcreateMCPClientManager(): HTTP vs Stdio transport selection
EventEmitter.subscribe(callback): UnsubscribeFnEnables UI updates without tight coupling. Supports both sync and async subscribers.
Each command follows:
- Load/merge config
- Resolve transport
- Connect to MCP/create session
- Run core logic
- Format output (CLI/JSON/CI modes)
- Close connection safely
- Exit with appropriate code
Use @/ for imports instead of relative paths:
import { loadConfig } from '@/config/loader';
import { RuntimeEventEmitter } from '@/events/emitter';Configured in tsconfig.json and resolved by tsc-alias during build.
Always emit events for significant actions:
eventEmitter.emit('TOOL_EXECUTION_STARTED', {
toolName,
input,
timestamp: Date.now()
});Use typed event names from event-type.ts and typed payloads from payloads/.
Use custom error types from utils/errors.ts:
ConfigurationError: Config validation failuresEventStoreError: Event persistence issues
All commands wrapped by command-error-handler.ts for consistent error UX.
- Test files:
*.test.tsco-located with source files - Use Vitest with
describe/it/expect - Test helper files:
__test-helpers__.ts - Coverage target: Focus on runtime engines and command logic
User runs: syrin dev --exec
→ Load local+global config (merger.ts)
→ Create EventEmitter with FileEventStore
→ Create MCPClientManager (HTTP or Stdio)
→ Create LLMProvider from config
→ Create DevSession (session.ts)
→ Start InteractiveREPL
User types query
→ REPL sends to DevSession.processInput()
→ LLMProvider.chat(history, tools)
→ Emit: LLM_CONTEXT_BUILT, LLM_PROPOSED_TOOL_CALL
→ MCPClientManager.executeTool() → Emit: TOOL_EXECUTION_*
→ Emit: VALIDATION_*, SESSION_COMPLETED
→ ChatUI formats and displays results
→ Events persisted to .syrin/events/{sessionId}.jsonl
User runs: syrin analyse
→ Load config, resolve transport
→ Connect to MCP server
→ analyseTools(client):
→ loadMCPTools()
→ normalizeTools()
→ buildIndexes()
→ inferDependencies()
→ runRules() [40+ rules]
→ displayAnalysisResult(result, { json?, ci?, graph? })
→ Exit with code 0 (pass) or 1 (fail)
User runs: syrin test --tool <name>
→ Load config
→ TestOrchestrator.runTests()
→ loadAllContracts() from tools/*.yaml
→ For each contract:
→ SandboxExecutor.execute()
→ BehaviorObserver tracks side effects
→ validateOutputStructure()
→ runContractTests()
→ Generate Diagnostics (E200-E600)
→ displayTestResults()
→ Verdict (pass/pass-with-warnings/fail)
Config provides transport type, credentials, LLM defaults. Schema validation ensures valid state before runtime initialization.
Every significant action emits typed events. Events provide complete audit trail. Test/analysis failures generate diagnostic events.
Event formatters in presentation layer support multiple output modes (CLI, JSON, CI). ChatUI subscribes to events for real-time updates.
MCP provides tools → LLM proposes tool calls → Validation checks calls before execution → Events record decision authority chain.
Tool contracts define behavioral guarantees in tools/<tool-name>.yaml:
version: 1
tool: fetch_user
contract:
input_schema: FetchUserRequest
output_schema: User
guarantees:
side_effects: none
max_output_size: 10kbTesting validates:
- Side effects detection
- Output size limits
- Non-determinism
- Schema compliance
- Hidden dependencies
Global Config (~/.syrin/syrin.yaml):
- LLM providers only
- Shared across projects
- User-wide defaults
Local Config (syrin.yaml):
- Complete configuration (transport, MCP connection, LLM)
- Project-specific
- Overrides global settings
# Set up global configuration
syrin config setup --global
# Set API keys in global .env
syrin config edit-env --global
# Set provider settings
syrin config set openai.model "gpt-4-turbo" --global
# View config
syrin config list --global- Create command handler in
src/cli/commands/<name>.ts - Implement command logic using existing patterns
- Add unit tests in
src/cli/commands/<name>.test.ts - Register command in
src/cli/index.ts - Add presentation formatter in
src/presentation/<name>-ui.tsif needed
- Create rule file in
src/runtime/analysis/rules/<category>/ - Extend
BaseRuleclass - Implement
check(ctx: AnalysisContext): Diagnostic[] - Add rule to
ALL_RULESinsrc/runtime/analysis/rules/index.ts - Add tests in co-located
.test.tsfile
- Create provider class in
src/runtime/llm/<provider>.ts - Implement
LLMProviderinterface - Add factory case in
src/runtime/llm/factory.ts - Update config schema in
src/config/schema.ts - Add environment variable to
env-checker.ts - Add tests in
src/runtime/llm/<provider>.test.ts
- Always emit events for state changes
- Use typed event names from
event-type.ts - Create typed payloads in
payloads/if needed - Subscribe to events in UI layer for real-time updates
- Events are persisted to
.syrin/events/for audit trails
- Path Resolution: Always use
@/aliases, never relative paths beyond parent directory - Template Copying: Run
npm run buildafter modifyingsyrin.template.yaml(manual copy step) - Event Sequence: Event emitter auto-increments sequence numbers per session
- Config Merging: Local LLM config overrides global's default provider but keeps other global providers
- Transport Resolution: Stdio spawns process, HTTP connects to existing server
- Opaque Types: Use factory functions (
makeSessionID), never cast strings directly - Error Codes: E100-E600 for errors, W100-W300 for warnings (strict mode converts warnings to errors)
- Main docs: https://docs.syrin.dev
- Commands:
docs/Commands/ - Configuration: Referenced in README.md
- Tool Contracts:
docs/tool-contracts.md