返回市场
MCP-物理接口框架

MCP-物理接口框架

作者:hungryrobot155 星标更新:2025-10-20

项目介绍

MCP-PIF

一个基于JSON的lambda演算运行时系统,具有元循环评估功能,设计为MCP(模型上下文协议)服务器。它使语言模型能够通过元编程动态演化工具。

快速开始

# 构建项目
cabal build

# 启用调试模式以进行详细的评估跟踪
MCP_DEBUG=1 cabal run mcp-pif
# 调试输出(到stderr)显示:
# - 每个评估步骤
# - 每步的环境键
# - 闭包创建和应用
# - 工具代码查找

基本示例

// 创建一个工具
{
  "jsonrpc": "2.0",
  "method": "tools/call",
  "params": {
    "name": "evolve",
    "arguments": {
      "name": "square",
      "description": "计算一个数的平方",
      "code": {"lam": "x", "body": {"mul": [{"var": "x"}, {"var": "x"}]}}
    }
  }
}

// 使用工具
{
  "jsonrpc": "2.0",
  "method": "tools/call",
  "params": {
    "name": "run",
    "arguments": {
      "tool": "square",
      "input": 7
    }
  }
}
// 返回:49

// 获取帮助
{
  "jsonrpc": "2.0",
  "method": "tools/call",
  "params": {
    "name": "help",
    "arguments": {"category": "lists"}
  }
}
// 返回:列表原始操作的文档

核心概念

问题

对于纯计算的语言模型需要计算工具,但现有的系统要么提供固定的API,要么允许无限制的代码执行。在元编程和自我修改的背景下,这两种都不是理想的解决方案。这个程序提供了一个中间结构,用于安全、可检查、可演化的计算。

从理想的角度来看,MCP-PIF可以被视为一种通用的、变形的计算机接口,其中语言模型作为执行功能插入。这里我们提供了一组简单的lambda演算原始操作,仅用于访问纯计算。

解决方案

MCP-PIF 提供了带有三个元编程原始操作的lambda演算:

  • quote - 将代码视为数据(防止评估)
  • eval - 动态执行被引用的代码
  • code_of - 查看任何工具的源代码

这创建了一个元循环系统,其中工具可以分析和转换其他工具,同时保持确定性和燃料有限的执行。它是同构的,但受到约束。

值得注意的是,quoteeval 并非完全对称:

-- eval 清理环境:
let cleanEnv = M.filterWithKey (\k _ -> not $ k `elem` ["__tool_name", "__self"]) env

这防止了被 eval 执行的代码继承错误的工具上下文。当 eval 执行被引用的代码时:

  • 用户变量是保留的(保持词法作用域)
  • 系统变量被清理(移除 __tool_name__self 以防止工具上下文混淆)
  • 工具代码仍然可用(用于 code_of 查看)
  • 添加 __eval_depth 计数器(最大深度:1100,防止无限 eval 循环)

事件视界

因为这个框架是在 MCP 上构建的,所以在纯粹性方面做出了重大妥协。特别是,更新服务器代码不属于元编程循环的一部分。这意味着原始操作列表、解析策略以及进化和评估过程本身在运行时不可修改。

有两种类型的工具:

  • MCP 工具:例如通过 evolve 创建(有副作用,修改注册表)
  • 进化工具:通过 run 执行工具(纯函数式)

进化工具可以相互交互,但不能自己创建新的工具,除非它们有权访问协议级别的工具注册表。这种“拟像”实现通过隔离使丰富元编程成为可能的不变量来防止无界的自我修改。

语言参考

核心原始操作

