Skip to content

Latest commit

 

History

History
654 lines (505 loc) · 16.8 KB

File metadata and controls

654 lines (505 loc) · 16.8 KB

pforge Architecture Documentation

Version: 0.1.0 Last Updated: 2025-10-03 Status: Production-Ready


Table of Contents

  1. Overview
  2. High-Level Architecture
  3. Component Design
  4. Data Flow
  5. Performance Architecture
  6. Security Architecture
  7. Extension Points
  8. Design Decisions

Overview

Design Philosophy

pforge follows these core architectural principles:

  1. Zero-Cost Abstractions: Declarative configuration compiles to optimal Rust code
  2. Type Safety: Compile-time guarantees wherever possible
  3. Performance First: Sub-microsecond dispatch, > 100K req/s throughput
  4. Production Ready: Built-in observability, error handling, and resilience

Architecture Goals

Goal Implementation Status
Cold start < 100ms Ahead-of-time compilation ✅ Achieved (< 50ms)
Dispatch < 1μs FxHash O(1) lookup ✅ Achieved (83-90ns)
Throughput > 100K req/s Lock-free registry ✅ Achieved (5.3M ops/s)
Memory < 512KB Zero-copy, arena allocation ✅ Achieved (< 300KB)
Type safety Compile-time schema validation ✅ Full coverage

High-Level Architecture

Component Stack

┌─────────────────────────────────────────────────────────┐
│                    pforge CLI                           │
│              (Scaffold, Build, Dev, Test)               │
└─────────────────────────────────────────────────────────┘
                           │
                           ▼
┌─────────────────────────────────────────────────────────┐
│                  pforge-codegen                         │
│           (YAML → Rust AST → Optimized Runtime)        │
└─────────────────────────────────────────────────────────┘
                           │
                           ▼
┌─────────────────────────────────────────────────────────┐
│                  pforge-runtime                         │
│  (Handler Registry, Type-safe validation, Middleware)   │
└─────────────────────────────────────────────────────────┘
                           │
                           ▼
┌─────────────────────────────────────────────────────────┐
│                    pmcp v1.6+                           │
│  (TypedTool, Multi-transport, SIMD JSON - 16x faster)  │
└─────────────────────────────────────────────────────────┘
                           │
                           ▼
┌─────────────────────────────────────────────────────────┐
│           MCP Protocol v2024-10-07                      │
│               (JSON-RPC 2.0 over transport)             │
└─────────────────────────────────────────────────────────┘

Workspace Structure

pforge/
├── crates/
│   ├── pforge-cli/        # CLI binary and commands
│   ├── pforge-runtime/    # Core runtime and handler registry
│   ├── pforge-codegen/    # Code generation engine
│   ├── pforge-config/     # Configuration parsing and validation
│   ├── pforge-macro/      # Procedural macros
│   └── pforge-bridge/     # Language bridges (Python, Go, Node.js)
│
├── examples/              # Example servers
├── docs/                  # Documentation
├── benches/               # Performance benchmarks
└── fuzz/                  # Fuzzing infrastructure

Component Design

1. pforge-config: Configuration Layer

Responsibility: Parse and validate YAML configuration

Key Types:

pub struct ForgeConfig {
    pub forge: ForgeMetadata,
    pub tools: Vec<ToolDef>,
    pub resources: Vec<ResourceDef>,
    pub prompts: Vec<PromptDef>,
    pub state: Option<StateDef>,
}

pub enum ToolDef {
    Native { name, handler, params, timeout_ms },
    Cli { name, command, args, env, stream },
    Http { name, endpoint, method, auth },
    Pipeline { name, steps },
}

Design Decisions:

  • Serde-based deserialization for zero-cost parsing
  • Strong typing (enums, not strings) for validation
  • Deny unknown fields to catch typos early

Performance:

  • Parse time: < 10ms for 100-tool config
  • Memory: ~100 bytes per tool definition

2. pforge-runtime: Execution Engine

