MCP 面试高频考点:如何给 Java MCP Server 写可重复执行的集成测试,并兼顾最小权限与审计日志
面试场景说明
这是一场偏后端与 AI 应用基础设施方向的技术面试,候选人有 Java 和 Spring Boot 经验,正在介绍自己做过的 MCP(Model Context Protocol)服务。面试官没有停留在"什么是 MCP"这类概念题,而是围绕一个具体工程问题持续追问:如何为 MCP Server 写稳定、可重复执行的集成测试,同时把最小权限和审计日志真正落到测试里。
基础问题:你会怎么测一个 Java MCP Server?
面试官: 假设你负责一个基于 Java MCP SDK 的 MCP Server,里面暴露了若干 Tools、Resources 和 Prompts。现在要做集成测试,而且要求测试可重复执行,不能依赖人工点界面,你会怎么设计?
候选人: 我的结论是,不要只测业务方法本身,要从"协议可达、能力可见、调用可校验、权限可验证、日志可审计"这五层来测。
具体来说,MCP 是客户端-服务端架构,通信基于 JSON-RPC,Server 向 Client 暴露 Tools、Resources、Prompts 三类能力 资料2。所以集成测试不能只调内部 service,而应该启动一个接近真实运行方式的 MCP Server,再用 MCP Client 走真实协议去连它,然后断言:
- 能力发现是否正确,比如 Tool 名称、描述、输入 schema 是否符合预期;
- Tool 调用是否成功,返回值是否符合协议;
- Resource 读取是否正常,URI 路由是否正确;
- Prompt 模板是否能被枚举和获取;
- 权限不足、参数非法、资源越权时是否按预期失败;
- 审计日志是否落盘或进入统一日志通道,且敏感字段已脱敏。
在 Java 技术栈里,可以使用 MCP Java SDK 来实现和配置 Server;如果项目基于 Spring Boot,也可以用 Spring AI MCP 的 starter 做集成,它提供了 server starter 和基于注解的方法处理能力 资料1。测试里我会优先选择和生产一致的传输方式做覆盖,但本地集成测试为了稳定性,会优先考虑 stdio 或同进程内可控制的传输;远程协议相关的鉴权、超时、限流、会话管理再单独补一组 Streamable HTTP 测试 资料2。
第一轮追问:为什么不能只写单元测试?
面试官: 你说要走真实协议,那为什么不直接对每个 Tool 方法写单元测试?那样不是更快吗?
候选人: 单元测试当然要写,但它覆盖不到 MCP 的协议边界。
第一,很多问题不是业务逻辑错,而是协议映射错。比如 Tool 的输入 schema 生成不对、参数名和业务方法参数没对上、Resource URI 模板解析错、异常没有转成符合 JSON-RPC 约定的错误,这些问题单元测试发现不了。
第二,MCP Server 的安全校验必须在服务端完成。资料里明确提到,Tool 的参数 schema 只是结构约束,不能代替服务端校验和授权;模型传入的文本必须视为不可信输入 资料2。也就是说,哪怕方法内部逻辑没问题,只要请求在进入方法前没做权限和输入校验,集成时就可能出问题。
第三,审计日志是跨切面能力,不是单个 Tool 方法自己能完整负责的。谁在什么时间调用了哪个 Tool、访问了什么资源范围、结果成功还是失败,这些信息只有在请求经过传输层、鉴权层、执行层之后才能完整记录 资料2。
所以我的取舍是:业务规则用单元测试保证,协议、权限、审计、异常映射用集成测试保证。
面试官: 那可重复执行怎么保证?比如测试会创建文件、调用外部接口、写入日志,第二次跑就互相污染。
候选人: 核心原则是"测试拥有自己的最小生命周期"。
我会给每个测试类或每个测试用例分配独立的临时目录、独立配置、独立日志输出、独立账号/权限上下文;涉及外部依赖时,不用真实生产服务,而是用可控替身,比如内存实现、Testcontainers 启动的依赖,或者固定响应的桩服务。对于文件路径、URL、SQL、Shell 参数这类高风险输入,测试里要专门构造越权和非法样例,验证服务端约束是否生效 资料2。
另外一个容易踩坑的点是 stdio 传输。MCP 的 stdio 模式下,标准输出用于协议消息,调试日志必须写到标准错误;如果业务代码或日志框架不小心把 JSON-RPC 以外的内容打到 stdout,会直接破坏通信,导致测试偶发失败 资料2。所以集成测试里必须断言"stdout 中只出现协议消息,日志走 stderr 或独立日志通道",否则问题会很隐蔽。
第二轮追问:最小权限怎么测进用例里?
面试官: 你提到最小权限,这不是线上配置吗?为什么要写进集成测试?
候选人: 如果不测,最小权限很容易退化成"文档上写了,代码里没拦住"。
MCP 的安全要求很明确:远程 Server 应对每次请求执行授权检查,不能只判断用户是否登录;也不能因为请求来自 AI 应用就默认可信 资料2。对 stdio 场景,代理或宿主进程还应限制文件系统访问、做沙箱隔离,并记录所有 stdio 使用情况 资料4。所以测试不能只验证"管理员能调用成功",还要验证"低权限主体只能访问被授权的 Tool 和 Resource"。
我一般会把权限测试分成三类:
- Tool 级权限:某账号只能看到白名单 Tool,未授权 Tool 既不能枚举到,也不能直接调用;
- Resource 级权限:同一类 Resource URI 下,A 用户只能读自己的资源,不能通过构造 URI 读到 B 的数据;
- 输入级约束:即使有权限调用 Tool,传入越权路径、危险 URL、注入型参数时也要被拒绝。
这里我不会把"权限是否生效"只寄托在前端或 Client 侧隐藏按钮,而是在 Server 集成测试里直接构造请求去打。因为 Client 侧防护只是纵深防御的一层,Server 才是最终边界 资料2资料4。
可以用版本无关的伪代码表达测试思路:
text
启动 MCP Server(测试配置, 临时目录, 审计日志输出)
使用 lowPrivilegeClient 连接 Server
断言 lowPrivilegeClient.listTools() 只包含允许的 toolA、toolB
断言 lowPrivilegeClient.callTool("adminTool", params) 返回授权错误
断言 lowPrivilegeClient.readResource("file:///outside-workspace/secret.txt") 返回拒绝
使用 auditReader 读取本次测试产生的审计日志
断言日志中包含 caller=lowPrivilege、tool=adminTool、result=denied
断言日志中不包含明文 token、密码、完整敏感内容
面试官: 如果权限逻辑依赖 OAuth2 或 API Key,测试里怎么处理?
候选人: 结论是测试环境要启用真实安全链,但使用可控凭据。
Spring AI MCP 提供了 OAuth 2.0 和 API Key 相关安全支持 资料1。集成测试里我不会绕过安全过滤器去"假装登录",而是启动完整安全配置,然后给不同测试客户端发放不同范围的凭据:只读凭据、可调用特定 Tool 的凭据、管理员凭据、过期凭据、缺 scope 的凭据。这样才能测出传输层、认证层、授权层之间有没有缝隙。
比如 API Key 场景,要验证: - 缺失凭据时请求被拒绝; - 凭据有效但 scope 不足时,Tool 调用和 Resource 读取都被拒绝; - 凭据绝不能出现在 Tool 返回值、普通日志或模型上下文中 资料2。
这也是一个常见坑:很多人在测试里为了方便,直接关闭认证,结果线上一开安全,Tool 列表、CORS、会话、用户上下文传递全出问题。
第三轮追问:审计日志要测到什么程度才算合格?
面试官: 你多次提到审计日志,那你觉得集成测试里对审计日志要断言到什么粒度?
候选人: 审计日志不是"有一条日志就行",而是要满足可追责、可脱敏、可关联。
根据 MCP 安全实践,审计日志应记录谁在什么时间调用了哪个 Tool、关键资源范围与结果状态,同时对敏感字段脱敏 资料2。因此测试至少要覆盖以下断言:
- 调用成功时:记录调用主体、时间、Tool/Resource/Prompt 标识、关键参数摘要、目标资源范围、结果状态;
- 调用失败时:区分参数错误、鉴权失败、依赖超时、业务失败等结果,不能都打成"error"就结束;
- 敏感信息脱敏:token、密钥、身份证号、手机号、完整文件路径中的敏感段等不能明文落日志;
- 不可抵赖性:日志格式要稳定,便于后续检索和告警,不能因为异常就丢上下文。
我不会在测试里硬编码"日志必须是某一行文本",那样太脆弱;更合理的是定义一个审计事件结构,测试时从内存 Appender、临时日志文件或测试专用日志通道读取结构化事件,再对字段做断言。
伪代码大概是这样:
text
event = auditSink.findLastEventByRequestId(requestId)
assert event.caller == "user:tester-001"
assert event.operation == "tool.call"
assert event.target == "document.search"
assert event.resourceScope contains "workspace=test-ws"
assert event.result == "success"
assert event.serializedContent doesNotContain apiKey
assert event.serializedContent doesNotContain rawSecret
面试官: 如果 Tool 执行到一半抛异常,审计日志还能保证写出来吗?
候选人: 这是测试必须覆盖的异常路径。我的结论是:审计日志的写入不能和业务成功路径绑定,必须放在请求边界的 finally 类机制或拦截链里,确保鉴权失败、参数异常、业务异常、超时中断时都能留下记录。
同时要注意两点取舍:
- 审计日志本身不能把敏感异常信息全量打出去,否则会泄露内部路径、SQL、堆栈片段;
- 审计失败不应默认把用户请求全部放行,但也不能因为日志系统抖动就让所有业务不可用,具体降级策略要根据安全等级和业务 SLA 确定,相关超时、重试、队列长度需要通过压测和风险评估确定,而不是拍脑袋写死。
方案设计小结:一个可落地的测试分层
候选人: 如果让我给出一套可落地的方案,我会这样分层:
- 单元测试层:覆盖 Tool、Resource、Prompt 对应的纯业务逻辑,快速验证参数转换、业务规则、异常分支;
- 协议集成测试层:启动 Java MCP Server,用真实 Client 连接,验证能力发现、JSON-RPC 调用、schema、异常映射;
- 安全集成测试层:启用 OAuth2/API Key 等安全机制,验证最小权限、输入校验、越权访问拒绝、危险操作拦截;
- 审计测试层:采集结构化审计事件,验证字段完整性、结果分类、敏感信息脱敏、异常路径落日志;
- 传输兼容层:stdio 重点测日志输出不能污染 stdout,Streamable HTTP 重点测认证、会话、超时、限流等远程场景 资料2资料4。
适用边界也很明确:这一套适合需要长期维护、要接入真实 Host、涉及文件系统、内部系统或高风险操作的 MCP Server。如果只是本地临时 demo,全量集成测试成本偏高,可以适当裁剪;但只要涉及多用户、远程访问或不可逆操作,权限和审计就不能省。
面试官点评
考察点: 这道题表面上在问"怎么写集成测试",实际考察的是候选人是否理解 MCP 的协议边界、安全模型和工程化落地能力。MCP Server 不是普通 HTTP 接口,也不是本地函数集合;候选人必须同时理解客户端-服务端架构、JSON-RPC 通信、三类能力的语义差异、传输层注意事项,以及"schema 不等于授权"这一核心安全原则 资料2。
合格回答: - 能说清为什么要通过真实 Client-Server 协议路径做集成测试,而不是只测方法; - 知道 stdio 下 stdout/stderr 的区别,能解释日志污染会导致通信失败; - 能把最小权限落实到 Tool、Resource、输入参数三个层面,并强调服务端每次请求都要鉴权; - 知道审计日志要记录主体、操作、资源范围、结果,并做敏感字段脱敏; - 能说明测试需要独立生命周期,避免环境污染。
加分项: - 能区分 stdio 与 Streamable HTTP 的不同测试重点,不把本地和远程场景混为一谈; - 能说明测试中应启用真实安全链,而不是简单关闭认证; - 能指出审计日志必须覆盖异常路径,并考虑日志系统自身故障的取舍; - 能明确一个容易踩坑的细节,例如 stdout 被日志污染、凭据泄露到日志、只读资源被错误设计成有副作用 Tool 等 资料2。
总结
给 Java MCP Server 写可重复执行的集成测试,本质上是在验证协议正确性、权限边界和审计闭环,而不只是验证业务函数能不能跑通。使用 Java MCP SDK 或 Spring AI MCP 搭建测试中的 Server 与 Client 时,要尽量保持与生产一致的传输和安全配置;测试用例要覆盖成功路径、越权路径、非法输入和异常中断;审计日志要做到可追责、可检索、可脱敏。真正稳定的 MCP 服务,不是"本地调通了"就算完成,而是每一次改动都能被自动化测试证明:能力没偏、权限没松、日志没漏、传输没坏。