类别原始操作JSON 语法描述
Lambda变量{"var": "x"}变量引用
函数{"lam": "x", "body": ...}Lambda 抽象
应用{"app": {"func": ..., "arg": ...}}函数应用
算术{"add": [a, b]}加法
{"sub": [a, b]}减法
{"mul": [a, b]}乘法
{"div": [a, b]}除法(除以零会出错)
取模{"mod": [a, b]}取模(除以零会出错)
比较等于{"eq": [a, b]}等值测试
小于{"lt": [a, b]}小于
小于等于{"lte": [a, b]}小于等于
大于{"gt": [a, b]}大于
大于等于{"gte": [a, b]}大于等于
逻辑{"and": [a, b]}逻辑与(短路)
{"or": [a, b]}逻辑或(短路)
{"not": a}逻辑非
控制如果{"if": {"cond": c, "then": t, "else": e}}条件
继续{"continue": {"input": x}}递归步骤
列表空列表{"nil": true}空列表
构造{"cons": {"head": ..., "tail": ...}}列表构造
折叠{"fold": [func, init, list]}通用归约器(参见 GUIDE.md 中的配对参数详情)
配对配对{"pair": [a, b]}配对构造
第一元素{"fst": pair}获取第一个元素
第二元素{"snd": pair}获取第二个元素
引用{"quote": term}防止评估
执行{"eval": quoted}执行被引用的代码
源码{"code_of": "tool_name"}获取工具源码
自身{"self": true}当前闭包引用(仅限工具)

文字

  • 数字42, -17, 3.14 → 整数(浮点数四舍五入)
  • 布尔值true, false
  • 字符串"hello", "world"
  • 数组[1, 2, 3] → 转换为 cons 列表
  • 空值null → 单位值

输入规范化

run 工具自动规范化输入,以便更方便地使用 CLI:

  • 字符串数字"42"42
  • 字符串布尔值"true"true, "false"false
  • JSON 字符串"{\"x\": 5}" → 解析为 JSON 对象
  • 列表:JSON 数组转换为 cons 列表

这允许灵活的输入格式,同时在评估期间保持类型安全性。

快速参考

有关详细模式和示例,请参阅**用户指南**。

需要记住的关键概念

  1. 工具引用:工具可以作为字符串("square")、内联lambda或通过 code_of 引用。
  2. Eval 作用域:用户变量保留,系统变量清理,工具代码仍然可用。
  3. 折叠签名:折叠函数接收单个配对(累加器,项),而不是两个参数。
  4. 延续:仅适用于已注册的工具,每一步都是 MCP 的往返。
  5. 自身引用{"self": true} 仅在通过 evolve 创建的已注册工具内部工作不在传递给 run 的内联lambda中工作
  6. 事件视界:工具无法从lambda演算内部创建其他工具。

递归快速参考

需要递归吗? 使用此决策树:

你可以用累加器来构建吗?
├─ 是 → 使用 `continue`(适用于任何深度)
│         模式:取配对 [状态, 累加器]
│         基础情况:返回累加器
│         递归:计算新累加器,继续 [新状态, 新累加器]
│
└─ 不,需要立即结果?
   ├─ 小输入(n < 20)→ 使用 `self`
   └─ 大输入 → 重新设计使用累加器或使用折叠

示例:

  • 阶乘 → 使用 continue 和累加器模式
  • 求和 → 使用 continue 和累加器模式
  • 斐波那契(小 n)→ 使用 self
  • 偶数/奇数互递归 → 使用 eval + code_of

模式与示例

递归阶乘

工具可以使用基于延续的递归进行逐步执行。由于 continue 暂停评估并将控制权返回给 MCP 层,因此必须使用累加器模式,在递归过程中进行计算,而不是之后:

{
  "name": "evolve",
  "arguments": {
    "name": "factorial",
    "description": "使用延续和累加器计算阶乘",
    "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]}
  }
}

程序将返回一个结构化响应:

{
  "type": "continuation",
  "message": "需要递归步骤。再次调用 run:",
  "tool": "factorial_acc",
  "next_input": {
    "pair": [4, 5]
  },
  "step": 1
}

这呈现给客户端为 Haskell 表示:

Object (fromList [("message",String "需要递归步骤。再次调用 run:"),("next_input",Object (fromList [("pair",Array [Number 4.0,Number 5.0])])),("step",Number 1.0),("tool",String "factorial_acc"),("type",String "continuation")])