Responsibility: Handler registry, dispatch, middleware

Handler Trait

#[async_trait]
pub trait Handler: Send + Sync + 'static {
    type Input: JsonSchema + DeserializeOwned + Send;
    type Output: JsonSchema + Serialize + Send;
    type Error: Into<Error>;

    async fn handle(&self, input: Self::Input) -> Result<Self::Output, Self::Error>;
}

Design Rationale:

  • Generic over Input/Output for type safety
  • Async by default (tokio runtime)
  • Zero-copy: &self receiver (no cloning)
  • Send + Sync for concurrent execution

Handler Registry

pub struct HandlerRegistry {
    handlers: DashMap<String, Arc<dyn HandlerTrait>>,
}

impl HandlerRegistry {
    pub fn register<H: Handler>(&mut self, name: &str, handler: H) {
        self.handlers.insert(name.to_string(), Arc::new(handler));
    }

    pub async fn dispatch(&self, name: &str, input: &[u8]) -> Result<Vec<u8>> {
        let handler = self.handlers.get(name)
            .ok_or(Error::ToolNotFound(name.to_string()))?;

        handler.execute(input).await
    }
}

Performance Optimizations:

  1. FxHash instead of SipHash (2x faster for small keys)
  2. DashMap for lock-free concurrent access
  3. Arc for zero-copy handler sharing
  4. Future: Perfect hashing (FKS algorithm) for O(1) worst-case

Benchmarks:

  • Single handler dispatch: 83-90ns
  • Registry with 1000 handlers: 91ns (no degradation)
  • Concurrent dispatch (8 threads): 3.1M ops/s

Middleware Chain

pub trait Middleware: Send + Sync {
    async fn before(&self, req: &Request) -> Result<()>;
    async fn after(&self, req: &Request, res: &Response) -> Result<()>;
}

pub struct MiddlewareChain {
    middlewares: Vec<Arc<dyn Middleware>>,
}

Built-in Middleware:

  • LoggingMiddleware: Request/response logging
  • MetricsMiddleware: Prometheus metrics
  • RecoveryMiddleware: Auto-retry on transient failures
  • TimeoutMiddleware: Enforce execution timeouts
  • ValidationMiddleware: Parameter validation

Execution Order:

Request → Middleware::before() → Handler::handle() → Middleware::after() → Response

3. pforge-codegen: Code Generation

Responsibility: Transform YAML → Rust code

Process:

pforge.yaml
    ↓
Parse (serde_yaml)
    ↓
Validate (pforge-config)
    ↓
Generate AST (syn/quote)
    ↓
Emit Rust code (build.rs)
    ↓
Compile (rustc)
    ↓
Optimized binary

Generated Code Example:

// From pforge.yaml
pub fn create_registry() -> HandlerRegistry {
    let mut registry = HandlerRegistry::new();

    // Generated for each tool
    registry.register("greet", handlers::greet::GreetHandler);
    registry.register("whoami", CliHandler::new("whoami", vec![]));

    registry
}

Optimizations:

  • Const propagation: Tool names are &'static str
  • Inline expansion: Small handlers inlined
  • Dead code elimination: Unused tools removed at compile time

4. pforge-bridge: Language Bridges

Responsibility: FFI for Python, Go, Node.js handlers

Architecture

┌──────────────┐
│ Rust Runtime │
└──────┬───────┘
       │ FFI (stable C ABI)
       │
       ├──► Python Bridge (ctypes)
       ├──► Go Bridge (cgo)
       └──► Node.js Bridge (napi-rs)

Python Bridge Example

Rust side (stable C ABI):

#[no_mangle]
pub extern "C" fn pforge_execute_python(
    handler_ptr: *const c_char,
    input_ptr: *const u8,
    input_len: usize,
) -> FfiResult {
    // Safety: Ownership transferred to C caller
    // Memory freed via pforge_free_result
}

Python side (ctypes):

import ctypes

