MCP TypeScript Implementation
About
A TypeScript implementation of the Model Context Protocol for the Personal Intelligence Framework.
Details
- Author
- hungryrobot1
- Categories
- Developer Tools
Jump to
Setup
Install MCP TypeScript Implementation in your MCP client (Claude Desktop, Cursor, Windsurf, and others).
Repository: https://github.com/hungryrobot1/MCP-PIF
Follow the installation instructions in the repository README, then restart your MCP client.
A TypeScript implementation of the Model Context Protocol for the Personal Intelligence Framework.
A JSON-native lambda calculus runtime with metacircular evaluation, designed as an MCP (Model Context Protocol) server. Enables language models to evolve tools dynamically through metaprogramming.
# Build the project cabal build # Enable debug mode for detailed evaluation tracing MCP_DEBUG=1 cabal run mcp-pif # Debug output (to stderr) shows: # - Each evaluation step # - Environment keys at each step # - Closure creation and application # - Tool code lookups
// Create a tool { "jsonrpc": "2.0", "method": "tools/call", "params": { "name": "evolve", "arguments": { "name": "square", "description": "Squares a number", "code": {"lam": "x", "body": {"mul": [{"var": "x"}, {"var": "x"}]}} } } } // Use the tool { "jsonrpc": "2.0", "method": "tools/call", "params": { "name": "run", "arguments": { "tool": "square", "input": 7 } } } // Returns: 49 // Get help { "jsonrpc": "2.0", "method": "tools/call", "params": { "name": "help", "arguments": {"category": "lists"} } } // Returns: Documentation for list primitives
For pure computation language models need computational tools, but existing systems either provide fixed APIs or unrestricted code execution. In the context of metaprogramming and self-modification, neither is ideal. This program presents a middle ground structure for safe, inspectable, evolvable computation.
In the ideal sense, MCP-PIF can be thought of as a generic, metamorphic computer interface where the language model drops-in as an executive function. Here, we provide a simple vocabulary of lambda calculus primitives for access to pure computing only.
MCP-PIF provides a lambda calculus with three metaprogramming primitives:
- quote- Treat code as data (prevent evaluation)
- eval- Execute quoted code dynamically
- code_of- Introspect any tool's source code
This creates ametacircularsystem where tools can analyze and transform other tools while maintaining deterministic, fuel-bounded execution. It is homoiconic, but constrained.
It's worth notingquoteandevalare not perfectly symmetric:
-- eval cleans the environment: let cleanEnv = M.filterWithKey (\k _ -> not $ k elem ["__tool_name", "__self"]) env
This prevents eval'd code from inheriting the wrong tool context. Whenevalexecutes quoted code:
- User variables ARE preserved(lexical scoping maintained)
- System variables are cleaned(__tool_nameand__selfremoved to prevent tool context confusion)
- Tool codes remain available(forcode_ofintrospection)
- __eval_depthcounter is added(max depth: 100, prevents infinite eval loops)
Because this framework is built in MCP it makes significant compromises in purity. Namely, updating the server code is not part of the metaprogramming loop. This means that the list of primitives, parsing strategies, and the evolution and evaluation processes themselves are not modifiable during runtime.
- MCP tools: For example creation viaevolve(effectful, mutates registry)
- Evolved tools: Tool execution viarun(pure, functional)
Evolved tools can interact with each other but cannot themselves evolve new tools without the access to the protocol-level tool registry. This "simulacrum" implementation prevents unbounded self-modification by isolating the very invariants which enable the rich metaprogramming.
- Numbers:42,-17,3.14→ Integers (floats rounded)
- Booleans:true,false
- Strings:"hello","world"
- Arrays:[1, 2, 3]→ Converted to cons lists
- Null:null→ Unit value
Theruntool automatically normalizes inputs to make CLI usage more ergonomic:
- String numbers:"42"→42
- String booleans:"true"→true,"false"→false
- JSON strings:"{\"x\": 5}"→ Parsed as JSON object
- Lists: JSON arrays are converted to cons lists
This allows flexible input formats while maintaining type safety during evaluation.
For detailed patterns and examples, see theUser Guide.
- Tool References: Tools can be referenced as strings ("square"), inline lambdas, or viacode_of
- Eval Scoping: User variables are preserved, system variables are cleaned, tool codes remain available
- Fold Signature: The fold function receives a single pair(accumulator, item), not two parameters
- Continuations: Only work with registered tools, each step is an MCP round-trip
- Self Reference:{"self": true}only works inside registered tools(created viaevolve),not in inline lambdaspassed torun
- Event Horizon: Tools cannot create other tools from within lambda calculus
Need to recurse?Use this decision tree:
Can you structure it with an accumulator? ├─ Yes → Use continue (works for any depth) │ Pattern: take pair [state, accumulator] │ Base case: return accumulator │ Recursive: compute new accumulator, continue with [new_state, new_acc] │ └─ No, need result immediately? ├─ Small input (n < 20) → Use self └─ Large input → Redesign with accumulator or use fold
- Factorial →continuewith accumulator pattern
- Sum →continuewith accumulator pattern
- Fibonacci (small n) →self
- Even/Odd mutual recursion →eval+code_of
Tools can use continuation-based recursion for step-by-step execution. Sincecontinuepauses evaluation and returns control to the MCP layer, you must use an accumulator pattern where computation happens during recursion, not after:
{ "name": "evolve", "arguments": { "name": "factorial", "description": "Computes factorial using continuation with accumulator", "code": { "lam": "n_acc", "body": { "if": { "cond": {"lte": [{"fst": {"var": "n_acc"}}, 1]}, "then": {"snd": {"var": "n_acc"}}, "else": { "continue": { "input": { "pair": [ {"sub": [{"fst": {"var": "n_acc"}}, 1]}, {"mul": [{"fst": {"var": "n_acc"}}, {"snd": {"var": "n_acc"}}]} ] } } } } } } } }
{ "name": "run", "arguments": { "code": "factorial", "input": {"pair": [5, 1]} } }
The program will return a structured response:
{ "type": "continuation", "message": "Recursive step needed. Call run again with:", "tool": "factorial_acc", "next_input": { "pair": [4, 5] }, "step": 1 }
This renders for the client as a Haskell representation:
Object (fromList [("message",String "Recursive step needed. Call run again with:"),("next_input",Object (fromList [("pair",Array [Number 4.0,Number 5.0])])),("step",Number 1.0),("tool",String "factorial_acc"),("type",String "continuation")])
Important:The tool takes a pair[n, accumulator]as input. Start with[5, 1]to compute 5!. Each continuation step multiplies the accumulator by the current n, then decrements n.
Thecontinueprimitive doesn't return a value you can compute with—it returns a continuation marker. All computation must happen before callingcontinue, stored in the accumulator. The pattern is:
- Input:[n, acc]whereaccholds the partial result
- Base case:Whenn ≤ 1, return the accumulator
- Recursive case:Compute new accumulator (n * acc), continue with[n-1, new_acc]
Alternative: Direct recursion withself(fuel-limited):
{ "name": "evolve", "arguments": { "name": "factorial_self", "description": "Simple factorial using self (small n only)", "code": { "lam": "n", "body": { "if": { "cond": {"lte": [{"var": "n"}, 1]}, "then": 1, "else": { "mul": [ {"var": "n"}, {"app": {"func": {"self": true}, "arg": {"sub": [{"var": "n"}, 1]}}} ] } } } } } }
This works for small inputs but will hit the fuel limit (10,000 steps) around n=20.
{ "name": "map", "description": "Maps a function over a list", "code": { "lam": "f", "body": { "lam": "list", "body": { "fold": [ {"lam": "acc_item", "body": { "cons": { "head": {"app": {"func": {"var": "f"}, "arg": {"snd": {"var": "acc_item"}}}}, "tail": {"fst": {"var": "acc_item"}} } }}, {"nil": true}, {"var": "list"} ] } } } }
{ "name": "count_operations", "description": "Counts arithmetic operations in a tool", "code": { "lam": "tool_name", "body": { "eval": { "quote": { "analyze": [{"code_of": {"var": "tool_name"}}] } } } } }
Thecode_ofprimitive returns a tool's source as quoted data, enabling program analysis and transformation.
JSON Input → Parser → Term → Evaluator → RuntimeValue → Encoder → JSON Output validation syntax execution values serialization
- Fuel-based termination: Every evaluation has finite steps (default: 10,000)
- Pure evaluation: No I/O or effects in lambda calculus
- Validated execution: Only structurally valid terms can run
- Immutable registry: Tools can't modify each other during execution
MCP-PIF implements the Model Context Protocol for tool discovery and execution:
- evolve- Create new tools (stores in registry)
- run- Execute tools or inline lambda expressions
- list- Show all registered tools
- help- Display documentation for primitives and system tools
- Client sends JSON-RPC request to stdin
- Server parses and routes to appropriate handler
- For tool execution:
- Parse input JSON → Term
- Inject tool codes into environment
- Evaluate with fuel limit
- Encode result → JSON
# Example using Python MCP SDK import mcp async with mcp.Client() as client: await client.connect(stdio_transport("cabal run mcp-pif")) # Create a tool await client.call_tool("evolve", { "name": "double", "description": "Doubles a number", "code": {"mul": [{"var": "x"}, 2]} }) # Use it result = await client.call_tool("run", { "tool": "double", "input": 21 }) print(result) # 42
MCP-PIF's current design maintains a clear boundary between pure computation and effectful operations. Several extensions have been considered that would expand these boundaries in interesting ways:
The current system is primarilysynthetic- using primitives to compose new functions. A natural extension would beanalyticcapabilities:
- Validation: Static analysis of term structure without evaluation
- Normalization: Reducing terms to canonical forms
- Equivalence Checking: Proving two terms compute the same function
- Type Inference: Deriving types for lambda terms
- Complexity Analysis: Estimating fuel requirement
- Capabilities Anlysis: Deomposing tools by their primitives
These analytic functions would operate on code-as-data (quoted terms) and could enable powerful metaprogramming patterns. However, they require careful design to maintain the simplicity of the core calculus while providing meaningful guarantees.
Another direction involves controlled introduction of effects:
- File I/O primitives (readFile,writeFile)
- Process control (exec,env)
- Network operations (fetch,serve)
- How to maintain purity boundaries?
- Should effects be monadic, algebraic, or continuation-based?
- How to handle errors and resource management?
- What security model for capability control?
One approach might becapability-based security: tools could declare required capabilities (file access, network, etc.) at creation time, with the MCP layer enforcing access control. This is consistent with the idea that evolved tools should bear proof of their own validity.
The ultimate metacircular goal: implementing MCP-PIF's evaluator in MCP-PIF itself. This would require:
- Primitives for JSON manipulation
- Pattern matching constructs
- Efficient representation of environments
- Fuel management at the meta-level
A self-hosted PIF could enable runtime evolution of the evaluation strategy itself - a truly reflective system.
The MCP boundary could support additional protocol-level operations:
- Tool versioning: Track tool evolution over time
- Tool composition: Protocol-level combinators for tool fusion
- Distributed registry: Share tools across MCP servers
- Proof certificates: Attach correctness proofs to tools
These extensions maintain the event horizon principle while enriching the protocol layer's capabilities.
The key design principle for any extension: preserve the simplicity and predictability that makes PIF a reliable substrate for language model computation.
This is a web browser that enables your coding agent, such as Claude Code, to visit websites on your behalf and assist you in identifying bugs or creating UI test cases.
Create crafted UI components inspired by the best 21st.dev design engineers.
Bring agent evaluations, observability, and synthetic test set generation directly into your IDE for free with Galileo's new MCP server
An MCP server to help AI assistants to answer questions and generate AccelByte Extend SDK code more effectively .
MCP server for AI Diagram Maker — generate beautiful software engineering diagrams directly inside Cursor, Claude Desktop, Claude Code, or any MCP-compatible AI agent
ALAPI MCP Tools,Call hundreds of API interfaces via MCP
AI-powered SVG animation generator that transforms static files into animated SVG components using the Allyson platform
MCP server that gives AI assistants on-demand access to 1,500+ amCharts docs, ~300 code examples, and 1000+ class API references.
APIMatic MCP Server is used to validate OpenAPI specifications using APIMatic. The server processes OpenAPI files and returns validation summaries by leveraging APIMatic’s API.
One shared context layer for AI agents and humans — live API specs, DB schemas, and versioned contracts across repos so every agent and teammate works from the same source of truth.
Build and deploy full-stack Next.js apps with 98 tools for React, AWS, and MongoDB
Sign in to leave a review
Use Google, GitHub, or an email account so ratings stay tied to real people.
No reviews posted yet.





