资讯动态

openJiuwen agent-core-java StdioClient 详解:基于 stdio 传输的 MCP 客户端原理与实战

发布时间:2026/10/13 10:43:17 来源:尧图企业网站定制
人工智能AI AgentAgent 框架工具调用Agent 工作流RAGMCP Clients强化学习【免费下载链接】agent-core-javaopenJiuwen agent core Java是 openJiuwen Core的 Java版本提供AI Agent开发、运行、调优与演进相关的全套SDK能力。项目地址https://gitcode.com/openJiuwen/agent-core-java点击查看免费下载导读StdioClient是 openJiuwen agent-core-java 中实现MCPModel Context Protocolstdio 传输的客户端它通过与本地子进程的标准输入/输出流通信让 Java Agent 能够直接调用本地 MCP Server 暴露的工具能力。本文将围绕该类展开先梳理它在 MCP 客户端体系中的定位再逐字段、逐方法讲解其 API 与内部协议实现随后深入McpServerConfig配置模型、进程启动与命令白名单安全机制最后结合仓库测试用例给出可复现的实战示例帮助你正确接入本地 stdio 型 MCP Server。一、背景MCP 与 stdio 传输MCPModel Context Protocol是连接大模型应用与外部工具/数据源的开放协议。openJiuwen agent-core-java 的 MCP 支持位于core/src/main/java/com/openjiuwen/core/foundation/tool/mcp/目录下其客户端体系覆盖了多种传输方式客户端类传输类型适用场景StdioClientstdio启动本地子进程通过标准输入输出通信SseClientsse基于 Server-Sent Events 的远程端点StreamableHttpClientstreamable-http基于 HTTP 流式传输的远程端点OpenApiClientopenapi基于 OpenAPI 规范的 HTTP 服务PlaywrightClientplaywright浏览器自动化工具StdioClient定位于本地进程对接它将 MCP Server 作为子进程拉起随后通过管道读写该进程的 stdin/stdout以Content-Length分帧的 JSON-RPC 2.0 协议完成initialize、tools/list、tools/call等标准交互。对应实现位于 StdioClient.java。从源码注释看Java 版StdioClient与 Python 版openjiuwen/core/foundation/tool/mcp/client/stdio_client.py保持行为镜像这保证了同一套 MCP 服务器配置在 Java 与 Python SDK 之间的一致语义。二、类定义与核心字段类声明public class StdioClient implements McpClient它实现了包内顶层的同步契约接口McpClient位于 McpClient.java接口定义的方法包括boolean connect(int retryTimes, float timeout)与默认的connect()默认retryTimes1、无超时boolean disconnect(float timeout)与默认的disconnect()ListObject listTools(float timeout)、Object callTool(String toolName, MapString, Object arguments, float timeout)OptionalObject getToolInfo(String toolName, float timeout)ListObject listResources(float timeout)、Object readResource(String uri, float timeout)String getServerPath()另外接口还提供了reconnect(timeout)默认实现语义为「先disconnect再connect(1, timeout)」用于瞬时故障后的传输重建。实例字段字段类型默认值说明configMcpServerConfig-客户端配置必填构造时经Objects.requireNonNull校验nameString-取自config.getServerName()用于工具卡片标识paramsMapString, Object空LinkedHashMap配置中的params承载command、args、env、cwd等进程启动参数sessionFactorySessionFactoryJsonRpcProcessSession::open会话工厂默认创建 JSON-RPC 子进程会话sessionStdioSessionnull当前活跃的 stdio 会话disconnectedbooleanfalse断连标记用于幂等关闭文档中提及的requestCounterAtomicLong、processProcess、stdout、stdin等字段在具体实现中被下沉到内部类JsonRpcProcessSession中管理见下文第四部分这也是源码相对于文档描述更精细化的体现。三、公开 API 详解构造方法public StdioClient(McpServerConfig config)以指定配置创建客户端内部委托给包级构造StdioClient(config, JsonRpcProcessSession::open)即将默认的会话工厂绑定为「拉起进程 JSON-RPC 帧解析」的会话实现。connect启动子进程并握手public boolean connect(int retryTimes, float timeout)循环执行最多retryTimes 1次尝试maxAttempts Math.max(0, retryTimes)循环条件i maxAttempts每次尝试调用tryConnectOnce(timeout)先由buildServerParameters()构造StdioServerParameters再通过会话工厂打开进程会话并发送initialize请求成功返回ConnectAttempt.SUCCESS遇到中断返回STOP立即终止其他异常返回RETRY重试前Thread.sleep(100L * (attemptIndex 1))退避第 1 次重试前等待 100ms第 2 次 200ms依次递增失败路径会调用disconnect(NO_TIMEOUT)清理半开资源cleanupFailedConnect()。连接成功的标志是子进程已启动且initialize握手完成。JsonRpcProcessSession.initialize的实现见 StdioClient.java会发送{jsonrpc:2.0,id:1,method:initialize,params:{ protocolVersion:2024-11-05, clientInfo:{name:agent-core-java,version:0.1.14}, capabilities:{} }}收到响应后再发送notifications/initialized通知完成协议握手。disconnect幂等关闭public boolean disconnect(float timeout)若disconnected已为true直接返回true幂等正常路径调用session.close(timeout)由会话关闭 stdin/stdout 流并销毁子进程若关闭过程抛出RuntimeException会回退到session.closeClientFallback()再次尝试强制清理对应 Python 中客户端上下文管理器__aexit__的兜底行为并仍然返回truefinally中将session置空避免悬挂引用。listTools枚举工具public ListObject listTools(float timeout)向会话发送tools/list请求把返回的ToolDefinitionname、description、inputSchema逐项转换为McpToolCard卡片上附加serverName即config.getServerName()与serverId即config.getServerId()便于上层工具注册表区分工具来源。McpToolCard定义于 McpToolCard.java其toolInfo()方法返回McpToolInfo包含name、description、parameters、serverName可直接对接工具发现能力。callTool调用工具public Object callTool(String toolName, MapString, Object arguments, float timeout)向会话发送tools/call请求体为{name: toolName, arguments: {...}}返回结果经McpBase.extractMcpToolResultContent(...)抽取实际内容后返回。以测试用例callToolExtractsResultContentAndPassesArguments为例当会话返回{content:[{text:ok}]}时callTool的返回值是字符串ok而非整包 JSON。getToolInfo按名查询工具public OptionalObject getToolInfo(String toolName, float timeout)实现为「先listTools(timeout)再遍历过滤名称匹配的McpToolCard」命中返回Optional.of(card)未命中记录 warning 日志并返回Optional.empty()。资源类方法listResources(float timeout)发送resources/list返回resources数组readResource(String uri, float timeout)发送resources/read返回contents字段。这两者在接口McpClient中已有默认实现默认返回nullStdioClient对其进行了覆盖因此基于 stdio 的本地 MCP Server 同样支持资源Resource能力。getServerPathpublic String getServerPath()直接返回config.getServerPath()即配置中声明的服务器路径/命令。四、内部实现JsonRpcProcessSession 的帧协议真正承载「子进程 JSON-RPC 帧」的是StdioClient的内部静态类JsonRpcProcessSession见 StdioClient.java。理解它对排查 stdio 连接问题至关重要。进程启动open(StdioServerParameters parameters)使用ProcessBuilder启动子进程命令行 command argscommandLine(...)拼接redirectErrorStream(false)stdout 与 stderr 分离stdout 专用于协议响应stderr 仅作诊断若env非空通过processBuilder.environment().putAll(env)注入环境变量若cwd非空通过processBuilder.directory(new File(cwd))指定工作目录。写入侧新行分隔帧private void writeFrame(byte[] jsonBytes) { stdin.write(jsonBytes); stdin.write(\n); stdin.flush(); }客户端发出的每个 JSON-RPC 消息都是单行 JSON 换行符不携带Content-Length头测试jsonRpcProcessSessionUsesNewlineDelimitedJsonRpcFramesAndSkipsNotifications中明确断言stdin中不包含Content-Length。读取侧同时兼容新行帧与 Content-Length 帧readFrame()的逻辑是readLine()逐字节读取直到\n自动去掉结尾\r跳过空行若该行以content-length:开头忽略大小写则进入readContentLengthFrame模式继续读取后续头行直到空行解析Content-Length数值再stdout.readNBytes(contentLength)精确读取消息体否则将该行直接按 JSON 解析。也就是说客户端发送的是新行分隔帧但能够兼容读取服务端返回的 Content-Length 分帧消息这与 MCP stdio 规范服务端通常使用Content-Length头 空行分帧相吻合。若读取时发现响应流提前关闭实际字节数不足声明的contentLength会抛出IOException(Stdio MCP response stream closed before reading full body)。响应匹配与服务器消息处理readResponse(requestId)在循环中若帧含method字段视为服务器发来的消息无id的消息被忽略ping消息会回写{result:{}}其他方法回写错误码-32601Method not found若帧的id与当前请求id不匹配循环跳过继续读取下一帧——这正是文档「读取响应时会循环跳过不匹配当前请求 id 的帧」的实现细节它使同一连接上的并发/乱序响应得以正确配对若帧含error字段抛出IllegalStateException否则返回result字段。超时与中断readResponseWithTimeout(requestId, timeout)使用单线程 Executor 提交读取任务并通过Future.get(timeoutMillis, TimeUnit.MILLISECONDS)等待超时TimeoutException取消任务、cleanupProcess()销毁进程抛出IOException(Timed out waiting for stdio MCP response id ...)并附加 stderr 尾部诊断信息被中断InterruptedException恢复线程中断标志同样清理进程后抛出执行异常ExecutionException解包并重新抛出原始异常。stderr 排水线程会话启动时创建名为stdio-mcp-stderr-drain的守护线程持续以 UTF-8 读取 stderr维护一个有上限8192 字符STDERR_TAIL_CHARS的尾部缓冲区stderrTail用于超时/失败时输出最近的服务端诊断日志关闭时会以 200ms 等待排水线程退出joinStderrDrain。进程清理cleanupProcess()在closeLock同步块内保证只执行一次依次静默关闭 stdin、stdout调用process.destroy()等待默认 1 秒DEFAULT_TERMINATION_WAIT退出若仍存活则process.destroyForcibly()强制终止并再次等待最后关闭 stderr 并等待排水线程结束。五、配置模型McpServerConfig 与启动参数StdioClient的输入是 McpServerConfig.java其关键字段如下字段JSON 属性类型说明serverIdserver_idString服务器标识为空时自动取serverName否则生成无连字符的随机 UUIDserverNameserver_nameString服务器名称serverPathserver_pathString服务器路径/命令stdio 场景下即要启动的命令clientTypeclient_typeString传输类型默认ssestdio 客户端应配置为stdioparams-MapString, Object额外参数如 stdio 的 command/args/env/cwdauthHeadersauth_headersMapString, String认证请求头主要供 HTTP 类传输使用authQueryParamsauth_query_paramsMapString, String认证查询参数connectTimeoutSecondsconnect_timeout_secondsDouble连接超时秒callTimeoutSecondscall_timeout_secondsDouble调用超时秒stdio 专有的 params 键buildServerParameters()会把params映射为StdioServerParameters记录command、args、env、cwd、encodingErrorHandlerparams 键类型说明commandString实际执行命令若缺失则回退到serverPathargsList追加的命令行参数如[-m, server]envMapString, String注入子进程的环境变量cwdString子进程工作目录encoding_error_handlerString仅接受strict/ignore/replace非法值回落为strict测试用例buildServerParametersUsesParamsAndNormalizesEncodingHandler验证了这些映射与encoding_error_handler的归一化行为传入非法的drop会被归一为strict。推荐构造示例McpServerConfig config McpServerConfig.builder() .serverId(my-local-mcp) .serverName(my-local-server) .serverPath(python3) // stdio 传输要启动的本地命令 .clientType(stdio) .params(Map.of( args, List.of(/path/to/server.py, --port, 9000), env, Map.of(MCP_LOG_LEVEL, debug), cwd, /path/to/workspace )) .build(); StdioClient client new StdioClient(config); boolean ok client.connect(1, McpServerConfig.NO_TIMEOUT); // 启动进程并完成 initialize 握手 ListObject tools client.listTools(McpServerConfig.NO_TIMEOUT); client.callTool(lookup, Map.of(query, hello), McpServerConfig.NO_TIMEOUT); client.disconnect(McpServerConfig.NO_TIMEOUT);超时参数NO_TIMEOUT -1.0F表示不限时传入正数时以秒为单位内部通过timeoutMillis max(1, ceil(timeout * 1000))转换为毫秒最小 1ms。六、安全机制命令白名单校验StdioClient自带两层命令合法性校验resolveAllowedCommand见 StdioClient.java这是本地进程型客户端的核心安全防线显式声明白名单若serverPath非空则serverPath为唯一允许的可执行文件。params.command中请求的命令必须先toRealPath()解析真实路径与serverPath解析出的真实路径比对不一致直接抛SecurityException(MCP stdio command is not in the configured allowlist.)。测试allowsOnlyCommandDeclaredByServerPath验证了「声明java却请求javac会被拒绝」。默认白名单serverPath为空时的兼容模式仅允许java、java.exe、python、python.exe、python3、python3.exe六个命令名。此时命令须落在PATH中且为可执行文件resolveCommand按PATH逐目录查找对java/java.exe额外校验其解析结果与java.home/bin下的 JVM 可执行文件一致支持白名单名称的绝对路径启动器如 venv 内的python3软链并保留符号链接路径本身测试preservesSymbolicLinkForAllowlistedAbsoluteLauncher。通用校验命令含\0字符、为空白、不可执行、无法在PATH解析均会抛出SecurityException或IllegalArgumentException。完整的安全行为由 StdioClientSecurityTest.java 覆盖包括拒绝未声明命令、拒绝非可执行文件、拒绝空白命令、保留符号链接等。七、客户端注册与工厂获取StdioClient不仅可以直接new也可通过全局客户端注册表按传输类型获取。入口是 McpClients.java 的registerDefaults()它会幂等注册sse、stdio、streamable-http、streamable_http、openapi、playwright六类工厂McpClients.registerDefaults(); McpServerConfig config ...; // clientType stdio StdioClient client (StdioClient) ClientRegistry.getClientRegistry() .getClient(stdio, mcp, Map.of(config, config));normalizeClientType支持常见别名归一化mcp_stdio→stdio、streamableHttp→streamable-http、open-api→openapi等由 McpClientsTest.java 的normalizeClientTypeSupportsCommonAliases用例覆盖。工厂要求kwargs[config]必须是McpServerConfig实例否则抛出带明确提示的IllegalArgumentException。八、测试覆盖与行为验证仓库测试为本文所述行为提供了可复现的证据主要集中在 StdioClientTest.java测试用例验证的行为connectInitializesSessionAndDisconnectIsIdempotentconnect成功后会话完成初始化disconnect可重复调用且只关闭一次connectRetriesUntilSessionOpens首次打开失败时按retryTimes重试operationsRequireConnectedSession未连接时调用listTools抛RuntimeException(Not connected to Stdio server)listToolsMapsSessionToolsToMcpCardstools/list结果正确映射为McpToolCard携带serverName/serverIdcallToolExtractsResultContentAndPassesArgumentstools/call结果抽取content[].text参数原样透传getToolInfoFindsMatchingToolOrReturnsEmpty按名称过滤工具缺失返回Optional.empty()jsonRpcProcessSessionTimesOutWaitingForResponseAndCleansProcess响应超时抛IOException且进程被destroydestroyForciblyjsonRpcProcessSessionUsesNewlineDelimitedJsonRpcFramesAndSkipsNotifications发送侧为单行 JSON 换行读取时跳过通知帧正确配对响应jsonRpcProcessSessionSendsInitializedNotificationAfterInitializeResponseinitialize响应后发送notifications/initializedjsonRpcProcessSessionRespondsToServerPingWhileWaitingForResponse等待响应期间能应答服务器pingjsonRpcProcessSessionDrainsStderrAndKeepsTail/...BoundedStderrTail...stderr 被排水线程持续收集尾部缓冲有界8192 字符此外 McpDiscoveryTimeoutTest.java 覆盖了慢调用、洪水帧等边界场景下的超时与进程清理。九、常见问题排查SecurityException: MCP stdio command is not in the configured allowlist.检查serverPath与params.command是否指向同一个可执行文件StdioClient会按真实路径比对若serverPath为空则命令必须属于默认白名单java/python/python3等且能从PATH解析到可执行文件。connect一直失败连接返回 false先手动在终端运行一次命令确认无语法错误再观察tryConnectOnce是否因initialize超时失败。超时异常消息中会附带 stderr 尾部stderr tail: ...这是最重要的诊断线索。IOException: Timed out waiting for stdio MCP response说明子进程已启动但未在timeout内返回对应id的响应。可能原因服务端未按 MCP stdio 规范输出客户端兼容新行帧与Content-Length帧两种读取方式若都不匹配则会持续空转直至超时服务端崩溃或 hang 住。Not connected to Stdio server所有工具/资源操作都要求先connect成功确认在调用listTools/callTool前已完成连接或连接失败后重新connect。client_type配置错误McpServerConfig的clientType默认是ssestdio 场景必须显式配置为stdio通过McpClients.normalizeClientType归一化后匹配。总结StdioClient是 openJiuwen agent-core-java 对接本地 MCP Server 的标准通道它通过McpServerConfig声明进程启动参数经JsonRpcProcessSession以新行分隔帧发送请求、以兼容Content-Length分帧的方式读取响应在握手阶段完成initialize与notifications/initialized随后提供listTools、callTool、getToolInfo以及资源类方法的完整工具调用链路同时通过显式/默认白名单机制严格限定可执行命令防止本地进程注入风险。无论是自行构造new StdioClient(config)直接使用还是通过McpClients.registerDefaults()走客户端注册表按stdio传输类型获取只要遵循serverPath/params的配置约定并满足命令白名单要求即可快速将本地 MCP 工具接入 Java Agent 的工具链。赞分享人工智能AI AgentAgent 框架工具调用Agent 工作流RAGMCP Clients强化学习【免费下载链接】agent-core-javaopenJiuwen agent core Java是 openJiuwen Core的 Java版本提供AI Agent开发、运行、调优与演进相关的全套SDK能力。项目地址https://gitcode.com/openJiuwen/agent-core-java点击查看免费下载相关推荐openjiuwen agent-core 五大 MCP 传输接入实战SSE、Stdio、Streamable HTTP、Playwright 与 OpenAPIopenjiuwen agent core 五大 MCP 传输接入实战SSE、Stdio、Streamable HTTP、Playwright 与 OpenA人工智能AI AgentAgent 框架大模型工具调用RAG提示工程强化学习AgentScope MCP客户端HTTP、SSE、StdIO传输AgentScope MCP客户端HTTP、SSE、StdIO传输 引言为什么需要多协议MCP客户端 在现代AI代理系统中模型上下文协议Model C人工智能大模型AI Agent多智能体Agent 框架Agent 编排工具调用RAGMCP 服务语音在 mcp-for-beginners 中构建 MCP TypeScript 客户端基于官方 SDK 的 stdio 传输与工具调用实战在 mcp for beginners 中构建 MCP TypeScript 客户端基于官方 SDK 的 stdio 传输与工具调用实战 本文围绕 mcp f教程文档人工智能创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