lib = ctypes.CDLL("libpforge.so")
lib.pforge_execute_python.argtypes = [
    ctypes.c_char_p,
    ctypes.POINTER(ctypes.c_uint8),
    ctypes.c_size_t
]
lib.pforge_execute_python.restype = FfiResult

def call_handler(name, input_json):
    result = lib.pforge_execute_python(
        name.encode('utf-8'),
        input_json.encode('utf-8'),
        len(input_json)
    )
    return result

Design Principles:

  • Stable C ABI (no Rust name mangling)
  • Zero-copy: Pointers, not serialization
  • Error semantics preserved
  • Memory safety: Documented ownership transfer

Data Flow

Request Lifecycle

1. Client sends JSON-RPC request
   │
   ▼
2. Transport layer (stdio/SSE/WebSocket)
   │
   ▼
3. MCP protocol handler (pmcp)
   │
   ▼
4. Request router
   │
   ▼
5. Middleware chain (before)
   │
   ▼
6. Handler registry lookup (FxHash O(1))
   │
   ▼
7. Input deserialization + validation (serde)
   │
   ▼
8. Handler execution (async)
   │
   ▼
9. Output serialization (serde)
   │
   ▼
10. Middleware chain (after)
    │
    ▼
11. Response sent to client

Latency Breakdown (stdio transport):

  • Transport overhead: ~5μs
  • Protocol parsing: ~10μs
  • Routing: ~0.09μs (our optimization!)
  • Deserialization: ~20μs
  • Handler execution: Variable (user code)
  • Serialization: ~15μs
  • Total overhead: ~50μs

Memory Layout

HandlerRegistry (stack)
    │
    ├─► DashMap<String, Arc<Handler>> (heap)
    │       │
    │       └─► Handler instances (heap, Arc-shared)
    │
    └─► Middleware chain (Vec<Arc<Middleware>>) (heap)

Memory Usage:

  • Base runtime: ~200KB
  • Per tool: ~256 bytes (handler + registry entry)
  • Per request: ~4KB (stack + temp allocations)

Performance Architecture

Optimization Strategies

1. Lock-Free Concurrency

Problem: Lock contention in multi-threaded server

Solution: DashMap (lock-free HashMap)

// Before: Mutex<HashMap> (400ns per lookup with contention)
let registry: Mutex<HashMap<String, Handler>> = ...;

// After: DashMap (90ns per lookup, no contention)
let registry: DashMap<String, Arc<Handler>> = ...;

Result: 4x faster under concurrent load

2. Fast Hashing

Problem: SipHash (default) is cryptographically secure but slow

Solution: FxHash for non-cryptographic use

use rustc_hash::FxHashMap;  // 2x faster than SipHash

Trade-off: Not DOS-resistant (acceptable for internal use)

3. Zero-Copy Deserialization

Problem: Copying JSON strings allocates

Solution: Borrow from input buffer

// Before: Allocates new String
#[derive(Deserialize)]
struct Input {
    name: String,  // Allocates!
}

// After: Borrows from input
#[derive(Deserialize)]
struct Input<'a> {
    name: &'a str,  // Zero-copy!
}

Result: 30% faster deserialization

4. SIMD JSON Parsing

pmcp uses simd-json (16x faster than serde_json):

  • Vectorized parsing (AVX2/NEON)
  • Branch-free state machine
  • Parallel validation

Benchmark: 1GB/s vs 60MB/s (serde_json)

Future Optimizations

  1. Perfect Hashing (FKS algorithm)

    • O(1) worst-case (currently average-case)
    • ~2x faster for large registries
  2. JIT Compilation (Cranelift)

    • Compile YAML to machine code at runtime
    • Eliminate interpreter overhead
  3. io_uring (Linux)

    • Kernel-bypass I/O
    • ~2x throughput for stdio transport

Security Architecture

Threat Model

