MCP TypeScript Implementation
Una implementación en TypeScript del Protocolo de Contexto de Modelo para el Marco de Inteligencia Personal.
Documentación
MCP-PIF
Un runtime de cálculo lambda nativo en JSON con evaluación metacircular, diseñado como un servidor MCP (Model Context Protocol). Permite que los modelos de lenguaje evolucionen herramientas dinámicamente mediante metaprogramación.
Inicio Rápido
# 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
Ejemplo Básico
// 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
Conceptos Fundamentales
El Problema
Para el cálculo puro, los modelos de lenguaje necesitan herramientas computacionales, pero los sistemas existentes ofrecen o APIs fijas o ejecución de código sin restricciones. En el contexto de la metaprogramación y la automodificación, ninguno es ideal. Este programa presenta una estructura intermedia para una computación segura, inspeccionable y evolucionable.
En el sentido ideal, MCP-PIF puede considerarse como una interfaz informática genérica y metamórfica donde el modelo de lenguaje actúa como función ejecutiva. Aquí, proporcionamos un vocabulario simple de primitivas de cálculo lambda para acceso a computación pura únicamente.
La Solución
MCP-PIF proporciona un cálculo lambda con tres primitivas de metaprogramación:
quote- Tratar el código como datos (prevenir la evaluación)eval- Ejecutar código citado dinámicamentecode_of- Inspeccionar el código fuente de cualquier herramienta
Esto crea un sistema metacircular donde las herramientas pueden analizar y transformar otras herramientas manteniendo una ejecución determinista y limitada por combustible. Es homoicónico, pero restringido.
Vale la pena señalar que quote y eval no son perfectamente simétricos:
-- eval cleans the environment:
let cleanEnv = M.filterWithKey (\k _ -> not $ k `elem` ["__tool_name", "__self"]) env
Esto evita que el código evaluado herede el contexto de herramienta incorrecto. Cuando eval ejecuta código citado:
- Las variables de usuario SE conservan (se mantiene el ámbito léxico)
- Las variables del sistema se limpian (
__tool_namey__selfse eliminan para evitar confusión de contexto de herramienta) - Los códigos de herramientas permanecen disponibles (para introspección con
code_of) - Se añade el contador
__eval_depth(profundidad máxima: 100, previene bucles de evaluación infinitos)
El Horizonte de Eventos
Debido a que este marco está construido en MCP, hace compromisos significativos en pureza. Específicamente, actualizar el código del servidor no es parte del bucle de metaprogramación. Esto significa que la lista de primitivas, las estrategias de análisis y los procesos de evolución y evaluación no son modificables durante el tiempo de ejecución.
Hay dos tipos de herramientas:
- Herramientas MCP: Por ejemplo, creación mediante
evolve(con efectos, muta el registro) - Herramientas evolucionadas: Ejecución de herramientas mediante
run(puras, funcionales)
Las herramientas evolucionadas pueden interactuar entre sí pero no pueden evolucionar nuevas herramientas sin acceso al registro de herramientas a nivel de protocolo. Esta implementación de "simulacro" previene la automodificación ilimitada aislando precisamente los invariantes que permiten la rica metaprogramación.
Referencia del Lenguaje
Primitivas Fundamentales
| Categoría | Primitiva | Sintaxis JSON | Descripción |
|---|---|---|---|
| Lambda | Variable | {"var": "x"} | Referencia a variable |
| Función | {"lam": "x", "body": ...} | Abstracción lambda | |
| Aplicación | {"app": {"func": ..., "arg": ...}} | Aplicación de función | |
| Aritmética | Suma | {"add": [a, b]} | Adición |
| Resta | {"sub": [a, b]} | Sustracción | |
| Multiplicación | {"mul": [a, b]} | Multiplicación | |
| División | {"div": [a, b]} | División (error en 0) | |
| Módulo | {"mod": [a, b]} | Módulo (error en 0) | |
| Comparación | Igual | {"eq": [a, b]} | Prueba de igualdad |
| Menor que | {"lt": [a, b]} | Menor que | |
| Menor o igual | {"lte": [a, b]} | Menor o igual | |
| Mayor que | {"gt": [a, b]} | Mayor que | |
| Mayor o igual | {"gte": [a, b]} | Mayor o igual | |
| Lógica | Y | {"and": [a, b]} | Y lógico (cortocircuito) |
| O | {"or": [a, b]} | O lógico (cortocircuito) | |
| No | {"not": a} | NO lógico | |
| Control | Si | {"if": {"cond": c, "then": t, "else": e}} | Condicional |
| Continuar | {"continue": {"input": x}} | Paso recursivo | |
| Listas | Nil | {"nil": true} | Lista vacía |
| Cons | {"cons": {"head": ..., "tail": ...}} | Construcción de lista | |
| Fold | {"fold": [func, init, list]} | Reductor universal (ver GUIDE.md para detalles de parámetros de par) | |
| Pares | Par | {"pair": [a, b]} | Construcción de par |
| Primero | {"fst": pair} | Obtener primer elemento | |
| Segundo | {"snd": pair} | Obtener segundo elemento | |
| Meta | Cita | {"quote": term} | Prevenir evaluación |
| Eval | {"eval": quoted} | Ejecutar código citado | |
| Código de | {"code_of": "tool_name"} | Obtener fuente de herramienta | |
| Self | {"self": true} | Referencia de cierre actual (solo herramientas) |
Literales
- Números:
42,-17,3.14→ Enteros (flotantes redondeados) - Booleanos:
true,false - Cadenas:
"hello","world" - Arreglos:
[1, 2, 3]→ Convertidos a listas cons - Nulo:
null→ Valor unitario
Normalización de Entrada
La herramienta run normaliza automáticamente las entradas para hacer el uso de CLI más ergonómico:
- Números como cadena:
"42"→42 - Booleanos como cadena:
"true"→true,"false"→false - Cadenas JSON:
"{\"x\": 5}"→ Analizado como objeto JSON - Listas: Los arreglos JSON se convierten en listas cons
Esto permite formatos de entrada flexibles mientras se mantiene la seguridad de tipos durante la evaluación.
Referencia Rápida
Para patrones y ejemplos detallados, consulte la Guía de Usuario.
Conceptos Clave para Recordar
- Referencias a Herramientas: Las herramientas pueden referenciarse como cadenas (
"square"), lambdas en línea, o mediantecode_of - Ámbito de Eval: Las variables de usuario se conservan, las variables del sistema se limpian, los códigos de herramientas permanecen disponibles
- Firma de Fold: La función fold recibe un solo par
(accumulator, item), no dos parámetros - Continuaciones: Solo funcionan con herramientas registradas, cada paso es un viaje de ida y vuelta MCP
- Referencia Self:
{"self": true}solo funciona dentro de herramientas registradas (creadas medianteevolve), no en lambdas en línea pasadas arun - Horizonte de Eventos: Las herramientas no pueden crear otras herramientas desde dentro del cálculo lambda
Referencia Rápida de Recursión
¿Necesita recursión? Use este árbol de decisión:
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
Ejemplos:
- Factorial →
continuecon patrón de acumulador - Suma →
continuecon patrón de acumulador - Fibonacci (n pequeño) →
self - Recursión mutua Par/Impar →
eval+code_of
Patrones y Ejemplos
Factorial Recursivo
Las herramientas pueden usar recursión basada en continuaciones para ejecución paso a paso. Dado que continue pausa la evaluación y devuelve el control a la capa MCP, debe usar un patrón de acumulador donde el cálculo ocurre durante la recursión, no después:
{
"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"}}]}
]
}
}
}
}
}
}
}
}
Uso:
{
"name": "run",
"arguments": {
"code": "factorial",
"input": {"pair": [5, 1]}
}
}
El programa devolverá una respuesta estructurada:
{
"type": "continuation",
"message": "Recursive step needed. Call run again with:",
"tool": "factorial_acc",
"next_input": {
"pair": [4, 5]
},
"step": 1
}
Esto se renderiza para el cliente como una representación de Haskell:
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")])
Importante: La herramienta toma un par [n, accumulator] como entrada. Comience con [5, 1] para calcular 5!. Cada paso de continuación multiplica el acumulador por la n actual, luego decrementa n.
¿Por qué este patrón?
La primitiva continue no devuelve un valor con el que pueda calcular—devuelve un marcador de continuación. Todo el cálculo debe ocurrir antes de llamar a continue, almacenado en el acumulador. El patrón es:
- Entrada:
[n, acc]dondeacccontiene el resultado parcial - Caso base: Cuando
n ≤ 1, devuelva el acumulador - Caso recursivo: Calcule el nuevo acumulador (
n * acc), continúe con[n-1, new_acc]
Alternativa: Recursión directa con self (limitada por combustible):
{
"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]}}}
]
}
}
}
}
}
}
Esto funciona para entradas pequeñas pero alcanzará el límite de combustible (10,000 pasos) alrededor de n=20.
Map de Orden Superior mediante Fold
{
"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"}
]
}
}
}
}
Metaprogramación: Análisis de Código
{
"name": "count_operations",
"description": "Counts arithmetic operations in a tool",
"code": {
"lam": "tool_name",
"body": {
"eval": {
"quote": {
"analyze": [{"code_of": {"var": "tool_name"}}]
}
}
}
}
}
La primitiva code_of devuelve el código fuente de una herramienta como datos citados, permitiendo análisis y transformación de programas.
Arquitectura
Pipeline
JSON Input → Parser → Term → Evaluator → RuntimeValue → Encoder → JSON Output
validation syntax execution values serialization
Módulos
| Módulo | Propósito |
|---|---|
| Main.hs | Punto de entrada, bucle JSON-RPC |
| Server.hs | Protocolo MCP, enrutamiento de solicitudes |
| Core/Parser.hs | Validación JSON → Término |
| Core/Evaluator.hs | Ejecución de términos con combustible |
| Core/Encoder.hs | RuntimeValue → JSON |
| Core/Types.hs | Definiciones de tipos fundamentales |
| Core/Syntax.hs | ADT de términos |
| Tools/Registry.hs | Almacenamiento de herramientas |
Garantías de Seguridad
- Terminación basada en combustible: Cada evaluación tiene pasos finitos (predeterminado: 10,000)
- Evaluación pura: Sin E/S ni efectos en el cálculo lambda
- Ejecución validada: Solo los términos estructuralmente válidos pueden ejecutarse
- Registro inmutable: Las herramientas no pueden modificarse entre sí durante la ejecución
Integración MCP
MCP-PIF implementa el Model Context Protocol para descubrimiento y ejecución de herramientas:
Herramientas del Sistema
evolve- Crear nuevas herramientas (almacena en el registro)run- Ejecutar herramientas o expresiones lambda en línealist- Mostrar todas las herramientas registradashelp- Mostrar documentación para primitivas y herramientas del sistema
Flujo del Protocolo
- El cliente envía una solicitud JSON-RPC a stdin
- El servidor analiza y enruta al manejador apropiado
- Para ejecución de herramientas:
- Analizar JSON de entrada → Término
- Inyectar códigos de herramientas en el entorno
- Evaluar con límite de combustible
- Codificar resultado → JSON
- La respuesta se envía a stdout
Conexión de un Cliente MCP
# 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
Direcciones Futuras
El diseño actual de MCP-PIF mantiene un límite claro entre computación pura y operaciones con efectos. Se han considerado varias extensiones que expandirían estos límites de maneras interesantes:
Funciones Analíticas
El sistema actual es principalmente sintético—usando primitivas para componer nuevas funciones. Una extensión natural serían capacidades analíticas:
- Validación: Análisis estático de la estructura de términos sin evaluación
- Normalización: Reducción de términos a formas canónicas
- Verificación de Equivalencia: Probar que dos términos calculan la misma función
- Inferencia de Tipos: Derivar tipos para términos lambda
- Análisis de Complejidad: Estimar requisitos de combustible
- Análisis de Capacidades: Descomponer herramientas por sus primitivas
Estas funciones analíticas operarían sobre código-como-datos (términos citados) y podrían habilitar poderosos patrones de metaprogramación. Sin embargo, requieren diseño cuidadoso para mantener la simplicidad del cálculo central mientras proporcionan garantías significativas.
Primitivas con Efectos
Otra dirección implica la introducción controlada de efectos:
Interacción con el Sistema:
- Primitivas de E/S de archivos (
readFile,writeFile) - Control de procesos (
exec,env) - Operaciones de red (
fetch,serve)
Desafíos de Diseño:
- ¿Cómo mantener los límites de pureza?
- ¿Deberían los efectos ser monádicos, algebraicos o basados en continuaciones?
- ¿Cómo manejar errores y gestión de recursos?
- ¿Qué modelo de seguridad para control de capacidades?
Un enfoque podría ser seguridad basada en capacidades: las herramientas podrían declarar capacidades requeridas (acceso a archivos, red, etc.) en el momento de creación, con la capa MCP aplicando control de acceso. Esto es consistente con la idea de que las herramientas evolucionadas deberían portar prueba de su propia validez.
Autoalojamiento y Arranque
El objetivo metacircular definitivo: implementar el evaluador de MCP-PIF en MCP-PIF mismo. Esto requeriría:
- Primitivas para manipulación JSON
- Constructos de coincidencia de patrones
- Representación eficiente de entornos
- Gestión de combustible a nivel meta
Un PIF autoalojado podría permitir la evolución en tiempo de ejecución de la propia estrategia de evaluación—un sistema verdaderamente reflexivo.
Extensiones del Protocolo
El límite MCP podría soportar operaciones adicionales a nivel de protocolo:
- Versionado de herramientas: Rastrear la evolución de herramientas a lo largo del tiempo
- Composición de herramientas: Combinadores a nivel de protocolo para fusión de herramientas
- Registro distribuido: Compartir herramientas entre servidores MCP
- Certificados de prueba: Adjuntar pruebas de corrección a las herramientas Estas extensiones mantienen el principio del horizonte de eventos mientras enriquecen las capacidades de la capa de protocolo.
El principio de diseño clave para cualquier extensión: preservar la simplicidad y previsibilidad que hacen de PIF un sustrato confiable para la computación de modelos de lenguaje.
Licencia
MIT