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ámicamente
  • code_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_name y __self se 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íaPrimitivaSintaxis JSONDescripción
LambdaVariable{"var": "x"}Referencia a variable
Función{"lam": "x", "body": ...}Abstracción lambda
Aplicación{"app": {"func": ..., "arg": ...}}Aplicación de función
AritméticaSuma{"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ónIgual{"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ógicaY{"and": [a, b]}Y lógico (cortocircuito)
O{"or": [a, b]}O lógico (cortocircuito)
No{"not": a}NO lógico
ControlSi{"if": {"cond": c, "then": t, "else": e}}Condicional
Continuar{"continue": {"input": x}}Paso recursivo
ListasNil{"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)
ParesPar{"pair": [a, b]}Construcción de par
Primero{"fst": pair}Obtener primer elemento
Segundo{"snd": pair}Obtener segundo elemento
MetaCita{"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

  1. Referencias a Herramientas: Las herramientas pueden referenciarse como cadenas ("square"), lambdas en línea, o mediante code_of
  2. Ámbito de Eval: Las variables de usuario se conservan, las variables del sistema se limpian, los códigos de herramientas permanecen disponibles
  3. Firma de Fold: La función fold recibe un solo par (accumulator, item), no dos parámetros
  4. Continuaciones: Solo funcionan con herramientas registradas, cada paso es un viaje de ida y vuelta MCP
  5. Referencia Self: {"self": true} solo funciona dentro de herramientas registradas (creadas mediante evolve), no en lambdas en línea pasadas a run
  6. 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 → continue con patrón de acumulador
  • Suma → continue con 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] donde acc contiene 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óduloPropósito
Main.hsPunto de entrada, bucle JSON-RPC
Server.hsProtocolo MCP, enrutamiento de solicitudes
Core/Parser.hsValidación JSON → Término
Core/Evaluator.hsEjecución de términos con combustible
Core/Encoder.hsRuntimeValue → JSON
Core/Types.hsDefiniciones de tipos fundamentales
Core/Syntax.hsADT de términos
Tools/Registry.hsAlmacenamiento 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ínea
  • list - Mostrar todas las herramientas registradas
  • help - Mostrar documentación para primitivas y herramientas del sistema

Flujo del Protocolo

  1. El cliente envía una solicitud JSON-RPC a stdin
  2. El servidor analiza y enruta al manejador apropiado
  3. 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
  4. 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