In Scope:

  • Malicious inputs (fuzzing, validation)
  • Resource exhaustion (timeouts, rate limits)
  • Dependency vulnerabilities (cargo-audit, cargo-deny)

Out of Scope:

  • Network-level attacks (DDoS, MITM)
  • Physical access to server
  • Social engineering

Security Measures

1. Input Validation

All inputs validated against JSON Schema:

let input: Input = serde_json::from_slice(bytes)
    .map_err(|e| Error::Validation(e.to_string()))?;

// Schema-based validation
validate_schema(&input, &schema)?;

2. Memory Safety

Rust guarantees:

  • No buffer overflows
  • No use-after-free
  • No data races (Send + Sync)

Unsafe code audit:

  • 6 total unsafe blocks (all FFI)
  • All documented with SAFETY comments
  • Valgrind verified (0 leaks)

3. Dependency Security

Tools:

  • cargo-audit: RustSec Advisory Database
  • cargo-deny: License + vulnerability enforcement
  • dependabot: Auto-update dependencies

Policy:

  • 0 critical vulnerabilities
  • Only permissive licenses (MIT, Apache-2.0, BSD)
  • No unmaintained dependencies

4. Sandboxing

CLI handlers run in restricted environment:

use std::process::Command;

Command::new(cmd)
    .args(args)
    .env_clear()  // Clear environment
    .current_dir("/tmp")  // Restricted directory
    .timeout(Duration::from_secs(30))  // Enforce timeout
    .spawn()?;

Extension Points

1. Custom Handlers

Implement the Handler trait:

struct MyCustomHandler;

#[async_trait]
impl Handler for MyCustomHandler {
    type Input = MyInput;
    type Output = MyOutput;
    type Error = MyError;

    async fn handle(&self, input: Self::Input) -> Result<Self::Output, Self::Error> {
        // Custom logic
    }
}

2. Custom Middleware

Implement the Middleware trait:

struct RateLimitMiddleware {
    limiter: Arc<RateLimiter>,
}

#[async_trait]
impl Middleware for RateLimitMiddleware {
    async fn before(&self, req: &Request) -> Result<()> {
        self.limiter.check(req.tool_name)?;
        Ok(())
    }

    async fn after(&self, _req: &Request, _res: &Response) -> Result<()> {
        Ok(())
    }
}

3. Custom Transports

Implement the Transport trait:

#[async_trait]
pub trait Transport {
    async fn send(&self, message: &[u8]) -> Result<()>;
    async fn receive(&self) -> Result<Vec<u8>>;
}

Design Decisions

Why Rust?

Requirement Rust Advantage
Performance Zero-cost abstractions, LLVM optimization
Safety Ownership system, no GC pauses
Concurrency Send/Sync, fearless concurrency
Reliability Strong typing, exhaustive pattern matching

Why YAML Configuration?

Pros:

  • Human-readable and writable
  • Industry standard (Kubernetes, Docker Compose)
  • Rich type system (strings, numbers, arrays, objects)

Cons:

  • Parsing overhead (mitigated: < 10ms)
  • No autocomplete (future: JSON Schema + LSP)

Alternatives Considered:

  • TOML: Less expressive for nested structures
  • JSON: Less human-friendly (no comments, trailing commas)
  • Rust code: Too much boilerplate

Why async/await?

Pros:

  • Non-blocking I/O (critical for I/O-bound tools)
  • Scales to 1000s of concurrent requests
  • Native Rust support (tokio)

Cons:

  • Async overhead (~1KB stack per task)
  • Complexity (colored functions)

Trade-off: Performance wins for I/O-bound workloads

Why pmcp SDK?

Alternatives:

  • Write custom MCP implementation
  • Use TypeScript SDK (via Node.js)

pmcp Advantages:

  • Rust-native (zero FFI overhead)
  • SIMD JSON parsing (16x faster)
  • TypedTool abstraction (type-safe)
  • Active maintenance

References

Internal Documentation

External Resources


Last Updated: 2025-10-03 pforge Version: 0.1.0