P2 Foundry
MCP 接入与安全标记:把本体安全地开放给 LLM 生态
V5 让 Foundry 长出"对外开放"的接口:MCP Server 把语义检索 / 对象查询 / 指标 / 写路径 Action / 数据集 5 项能力以工具形式暴露给 Claude 等 LLM 客户端,全程强制审计;安全标记(Markings)给属性打上"谁可持有、谁可见"的列级闸门;OWL/SHACL 把本体定义以标准语义格式外送。看完这 4 个故事,你就知道怎么开放得出去、又关得住。
平台管理员
数据工程师
开发工程师
MCP
安全标记
OWL/SHACL
共 4 个故事
能 / 不能速览
✅ 这个主题能做
- MCP Server:JSON-RPC 2.0 双 transport(stdio --user 绑定服务账号 / HTTP 挂 /api/v1/mcp 复用 JWT)
- 5 个工具:search_objects / query_objects(limit≤200 强制)/ get_metric / execute_action / get_dataset
- execute_action 走 writepath 五步流水线不可绕过;越权 403 红线;每次 tools/call 落审计(MCP_TOOL_CALL)
- Markings 三层模型驱动 CLS 第二道列级闸门(任一来源隐藏即隐藏,NULL AS col)
- OWL/TTL 整库导出 + 单对象 SHACL 校验形状导出(enum→sh:in、pattern→sh:pattern)
⛔ 这个主题做不了
- MCP 默认关闭(foundry.mcp.enabled=false → 路由不注册,访问 404),需显式开启
- 仅 tools 能力面:无 resources / prompts;tools/list 静态(listChanged=false)
- HTTP SSE GET 路由当前未注册(Handler 支持,gin 只挂了 POST /mcp)
- object_type / dataset / metric 的标记绑定仅登记、无消费方(只有 property 绑定驱动列级闸门)
- OWL/SHACL 单向导出,不做 roundtrip 保证;currency/percentage/enum/json/array 映射 xsd:string 并标注
适用角色
本主题面向三个角色:
- 平台管理员:开启 MCP、配置服务账号、管理安全标记与用户授权矩阵(admin 语义)。
- 数据工程师:导出 OWL / SHACL 给外部工具,配合标记验证列级隐藏效果。
- 开发工程师:把 MCP 客户端接上 Foundry,封装工具调用并消费审计。
运营 / 业务人员作为 MCP 工具的最终使用者(如让 LLM 查数、改订单状态),权限受写路径 RBAC 保护。
能力速览(能做什么)
MCP 双 transport
stdio(子进程,--user 显式绑定服务账号,未绑定拒绝启动)+ HTTP(/api/v1/mcp 复用 JWT,无会话 401)。
5 个 MCP 工具
语义检索、对象查询(limit≤200 红线)、指标查询、写路径 Action、数据集;工具输出经语义翻译,口径与 Web 端一致。
强制审计
每次 tools/call 落 MCP_TOOL_CALL:traceID=mcp_ 前缀、参数脱敏(secret/password/token 等键值替换 ***)、失败也留痕。
Markings 打标
打标定义 → 目标绑定 → 用户授权三层模型;属性绑定后与 ApplyCLS 并集叠加成第二道列级闸门。
OWL / SHACL 导出
整库 OWL/Turtle(Class / DatatypeProperty / ObjectProperty / subClassOf)+ 单对象 SHACL(NodeShape / PropertyShape / sh:in / sh:pattern)。
调整指南(怎么调整)
- 开 MCP:config.yaml 在 foundry 段下加 mcp.enabled=true(可选 service_user);不改配置则 /api/v1/mcp 404。
- 接客户端:HTTP 用 JWT Bearer 调 POST /api/v1/mcp;stdio 用 foundry mcp stdio --user <服务账号> 子进程方式。
- 收紧写权限:给服务账号只授必要的 action:<name>,越权即 403;无会话用户 401。
- 打标隐藏列:先建对象再绑定 property(存在性校验);dataset / metric 绑定可先行,目标未建也能打标。
- 外送本体:单对象用 GET /ontology/objects/:id/shacl(或 export?format=shacl),整库用 GET /ontology/export/owl。
做得好的场景
MCP + 标记让"对外开放"这件事同时满足即插即用与安全可控,特别适合以下场景:
- LLM 零适配接入:Claude Desktop 等 MCP 客户端一次握手(initialize → tools/list → tools/call)即能查数。
- 写数据有红线:execute_action 走 writepath 五步流水线,RBAC 第 2 步 403,模型发起的写操作与 Web 操作地位等价、全程留痕。
- 敏感列按人可见:PII 打标绑定属性后,未授权用户查询自动 NULL,不用逐列配 CLS 策略。
- 本体语义外送:OWL/SHACL 让外部推理器 / 校验工具直接消费对象、属性、链接与约束。
限制与不足
以下是明确的边界,使用前先知道:
- MCP 默认关闭:enabled=false 时路由不注册,访问 /api/v1/mcp 是 gin 404 而非 JSON-RPC 错误。
- 能力面窄:只有 tools,无 resources / prompts;工具集运行时不可变(listChanged=false)。
- SSE GET 未注册:Handler 支持 SSE 但 gin 路由层只挂了 POST /mcp,HTTP+SSE 客户端需自行补挂 GET。
- 标记消费面窄:只有 property 绑定驱动列级隐藏;object_type / dataset / metric 绑定仅登记。
- 指标聚合不叠加编辑态:get_metric 按源表口径计算,与行级查询可能不一致(已知语义债)。
- OWL/SHACL 单向导出:不做 roundtrip 保证;部分类型映射 xsd:string 并以 foundry:dataType 标注。
场景故事
故事 1
启用 MCP:从默认 404 到一次握手拿到 5 个工具
场景:MCP 启用
角色:平台管理员 + 数据工程师
耗时:约 5 分钟
- 背景
- 张工想把 Foundry 接进 Claude 客户端,让模型直接查订单数据。MCP Server 默认关闭(foundry.mcp.enabled=false 是红线),他先在 config.yaml 显式开启,重启后走一遍 initialize → tools/list 握手。
- 传统做法对比
- 以前给 LLM 暴露查询能力要自研一套工具 API,还要自己写鉴权和审计;MCP 客户端也得专门适配。现在 JSON-RPC 2.0 + 双 transport(stdio 绑服务账号 / HTTP 复用 JWT)是开放标准,Claude 等客户端零适配接入,一次握手即得工具清单。
- 角色
- 平台管理员(改配置 / 重启);数据工程师(走握手验证)。
- 操作步骤
-
- 在 config.yaml 的 foundry 段加 mcp.enabled=true(可带 service_user)
- 重启 Foundry(默认端口 18081),确认日志出现"Foundry MCP Server 已启用(HTTP /api/v1/mcp)"
- POST /api/v1/mcp 发 initialize 握手
- 再发 tools/list 查看 5 个工具
- 系统响应
- 握手返回:
{"jsonrpc":"2.0","id":1,"result":{
"protocolVersion":"2025-03-26",
"capabilities":{"tools":{"listChanged":false}},
"serverInfo":{"name":"zy-foundry-mcp","version":"v5-stage5-b7-3"},
"instructions":"ZY LightFoundry MCP Server:语义检索/对象查询/指标/Action 写路径/数据集。"}}
tools/list 返回 5 个工具(字典序):{"jsonrpc":"2.0","id":2,"result":{
"tools":[
{"name":"execute_action","description":"...","inputSchema":{...}},
{"name":"get_dataset","description":"...","inputSchema":{...}},
{"name":"get_metric","description":"...","inputSchema":{...}},
{"name":"query_objects","description":"...","inputSchema":{...}},
{"name":"search_objects","description":"...","inputSchema":{...}}]}}
- 结果洞察
- 默认关闭是安全红线:enabled=false 时 NewServer 返回 ErrDisabled,/api/v1/mcp 路由不注册、访问自然 404;stdio 子命令在禁用配置下同样拒绝启动。启用后 5 个工具全部就绪,依赖装配缺失(Ontology/Semantic/Search/Metrics/Executor/Datasets 六项)会拒绝启动,防半装配静默降级。
- 调整建议
- HTTP 场景用 JWT Bearer 调 POST /api/v1/mcp;stdio 场景给专用服务账号(--user 显式绑定,防匿名调用);不启用时 actionctl mcp ping 会收到 404,可作脚本判活信号。
- 动手试一试
- 登录:admin / admin1。输入内容:先不启用,POST /api/v1/mcp 发 initialize;再在 config.yaml 开启后重启重发。预期结果:未启用返回 404;启用后 initialize 返回 protocolVersion=2025-03-26,tools/list 列出 5 个工具。
- 限制提示
- 仅 tools 能力面(无 resources/prompts);HTTP SSE GET 路由当前未注册(gin 只挂 POST /mcp);MCP enabled 场景未加 HTTP 级冒烟测试(包内单测覆盖)。
故事 2
LLM 调工具查数据:limit 被钳制,每次调用都落审计
场景:工具调用
角色:开发工程师
耗时:约 8 分钟
- 背景
- MCP 接好后,开发小唐让模型执行 tools/call:先 search_objects 按"订单"做语义检索,再 query_objects 拉订单明细(故意传 limit=500 试探钳制),最后 get_metric 看总销售额口径。全程他去审计日志核对工具调用留痕。
- 传统做法对比
- 以前模型访问数据要专门写工具代码,鉴权 / 审计 / 口径全靠自己补,模型看到的数据和业务用户在 Web 端看到的还可能不一致。现在 5 个工具全部复用 semantic / metric / dataset 既有服务,工具输出经过语义翻译,自动带 RLS/CLS/属性级安全/编辑态叠加;每次 tools/call 强制落审计。
- 角色
- 开发工程师(封装工具调用 + 核对审计);MCP 客户端(发起调用)。
- 操作步骤
-
- tools/call search_objects,arguments 传 {"query":"订单","limit":5}
- tools/call query_objects,arguments 传 {"object_type":"order","fields":["id","status"],"limit":500}
- tools/call get_metric,arguments 传 {"metric_name":"total_gmv","dimensions":["region"]}
- 到审计日志查 MCP_TOOL_CALL 记录
- 系统响应
- query_objects 的 limit=500 被钳制为 200(红线):
{"jsonrpc":"2.0","id":3,"result":{
"content":[{"type":"text",
"text":"{\"columns\":[\"id\",\"status\"],\"rows\":[[\"A1\",\"open\"]],\"total\":1}"}],
"isError":false}}
审计日志出现:{"event":"MCP_TOOL_CALL","ref_type":"query_objects",
"ref_id":"mcp_<uuid>","user_id":"admin",
"details":{"tool":"query_objects","trace_id":"mcp_<uuid>","arguments":{"object_type":"order","limit":500}},
"result":"success"}
- 结果洞察
- query_objects 的 limit 默认 100、强制上限 200(超出钳制);search_objects 上限 50;get_metric 返回 columns/rows/metric 元信息。审计每次调用生成 traceID=mcp_ 前缀,参数里含 secret/password/token/api_key 的键值自动脱敏为 ***;失败(含越权 403)同样留痕 result=error。
- 调整建议
- 一次拉不完就分页多次查(limit 上限 200);敏感参数让客户端尽量别传明文凭证;模型发起的操作与 Web 端在审计链上地位等价,可回查"哪个服务账号、哪次调用、带了什么参数"。
- 动手试一试
- 登录:admin / admin1。输入内容:tools/call query_objects 传 limit=500。预期结果:返回行数 ≤200(钳制生效);审计日志出现 MCP_TOOL_CALL,ref_id 为 mcp_ 前缀 traceID。
- 限制提示
- get_metric 指标聚合按源表口径计算,不叠加编辑态(与行级查询可能不一致,为已知语义债);工具集运行时不可变;一次性拉全量会被协议层钳制。
故事 3
execute_action 越权红线:未授权 403,授权走五步流水线
场景:写路径
角色:运营人员 + 平台管理员
耗时:约 6 分钟
- 背景
- 运营小陈想让 LLM 客户端帮她改订单状态(order_id=2 从 pending 改为 shipped)。execute_action 是 MCP 侧唯一写能力,她先用自己账号调用——没有授权,看看平台怎么拦;再由管理员授权 action:update_order_status 后重试。
- 传统做法对比
- 以前模型写数据要么"根本没门"(只能读),要么给裸 SQL / 直连库权限(危险且无法审计)。现在 execute_action 直接调 writepath.Executor(VALIDATE_AND_EXECUTE),五步流水线(参数 Schema → 逐动作 RBAC → 记录级检查 → 属性级写权限 → 审计 + 幂等 + 乐观锁 + 编辑态)全走、不可绕过,越权就是 403。
- 角色
- 运营人员(发起调用);平台管理员(授权 action:update_order_status)。
- 操作步骤
-
- 用未授权账号 tools/call execute_action(action=update_order_status,params 含 status、record_filter 含 order_id)
- 观察 403 拒绝与审计留痕
- 管理员给账号授予 action:update_order_status 权限
- 再次调用,观察写路径返回与订单状态变化
- 系统响应
- 未授权返回 JSON-RPC 错误:
{"jsonrpc":"2.0","id":4,"error":{
"code":-32001,
"message":"<越权详情>",
"data":{"http_status":403,"code":"FORBIDDEN","trace_id":"mcp_<uuid>"}}}
授权后成功返回 writepath 结果:{"jsonrpc":"2.0","id":5,"result":{
"content":[{"type":"text",
"text":"{\"mode\":\"VALIDATE_AND_EXECUTE\",\"status\":\"success\",\"validation_result\":{\"result\":\"VALID\"},\"edits\":{\"changed_records\":1},\"rows_affected\":1,\"result\":{\"order_id\":2,\"status\":\"shipped\"},\"operation_id\":\"run_7\"}"}],
"isError":false}}
- 结果洞察
- RBAC(权限点 action:<name>)在流水线第 2 步强制校验,admin 绕过;未授权 403,无会话用户 401。每次调用自动生成新幂等键 mcp_<uuid>(MCP 侧不重复提交同一键),服务端幂等登记按 action_runs 生效。越权失败同样落审计(result=error),写操作与 Web 端操作在审计链上地位等价。
- 调整建议
- 给 MCP 服务账号做最小授权(只授需要的 action:<name>);批量改动先确认记录级检查(RecordScope:无 WHERE 无边界写整批拒绝);乐观锁冲突返回 409 语义,重试前先查最新值。
- 动手试一试
- 登录:admin / admin1。输入内容:未授权 tools/call execute_action(action=update_order_status)。预期结果:返回 error.code=-32001、data.http_status=403、data.code=FORBIDDEN;授权后再调返回 rows_affected=1。
- 限制提示
- 幂等键自动生成,MCP 侧不提供重放语义;HTTP/API 类回写(对接外部系统)未实现,仅 SQL 类四种 edit_type;demo 数据源重启重建,回写改动会被还原但编辑态与审计保留。
故事 4
安全标记隐藏列 + OWL/SHACL 导出:敏感与开放两手抓
场景:安全与导出
角色:平台管理员 + 数据工程师
耗时:约 12 分钟
- 背景
- 合规要求订单金额按人授权可见,同时数据团队要把 order 对象定义送给外部语义校验工具。管理员在"数据打标"页建 PII 标记并绑定 order.amount,给分析师 alice 授权;数据工程师顺手导出单对象 SHACL 和整库 OWL。
- 传统做法对比
- 以前列级隐藏靠手工配 CLS 策略,一个敏感列一份策略,漏配就裸奔;本体外送靠人写文档,语义丢失。现在 Markings 三层模型直接驱动列级闸门,未持有标记的用户查询自动 NULL;OWL/SHACL 把对象 / 属性 / 链接 / 约束以标准格式一次性导出。
- 角色
- 平台管理员(打标 / 授权,admin 语义);数据工程师(导出 + 验证列级隐藏)。
- 操作步骤
-
- POST /api/v1/markings 创建 PII(category=privacy)
- POST /api/v1/markings/:id/bindings 绑定 order.amount(target_type=property)
- POST /api/v1/markings/user-markings 给 alice 授权 PII
- 用未持有用户查询 order,观察 amount 列;授权后复查
- GET /ontology/objects/:id/shacl 与 GET /ontology/export/owl 拉取语义导出
- 系统响应
- 创建 / 绑定 / 授权返回:
POST /api/v1/markings { "name": "PII", "category": "privacy" }
-> { "code": 0, "data": { "id": "3f2a..." } }
POST /api/v1/markings/3f2a.../bindings { "target_type": "property", "target_id": "amount" }
-> { "code": 0, "data": { "id": "9b1c...", "marking_id": "3f2a..." } }
POST /api/v1/markings/user-markings { "user_id": "alice", "marking_id": "3f2a..." }
-> { "code": 0, "data": { "id": "7d4e..." } }
未持有 PII 的用户查询 order 时 amount 列隐藏:{ "code": 0, "data": {
"columns": ["order_id", "amount", "region"],
"rows": [[1, null, "华东"]] } }
SHACL 导出片段:# LightFoundry Ontology — SHACL 校验形状导出(B7-5)
@prefix sh: <http://www.w3.org/ns/shacl#> .
foundry:orderShape a sh:NodeShape ;
sh:targetClass foundry:order ;
sh:property foundry:order_order_idShape , foundry:order_statusShape .
- 结果洞察
- ColumnsHiddenByMarking 与 ApplyCLS 属性级安全经 MergeHiddenColumns 并集叠加——任一来源隐藏即隐藏,最终统一 NULL AS col 隐藏值而非整行拒绝。OWL 整库导出命名空间 http://zyinfo.local/foundry/ontology#:对象→owl:Class、属性→owl:DatatypeProperty(按 DataType→XSD 映射)、链接→owl:ObjectProperty、接口→rdfs:subClassOf。
- 调整建议
- 绑定 object_type/property 前先确保本体对象已建(直查主表校验存在性);dataset/metric 绑定可先行(目标未建也能打标);SHACL 看单对象约束、OWL 看整库结构,两者配合覆盖"校验 + 推理"两类消费。
- 动手试一试
- 登录:admin / admin1。输入内容:建 PII 标记 → 绑定 amount → 授权 alice。预期结果:未持有用户查询 amount 为 null、region 保持真实值;授权后 amount 恢复;GET /ontology/export/owl 返回含前缀声明的 Turtle 文本。
- 限制提示
- object_type/dataset/metric 的标记绑定仅登记、无消费方(只有 property 绑定驱动列级闸门);OWL/SHACL 单向导出不做 roundtrip 保证;currency/percentage/enum/json/array 映射 xsd:string 并以 foundry:dataType 标注。
常见问题
为什么 /api/v1/mcp 直接 404?
MCP Server 默认关闭(foundry.mcp.enabled=false)。未启用时 NewServer 返回 ErrDisabled,路由不注册,访问 /api/v1/mcp 是 gin 404(不是 JSON-RPC 错误)。在 config.yaml 的 foundry 段加 mcp.enabled=true 并重启即可。
MCP 里的 execute_action 安全吗?
安全。它直接调 writepath.Executor(VALIDATE_AND_EXECUTE),五步流水线全走不可绕过:参数 Schema 校验、逐动作 RBAC(越权 403)、记录级检查、属性级写权限、审计 + 幂等 + 乐观锁 + 编辑态落盘。每次 tools/call 都落 MCP_TOOL_CALL 审计。
打标和 CLS 列级安全是什么关系?
是叠加的两道闸门:ApplyCLS 是原有列级策略;Markings 是 V5 新增的第二道。属性绑定 marking 后,用户未持有该标记即隐藏该列,与 CLS 结果取并集(任一来源隐藏即隐藏),最终统一把命中列替换为 NULL AS col。
OWL 和 SHACL 有什么区别?
OWL 描述"本体是什么":对象是 Class、属性是 DatatypeProperty、链接是 ObjectProperty、接口是 subClassOf,适合推理与语义对齐;SHACL 描述"数据应该长什么样":NodeShape + PropertyShape、基数、枚举(sh:in)、正则(sh:pattern),适合做数据校验。
MCP 工具能接 SSE 吗?
Handler 实现支持 SSE(GET + Accept: text/event-stream 返回 endpoint 事件和 30s 心跳),但 server.go 当前只注册了 POST /api/v1/mcp。HTTP+SSE 客户端接入需自行补挂 GET 路由,或直接用 Handler 挂载。
主题小结
一句话:MCP 把本体能力以标准协议开放给 LLM 生态(默认关闭、写路径 403 红线、强制审计),Markings 用"谁持有谁可见"做列级闸门,OWL/SHACL 把语义外送。记住几个边界:仅 tools 能力面、SSE GET 未注册、只有 property 绑定驱动列级隐藏、OWL/SHACL 单向导出。