1. 从「问 SAP 问题」到「查 SAP 证据」:MCP 接入到底改变了什么
SAP 系统通过 MCP 协议对接 AI 工具链,指的是把 SAP 的 ADT、RFC、ABAP 三类接口封装成 MCP 工具,让大模型在权限边界内直接读取程序源码、查询透明表数据、调用受控远程函数,从而完成查询、调用与自动化动作。它适合已经有一定 ABAP 开发基础、手里有 DEV/QA 环境、想用 AI 辅助排查业务问题或整理技术文档的 SAP 顾问、开发和运维人员。
很多人第一次听到「大模型连接 SAP」,脑子里浮现的画面是在聊天窗口里问一句「VA01 是干什么的」,然后 AI 给出一段标准解释。这种用法确实能用,但它和真正把 SAP 接进 MCP 之后的能力,差了一个数量级。
没有连接企业系统之前,大模型靠的是公开资料和训练数据。它能解释标准事务码、ABAP 语法、常见配置,却不知道你们公司的 ZSDR001 里写了什么过滤条件,不知道 ZMMFM003 为什么返回「库存地点不存在」,更看不到开发系统已经修复、生产系统还没传输的版本差异。
接入 MCP 之后,情况变了。大模型可以在权限允许的范围内调用工具,由工具进一步访问 SAP 的 ADT、RFC 或其他接口,拿到当前系统里的真实信息。它开始从一个「懂一些 SAP 知识的聊天机器人」,变成一个能协助取证、分析、开发、测试和知识沉淀的技术助手。
这篇文章我会按可跟做的顺序拆开讲:先讲清楚 MCP、ADT、RFC 三者的层次关系,再给出可复制的 MCP 服务端配置片段和 TaoToken 统一 Key 的接入参数,然后用 ADT 和 RFC 两条路径验证连通性,最后把常见的报错逐个排掉。你可以边看边在自己的 DEV 环境里试。
2. TaoToken 统一 Key 前置准备:MCP 服务端与模型侧怎么接
在动手写 SAP MCP Server 之前,先把模型侧的调用通道准备好。我自己的做法是让 MCP Server 通过一个统一的 OpenAI 兼容入口去调用模型,这样工具定义、鉴权、审计都集中在服务端,模型侧只认一个 Base URL 和一把 Key。
TaoToken 在这里扮演的角色就是那个统一入口。它的 API 地址是https://taotoken.net/api,兼容 OpenAI 的/v1/chat/completions和/v1/models接口。你需要在控制台创建一把 API Key,然后把它写进 MCP Server 的环境变量里,而不是硬编码在代码中。
创建 Key 的入口在控制台的 API Keys 页面:https://taotoken.net/console/api-keys?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=api_keys。拿到 Key 之后,先别急着接 SAP,用一条最简单的请求确认模型通道是通的。
curl https://taotoken.net/api/v1/chat/completions \ -H "Content-Type: application/json" \ -H "Authorization: Bearer $TAOTOKEN_API_KEY" \ -d '{ "model": "claude-sonnet-4-5", "messages": [ {"role": "user", "content": "用一句话说明 ABAP 内表和透明表的区别"} ] }'如果返回里有正常的choices[0].message.content,说明模型侧没问题。这一步很关键,因为后面 SAP MCP 报错时,你要能快速判断是模型通道的问题还是 SAP 接口的问题。
接下来是模型 ID 的选择。MCP 场景下工具调用比较密集,建议选支持 function calling 的模型。你可以在模型对话页面先试一下工具调用是否正常:https://taotoken.net/models?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=models。如果只是做代码分析和文档整理,长上下文模型会更稳;如果要做多轮 Agent 编排,编码计划类的套餐在成本上更划算,入口在https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=coding_plan。
环境变量建议这样组织,把模型侧和 SAP 侧彻底分开:
export TAOTOKEN_API_KEY="sk-你的Key" export TAOTOKEN_BASE_URL="https://taotoken.net/api" export SAP_BASE_URL="https://your-sap-dev.example.com:44300" export SAP_CLIENT="100" export SAP_USER="mcp_service" export SAP_PASSWORD="你的SAP密码" export SAP_ENV="DEV"注意:SAP 的登录凭据只保存在 MCP Server 这一侧,不要传给大模型。模型拿到的是「工具使用权」,不是 SAP 账号本身。这一点在正式环境里是硬性要求。
如果你用的是 Claude Code 这类客户端来驱动 MCP,配置里同样只填 Base URL 和 Key,模型 ID 按客户端要求写。三件套缺一不可:Base URL 指向https://taotoken.net/api,Key 用上面创建的,Model ID 选一个支持工具调用的。接入文档在https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=doc,里面有各客户端的字段对照。
3. 可复制的 SAP MCP Server 配置:ADT 与 RFC 双通道
这一节给你可以直接抄的配置。我按「MCP 客户端配置 + MCP Server 工具定义 + ADT/RFC 适配层」三层来写,路径和字段名尽量贴近真实项目。
先看 MCP 客户端的配置。以常见的mcp.json或settings.json为例,把 SAP MCP Server 注册进去:
{ "mcpServers": { "sap-adt": { "command": "python", "args": ["-m", "sap_mcp.server"], "env": { "TAOTOKEN_BASE_URL": "https://taotoken.net/api", "TAOTOKEN_API_KEY": "${TAOTOKEN_API_KEY}", "SAP_BASE_URL": "${SAP_BASE_URL}", "SAP_CLIENT": "100", "SAP_USER": "${SAP_USER}", "SAP_PASSWORD": "${SAP_PASSWORD}", "SAP_ENV": "DEV", "SAP_ALLOW_WRITE": "false" } } } }SAP_ALLOW_WRITE这个开关很重要。DEV 环境可以设成true允许激活对象,QA 设成false只读加受控测试,PRD 永远false。不要指望用提示词告诉模型「别改生产」,限制必须在服务端代码里真正执行。
再看 MCP Server 的工具定义。用 FastMCP 写一个最小可用的 ADT 工具集:
from mcp.server.fastmcp import FastMCP import os, requests mcp = FastMCP("sap_adt") SAP_BASE_URL = os.environ["SAP_BASE_URL"] SAP_CLIENT = os.environ.get("SAP_CLIENT", "100") def make_adt_request(url: str, method: str = "GET", **kwargs): session = requests.Session() session.auth = (os.environ["SAP_USER"], os.environ["SAP_PASSWORD"]) session.headers.update({ "Accept": "application/xml", "sap-client": SAP_CLIENT, }) return session.request(method, url, timeout=30, **kwargs) @mcp.tool(name="GetProgram") def get_program(program_name: str) -> str: """读取指定 ABAP 程序的当前源码。""" if not program_name: raise ValueError("program_name is required") url = f"{SAP_BASE_URL}/sap/bc/adt/programs/programs/{program_name}/source/main" resp = make_adt_request(url, "GET") resp.raise_for_status() return resp.text @mcp.tool(name="GetTableStructure") def get_table_structure(table_name: str) -> str: """获取透明表或结构的字段定义。""" url = f"{SAP_BASE_URL}/sap/bc/adt/ddic/tables/{table_name}/source/main" resp = make_adt_request(url, "GET") resp.raise_for_status() return resp.textADT 的 URL 路径是固定的,/sap/bc/adt/programs/programs/{name}/source/main取程序源码,/sap/bc/adt/ddic/tables/{name}/source/main取表结构。真实项目里还要处理 CSRF Token、对象锁、超时重试和字符编码,这些封装在make_adt_request内部就行。
RFC 通道单独写一个适配器,用 PyRFC 调用受控函数:
from pyrfc import Connection ALLOWED_FUNCTIONS = { "Z_GET_SALES_ORDER_INFO": "readonly", "BAPI_SALESORDER_GETDETAIL": "readonly", } def get_rfc_conn(): return Connection( ashost=os.environ["SAP_HOST"], sysnr=os.environ.get("SAP_SYSNR", "00"), client=SAP_CLIENT, user=os.environ["SAP_USER"], passwd=os.environ["SAP_PASSWORD"], lang="ZH", ) @mcp.tool(name="CallFunctionModule") def call_function_module(function_name: str, parameters: dict) -> dict: """调用经过授权的远程函数模块。""" if function_name not in ALLOWED_FUNCTIONS: raise PermissionError(f"{function_name} 不在白名单内") conn = get_rfc_conn() return conn.call(function_name, **parameters)白名单是 RFC 通道的生命线。CallFunctionModule这类工具属于高风险等级,正式环境必须区分只读函数、可写函数、允许使用的环境,以及是否需要人工确认。查询类可以自动执行,可能修改数据的函数一定要暂停等用户确认。
4. 验证连通性:用 ADT 和 RFC 各跑一次真实请求
配置写完,先别急着让模型自由发挥,手动验证两条通道是否通。这一步能帮你把「模型问题」和「SAP 接口问题」彻底分开。
先验证 ADT。启动 MCP Server 后,在客户端里发一条明确的工具调用请求:
{ "name": "GetProgram", "arguments": { "program_name": "ZSDR001" } }如果返回的是 ABAP 源码文本,说明 ADT 通道打通了。如果返回 401,说明 SAP 凭据或 client 不对;如果返回 404,说明程序名拼错或者该程序在当前系统不存在。我试过把程序名写成小写,ADT 会直接报 404,SAP 对象名对大小写敏感,这点要留意。
再验证 RFC。调用一个只读函数:
{ "name": "CallFunctionModule", "arguments": { "function_name": "Z_GET_SALES_ORDER_INFO", "parameters": { "IV_VBELN": "1234567890" } } }返回结构里应该能看到订单的抬头信息。如果报RFC_ERROR_LOGON_FAILURE,检查SAP_HOST、SAP_SYSNR和用户权限;如果报函数不存在,确认该函数在 SAP 里已经激活并且勾选了「远程启用」。
两条通道都通之后,跑一个完整的证据链分析。假设用户问:「销售订单 1234567890 在 ZSDR001 中查不到,但 VA03 能看到,帮我分析原因。」
第一步,模型调用GetProgram拿到 ZSDR001 的真实源码,发现查询里固定限制了AUART = 'ZOR'。第二步,模型调用ReadTableData查 VBAK:
{ "name": "ReadTableData", "arguments": { "table_name": "VBAK", "fields": ["VBELN", "AUART", "VKORG"], "where": "VBELN = '1234567890'", "max_rows": 1 } }返回{"VBELN": "1234567890", "AUART": "ZRE", "VKORG": "1000"}。第三步,模型得出结论:订单存在,但实际类型是 ZRE,被程序里AUART = 'ZOR'的过滤条件挡掉了。这个结论的依据是「当前系统源码 + 当前业务数据」,和普通大模型凭经验列可能性有本质区别。
ReadTableData这一层不能把模型生成的 SQL 直接转发给 SAP。必须限制只允许 SELECT、限制返回行数、控制可访问表、对敏感字段脱敏,并记录用户和查询条件。MCP 不只是协议转换器,它同时是 AI 访问 SAP 的安全网关。
5. 常见报错排查:401、local proxy failed、reading choices、OAuth
这一节按真实报错来排。我把踩过的坑按现象、原因、处理三步写,你对照自己的日志找。
401 Unauthorized(模型侧):现象是 MCP Server 调用模型时返回 401。原因通常是TAOTOKEN_API_KEY没设、设错,或者环境变量没被 MCP 进程继承。处理方式是先在终端里echo $TAOTOKEN_API_KEY确认有值,再用第 2 节的 curl 命令单独测一次。如果 curl 通、MCP 不通,就是 MCP 客户端的环境变量传递问题,检查mcp.json里有没有写"${TAOTOKEN_API_KEY}"。
401 Unauthorized(SAP 侧):现象是 ADT 请求返回 401。原因是 SAP 用户密码错、client 不对,或者该用户没有 ADT 访问权限。处理方式是先用 SAP GUI 或 Eclipse ADT 用同一账号登录一次,确认账号可用;再检查sap-client请求头是否和SAP_CLIENT一致。
local proxy failed:现象是客户端报local proxy failed或连接被拒。原因通常是 MCP Server 进程没起来,或者端口被占用。处理方式是先手动运行python -m sap_mcp.server看有没有报错,确认进程能正常启动;再检查客户端配置里的command和args路径是否正确。如果是容器环境,确认端口映射没冲突。
reading choices 报错:现象是模型返回里choices字段读不到,报reading 'choices'或类似。原因是模型通道返回了非预期结构,常见于 Base URL 写错、模型 ID 不存在,或者请求体格式不对。处理方式是确认 Base URL 是https://taotoken.net/api(注意结尾不要多加/v1,具体以接入文档为准),模型 ID 用/v1/models列出来的有效值,请求体里messages是数组。
OAuth 相关报错:现象是客户端提示 OAuth 授权失败或 token 过期。原因是部分客户端默认走 OAuth 流程,而 MCP 场景下用的是 API Key。处理方式是在客户端配置里显式指定用 API Key 鉴权,把 Base URL、Key、Model ID 三件套填全。如果客户端支持authType字段,设成api_key。
ADT 返回 403 Forbidden:现象是程序源码读不到。原因是 SAP 用户缺少该对象的显示权限,或者对象处于锁定状态。处理方式是让 Basis 给 MCP 服务账号加S_DEVELOP的显示权限,并确认对象没有被传输锁占用。
RFC 报 function not found:现象是CallFunctionModule报函数不存在。原因是函数没激活、没勾选远程启用,或者白名单里没加。处理方式是先在 SE37 里确认函数状态是「已释放」,属性里勾了「远程启用的模块」,再把函数名加进ALLOWED_FUNCTIONS。
排错时有个通用原则:先分层,再定位。模型侧的问题用 curl 测,SAP 侧的问题用 Eclipse ADT 测,MCP 层的问题看 Server 日志。三层分开测,比盯着一个报错猜要快得多。
6. 把 SAP MCP 用起来:从查询到受控自动化的边界
通道打通、报错排完之后,回到最初的问题:接上 MCP 到底能做哪些事。我按风险从低到高列一下,你可以对照自己的环境判断适合从哪一层切入。
只读查询层是最安全的起点。读取 Z 报表、Include、函数模块、类、CDS View 和数据字典对象,分析查询条件、内表读取、权限检查和数据流向。辅助排查业务问题时,围绕单据逐步查状态、配置、自建表、程序和日志,对比正常与异常数据,形成可验证的证据链。这一层在 DEV、QA、PRD 都可以开,PRD 保持只读即可。
代码检查层可以结合真实源码检查数据库访问、内表性能、异常处理、硬编码和版本兼容性。进一步封装 ADT 的 Check Run、SLIN 或 ATC 之后,还能把 SAP 自身的检查结果拉进来一起看。这一层建议在 DEV 和 QA 开,PRD 不开。
受控开发层要加严格门禁。流程是:分析需求、读取现有源码、设计修改方案、用户确认、UpdateProgram、CheckAbapSyntax、ActivateAbapObject、读回并验证。MCP 能提高效率,但不能取消需求确认、代码审查和变更管理。这一层只在 DEV 开,且SAP_ALLOW_WRITE要配合人工确认一起用。
传输与文档层适合做知识沉淀。查询 TR 包含的对象、对象激活状态,以及开发、测试和生产之间的版本差异,避免把版本不同误判成业务数据问题。通过读取函数、结构和数据元素,生成接口封装文档、字段映射表、测试数据模板。问题处理完后,把现象、证据、根因、解决方案和验证结果存成结构化案例,下次类似问题先检索历史经验,再结合当前 SAP 数据验证。
安全控制上,环境隔离、用户身份传递、工具分级、操作审计、人工确认这五件事要在 MCP Server 里真正落地。一次调用至少记录用户、环境、工具名称、参数摘要、执行结果和时间。涉及源码和业务数据时,防止敏感内容被完整写进普通日志。
如果你打算长期跑编码和 Agent 编排,Coding Plan 在成本上比按次调用更可控,入口在https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=coding_plan。需要先确认模型通道和工具调用是否正常,可以去模型对话页面试:https://taotoken.net/models?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=models。Key 的创建和管理在 API Keys 页面:https://taotoken.net/console/api-keys?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=api_keys。字段对照和客户端配置参考接入文档:https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=doc。
真正有价值的不是让 AI 自动操作所有事务码,而是在安全边界内把大模型的理解推理能力和 SAP 的真实系统证据连起来。大模型负责理解和推理,MCP 负责工具调用和能力控制,ADT 负责开发对象和技术能力,RFC 负责受控的业务函数调用,SAP 提供真实系统证据。先把连接稳定性、工具定义、权限边界、操作审计和环境隔离做好,再谈自动化,这条路才走得稳。