重要: 工具接受一个配对 [n, 累加器] 作为输入。从 [5, 1] 开始计算 5!。每次延续步骤将累加器乘以当前 n,然后递减 n。

为什么使用这种模式?

continue 原始操作不会返回一个可以直接计算的值——它返回一个延续标记。所有计算必须在调用 continue 之前完成,并存储在累加器中。模式是:

  • 输入: [n, acc] 其中 acc 持有部分结果
  • 基础情况:n ≤ 1 时,返回累加器
  • 递归情况: 计算新的累加器(n * acc),继续 [n-1, 新累加器]

替代方案:直接递归使用 self(燃料有限):

{
  "name": "evolve",
  "arguments": {
    "name": "factorial_self",
    "description": "简单阶乘使用 self(仅适用于小 n)",
    "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]}}}
            ]
          }
        }
      }
    }
  }
}

这适用于小输入,但在 n≈20 时会达到燃料限制(10,000 步)。

高阶映射通过折叠

{
  "name": "map",
  "description": "将函数映射到列表上",
  "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": "统计工具中的算术操作次数",
  "code": {
    "lam": "tool_name",
    "body": {
      "eval": {
        "quote": {
          "analyze": [{"code_of": {"var": "tool_name"}}]
        }
      }
    }
  }
}

code_of 原始操作返回工具的源码作为引用数据,从而支持程序分析和转换。

架构

管道

JSON 输入 → 解析器 → 项 → 评估器 → 运行时值 → 编码器 → JSON 输出
         验证       语法      执行       值       序列化

模块

模块目的
Main.hs入口点,JSON-RPC 循环
Server.hsMCP 协议,请求路由
Core/Parser.hsJSON → 项验证
Core/Evaluator.hs带燃料的项执行
Core/Encoder.hs运行时值 → JSON
Core/Types.hs核心类型定义
Core/Syntax.hs项 ADT
Tools/Registry.hs工具存储

安全保证

  • 基于燃料的终止:每个评估都有有限的步骤(默认:10,000)
  • 纯评估:lambda 演算中没有 I/O 或效果
  • 有效执行:只有结构有效的项才能运行
  • 不可变注册表:工具在执行期间不能互相修改

MCP 集成

MCP-PIF 实现了模型上下文协议,用于工具发现和执行:

系统工具

  • evolve - 创建新工具(存储在注册表中)
  • run - 执行工具或内联 lambda 表达式
  • list - 显示所有已注册的工具
  • help - 显示原始操作和系统工具的文档

协议流程

  1. 客户端通过 stdin 发送 JSON-RPC 请求
  2. 服务器解析并路由到适当的处理器
  3. 对于工具执行:
    • 解析输入 JSON → 项
    • 注入工具代码到环境中
    • 在燃料限制下评估
    • 编码结果 → JSON
  4. 响应发送到 stdout

连接 MCP 客户端

# 示例使用 Python MCP SDK
import mcp

async with mcp.Client() as client:
    await client.connect(stdio_transport("cabal run mcp-pif"))

    # 创建一个工具
    await client.call_tool("evolve", {
        "name": "double",
        "description": "将一个数加倍",
        "code": {"mul": [{"var": "x"}, 2]}
    })

    # 使用它
    result = await client.call_tool("run", {
        "tool": "double",
        "input": 21
    })
    print(result)  # 42

未来方向

MCP-PIF 当前的设计保持了纯计算和效应操作之间的明确界限。已经考虑了几种扩展,这些扩展将以有趣的方式扩大这些界限:

分析函数

当前系统主要是合成的——使用原始操作组合新函数。一个自然的扩展将是分析能力:

  • 验证:静态分析项结构而不进行评估
  • 规范化:将项简化为规范形式
  • 等价检查:证明两个项计算相同的函数
  • 类型推断:为 lambda 项推导类型
  • 复杂度分析:估算燃料需求
  • 能力分析:按原始操作