Version: 0.1.0 Last Updated: 2025-10-03 Status: Production-Ready
- Overview
- High-Level Architecture
- Component Design
- Data Flow
- Performance Architecture
- Security Architecture
- Extension Points
- Design Decisions
pforge follows these core architectural principles:
- Zero-Cost Abstractions: Declarative configuration compiles to optimal Rust code
- Type Safety: Compile-time guarantees wherever possible
- Performance First: Sub-microsecond dispatch, > 100K req/s throughput
- Production Ready: Built-in observability, error handling, and resilience
| 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 |
┌─────────────────────────────────────────────────────────┐
│ 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) │
└─────────────────────────────────────────────────────────┘
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
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
Responsibility: Handler registry, dispatch, middleware
#[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
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:
- FxHash instead of SipHash (2x faster for small keys)
- DashMap for lock-free concurrent access
- Arc for zero-copy handler sharing
- 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
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 loggingMetricsMiddleware: Prometheus metricsRecoveryMiddleware: Auto-retry on transient failuresTimeoutMiddleware: Enforce execution timeoutsValidationMiddleware: Parameter validation
Execution Order:
Request → Middleware::before() → Handler::handle() → Middleware::after() → Response
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
Responsibility: FFI for Python, Go, Node.js handlers
┌──────────────┐
│ Rust Runtime │
└──────┬───────┘
│ FFI (stable C ABI)
│
├──► Python Bridge (ctypes)
├──► Go Bridge (cgo)
└──► Node.js Bridge (napi-rs)
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 resultDesign Principles:
- Stable C ABI (no Rust name mangling)
- Zero-copy: Pointers, not serialization
- Error semantics preserved
- Memory safety: Documented ownership transfer
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
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)
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
Problem: SipHash (default) is cryptographically secure but slow
Solution: FxHash for non-cryptographic use
use rustc_hash::FxHashMap; // 2x faster than SipHashTrade-off: Not DOS-resistant (acceptable for internal use)
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
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)
-
Perfect Hashing (FKS algorithm)
- O(1) worst-case (currently average-case)
- ~2x faster for large registries
-
JIT Compilation (Cranelift)
- Compile YAML to machine code at runtime
- Eliminate interpreter overhead
-
io_uring (Linux)
- Kernel-bypass I/O
- ~2x throughput for stdio transport
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
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)?;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)
Tools:
cargo-audit: RustSec Advisory Databasecargo-deny: License + vulnerability enforcementdependabot: Auto-update dependencies
Policy:
- 0 critical vulnerabilities
- Only permissive licenses (MIT, Apache-2.0, BSD)
- No unmaintained dependencies
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()?;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
}
}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(())
}
}Implement the Transport trait:
#[async_trait]
pub trait Transport {
async fn send(&self, message: &[u8]) -> Result<()>;
async fn receive(&self) -> Result<Vec<u8>>;
}| 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 |
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
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
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
- SECURITY.md - Security policies
- MEMORY_SAFETY.md - Memory safety guarantees
- PERFORMANCE.md - Performance benchmarks
- CI_CD.md - CI/CD pipeline documentation
Last Updated: 2025-10-03 pforge Version: 0.1.0