首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >PHP MCP 客户端 v1.0.0 发布!

PHP MCP 客户端 v1.0.0 发布!

作者头像
Tinywan
发布2025-05-08 16:02:21
发布2025-05-08 16:02:21
6660
举报
文章被收录于专栏:开源技术小栈开源技术小栈

简介

PHP MCP Client是一个PHP库,用于与实现Model Context Protocol (MCP)的服务器进行交互。它为开发者提供了友好的接口,可通过不同的传输方式(stdiohttp+sse)连接到单个MCP服务器,管理连接生命周期,发现服务器功能(工具、资源、提示),并执行诸如调用工具或读取资源等请求。

该库内部通过ReactPHP利用异步I/O来确保健壮性并处理服务器发送事件等功能,同时提供了简单的同步(阻塞)API用于常见用例,以及异步(基于Promise)API用于高级控制和并发操作。它遵循MCP规范模型,即一个客户端实例管理与一个服务器的有状态连接。

MCP介绍

Model Context Protocol (MCP)是一个开放标准,旨在规范AI助手和应用程序连接到外部数据源、API和工具(如代码库、数据库、网页浏览器)的方式。它作为一个通信层,使AI模型(如Claude,或通过OpenAI等框架集成的模型)能够安全地访问不同服务器提供的上下文并与之交互。这个客户端库允许你的PHP应用程序(在MCP术语中称为“主机”)使用一个或多个MCP服务器提供的功能。

特性

  • 客户端-服务器一对一模型:每个Client实例管理与单个配置好的MCP服务器的有状态连接,符合MCP规范。
  • 流畅的配置方式:使用Client::make()->with...()构建器模式轻松设置每个客户端实例。
  • 双API设计
    • 同步门面API:使用简单的阻塞方法(如client->listTools()、
    • 异步API:访问底层基于Promise的方法(如client->listToolsAsync()、
  • 多种传输方式支持
    • stdio:通过标准输入/输出与服务器进程通信。
    • http:通过HTTP POST和服务器发送事件(SSE)与服务器通信。
  • 明确的连接生命周期管理:需要调用->initialize()->initializeAsync()连接并执行握手操作后才能发出请求。提供disconnect() / disconnectAsync()方法断开连接。
  • 工具/资源/提示交互功能:提供全面的同步和异步方法,用于列出可用元素并执行tools/callresources/readprompts/get等请求。
  • 符合PSR标准:与标准PHP接口集成
    • PSR-3(LoggerInterface):集成应用程序的日志记录器。
    • PSR-16(SimpleCacheInterface):可选的服务器定义缓存。
    • PSR-14(EventDispatcherInterface):通过事件(需要异步处理)可选地处理服务器发送的通知。
  • 强大的错误处理机制:针对不同的失败模式提供特定的异常。
  • 异步核心:内部使用ReactPHP实现非阻塞I/O。

要求

  • PHP >= 8.1
  • Composer
  • (对于Stdio传输)能够执行服务器命令。
  • (对于Http传输)能够访问MCP服务器URL的网络权限。

安装

通过Composer安装该包:

代码语言:javascript
复制
composer require php-mcp/client

必要的ReactPHP依赖(event-looppromisestreamchild-processhttp)会自动安装。

快速上手:简单同步使用(Stdio)

这个示例展示了如何连接到通过npx运行的本地文件系统服务器。

代码语言:javascript
复制
<?php 
require'vendor/autoload.php';

usePhpMcp\Client\Client;
usePhpMcp\Client\Enum\TransportType;
usePhpMcp\Client\Model\CapabilitiesasClientCapabilities;
usePhpMcp\Client\ServerConfig;
usePhpMcp\Client\Exception\McpClientException;

$clientCapabilities = ClientCapabilities::forClient(); // 默认客户端功能
$userHome = $_SERVER['HOME'] ?? $_SERVER['USERPROFILE'] ?? getcwd();
$fsServerConfig = new ServerConfig(
    name: 'local_filesystem',
    transport: TransportType::Stdio,
    timeout: 15,
    command: 'npx',
    args: [
        '-y',
        '@modelcontextprotocol/server-filesystem',
        $userHome . '/Documents',
    ],
    workingDir: $userHome
);

$fsClient = Client::make()
    ->withClientInfo('MyFileSystemApp', '1.0')
    ->withCapabilities($clientCapabilities)
    // ->withLogger(new MyPsrLogger()) // 可选
    ->withServerConfig($fsServerConfig)
    ->build();

try {
    // 初始化连接(阻塞)
    $fsClient->initialize();

    // 同步交互
    $tools = $fsClient->listTools(); // 阻塞调用
    foreach ($tools as $tool) {
        echo"- Tool: {$tool->name}\n";
    }

    //... 调用其他方法,如 $fsClient->callTool(...)...

} catch (McpClientException $e) {
    echo"[MCP ERROR] ". get_class($e). ": ". $e->getMessage(). "\n";
    // 检查 $e->getPrevious() 获取底层传输/进程错误
} catch (\Throwable $e) {
    echo"[UNEXPECTED ERROR] ". $e->getMessage(). "\n";
} finally {
    // 断开连接(阻塞)
    if (isset($fsClient)) {
        $fsClient->disconnect();
    }
}

配置

配置包括以下几个方面:

  • 客户端标识:应用程序的名称和版本,直接传递给构建器。
  • 客户端功能:使用ClientCapabilities声明客户端支持的功能。
  • 服务器连接:使用ServerConfig配置客户端实例要连接的单个服务器的详细信息。
  • (可选)依赖项:日志记录器、缓存、事件分发器、事件循环。

ClientCapabilities

用于声明客户端支持的功能,使用静态工厂方法。

代码语言:javascript
复制
use PhpMcp\Client\Model\Capabilities as ClientCapabilities;

// 客户端支持从服务器采样请求
$clientCapabilities = ClientCapabilities::forClient(supportsSampling: true);

// 客户端不支持从服务器采样请求
$clientCapabilities = ClientCapabilities::forClient(supportsSampling: false);

// 待办事项:如果需要,添加对声明“roots”功能的支持

ServerConfig

定义如何连接到单个MCP服务器。

代码语言:javascript
复制
use PhpMcp\Client\Enum\TransportType;
usePhpMcp\Client\ServerConfig;

// 示例:Stdio服务器
$stdioConfig = new ServerConfig(
    name: 'local_file_server',       // 必需:此配置的唯一ID
    transport: TransportType::Stdio, // 必需:传输类型
    timeout: 15.0,                   // 可选:请求超时时间(秒)
    command: 'npx',                  // Stdio必需:可执行文件
    args: [                          // Stdio可选:参数数组
        '-y',
        '@modelcontextprotocol/server-filesystem',
        '/path/to/project'
    ],
    workingDir: '/path/to/project',  // Stdio可选:工作目录
    env: ['DEBUG' =>'mcp*']         // Stdio可选:环境变量
);

// 示例:HTTP服务器
$httpConfig = new ServerConfig(
    name:'remote_web_agent',        // 必需:唯一ID
    transport: TransportType::Http,  // 必需:传输类型
    timeout: 45.0,                   // 可选:请求超时时间
    url: 'http://localhost:8080/sse',// Http必需:SSE URL
    headers: [                       // Http可选:认证/自定义头
        'Authorization' => 'Bearer xyz789'
    ],
);

从数组/JSON加载配置

可以轻松解析存储在数组中的配置(例如从JSON文件或框架配置中获取)。

代码语言:javascript
复制
use PhpMcp\Client\ServerConfig;
usePhpMcp\Client\Exception\ConfigurationException;

$jsonConfig = '{
    "mcpServers": {
        "stdio_files": {
            "command": "php",
            "args": ["/app/mcp/file_server.php"],
            "timeout": 10
        },
        "http_api": {
            "url": "https://api.example.com/mcp/sse",
            "transport": "http",
            "headers": {"X-API-Key": "secret"}
        }
    }
}';

$decodedConfig = json_decode($jsonConfig, true)['mcpServers']?? [];

$serverConfigs = [];
foreach ($decodedConfig as $name => $data) {
    try {
        $serverConfigs[$name] = ServerConfig::fromArray($name, $data);
    } catch (ConfigurationException $e) {
        echo"Error parsing config for '{$name}': {$e->getMessage()}\n";
    }
}

// 现在 $serverConfigs['stdio_files'] 和 $serverConfigs['http_api'] 包含ServerConfig对象。

ClientBuilder

使用构建器组装Client实例:

代码语言:javascript
复制
use PhpMcp\Client\Client;
//... 其他用于Config、Logger等的use语句...
$client = Client::make()
    ->withClientInfo($clientName, $clientVersion) // 必需
    ->withCapabilities($clientCapabilities)       // 可选(提供默认值)
    ->withServerConfig($stdioConfig)              // 必需:服务器配置
    ->withLogger($myLogger)                       // 可选
    ->withCache($myCache, 3600)                   // 可选(缓存 + 生存时间)
    ->withEventDispatcher($myDispatcher)          // 可选
    ->withIdGenerator($myIdGenerator)             // 可选
    ->withLoop($myEventLoop)                      // 可选(默认为Loop::get())
    ->build();

使用方法

一旦为特定服务器配置好了Client实例:

1. 初始化连接

在发出请求之前,必须调用initialize()initializeAsync()

代码语言:javascript
复制
// 同步(阻塞)
try {
    $client->initialize(); // 连接、执行握手操作,等待直到准备就绪
    echo"Connection Ready!";
} catch (Throwable $e) {
    echo"Initialization failed: ". $e->getMessage();
    // 处理错误... 客户端可能处于错误状态
}

// 异步(基于Promise)
$client->initializeAsync()->then(
    function(Client $readyClient) { /* 准备就绪 */ },
    function(Throwable $error) { /* 处理初始化失败 */ }
);
// 需要运行事件循环 ($client->getLoop()->run())

2. 发出请求

使用客户端方法,这些方法基于initialize()建立的单个连接进行操作。

  • 同步API(推荐用于简单脚本/框架):像listTools()callTool()readResource()等方法会阻塞执行,直到收到响应或超时。它们返回解析后的结果对象(如array<ToolDefinition>CallToolResult),或者抛出异常(TimeoutExceptionRequestExceptionConnectionException等)。
代码语言:javascript
复制
try {
    if ($client->isReady()) { // 检查状态
        $tools = $client->listTools();
        $result = $client->callTool('myTool', ['param' => 'value']);
    }
} catch (Throwable $e) { /* 处理错误 */ }
  • 异步API(用于异步应用或并发请求):像listToolsAsync()callToolAsync()readResourceAsync()等方法返回一个React\Promise\PromiseInterface。需要使用Promise方法(thencatchfinally)或React\Async\await(在Fiber上下文中)来处理结果。并且需要运行事件循环。
代码语言:javascript
复制
use functionReact\Promise\all;

if ($client->isReady()) {
    $p1 = $client->listToolsAsync();
    $p2 = $client->readResourceAsync('config://settings');

    all([$p1, $p2])->then(
        function(array $results) {
            [$tools, $readResult] = $results;
            // 处理异步结果...
        },
        function(Throwable $error) {
            // 处理异步错误...
        }
    );
    // $client->getLoop()->run(); // 需要运行循环
}

3. 断开连接

与服务器交互完成后,始终要断开连接以释放资源(特别是对于stdio传输)。

代码语言:javascript
复制
// 同步
$client->disconnect(); // 阻塞直到连接关闭或超时

// 异步
$client->disconnectAsync()->then(function() { echo "Disconnected async"; });
// $loop->run();

可用的客户端方法

Client类提供了与连接的MCP服务器交互的方法。大多数方法都有同步(阻塞)和异步(返回Promise)两种变体。

连接与生命周期

  • (同步)initialize(): self:连接到服务器并执行MCP握手操作。阻塞直到准备就绪或抛出异常。返回客户端实例。
  • (异步)initializeAsync(): PromiseInterface<Client>:异步启动连接和握手操作。返回一个Promise,在连接准备就绪时解析为客户端实例,失败时拒绝。
  • (同步)disconnect(): void:优雅地关闭连接。阻塞直到断开连接完成或超时。
  • (异步)disconnectAsync(): PromiseInterface<void>:异步启动优雅断开连接操作。返回一个Promise,在断开连接完成时解析。
  • getStatus(): ConnectionStatus:返回当前连接状态枚举(DisconnectedConnectingHandshakingReadyClosingClosedError)。
  • isReady(): bool:辅助方法,如果状态为Ready则返回true
  • getServerName():?string:返回服务器名称(成功初始化后可用)。
  • getServerVersion():?string:返回服务器版本(成功初始化后可用)。
  • getNegotiatedCapabilities():?Capabilities:返回与服务器协商的功能(成功初始化后可用)。
  • getNegotiatedProtocolVersion():?string:返回与服务器商定的协议版本(成功初始化后可用)。

MCP操作(同步)

(这些方法要求客户端先初始化,并且会阻塞)

  • ping(): void
  • listTools(bool $useCache = true): array<ToolDefinition>
  • listResources(bool $useCache = true): array<ResourceDefinition>
  • listPrompts(bool $useCache = true): array<PromptDefinition>
  • listResourceTemplates(bool $useCache = true): array<ResourceTemplateDefinition>
  • callTool(string toolName, array arguments = []): CallToolResult
  • readResource(string $uri): ReadResourceResult
  • getPrompt(string promptName, array arguments = []): GetPromptResult
  • subscribeResource(string $uri): void
  • unsubscribeResource(string $uri): void
  • setLogLevel(string $level): void

MCP操作(异步)

(这些方法要求客户端先初始化,并返回React\Promise\PromiseInterface

  • pingAsync(): PromiseInterface<void>
  • listToolsAsync(): PromiseInterface<array<ToolDefinition>>
  • listResourcesAsync(): PromiseInterface<array<ResourceDefinition>>
  • listPromptsAsync(): PromiseInterface<array<PromptDefinition>>
  • listResourceTemplatesAsync(): PromiseInterface<array<ResourceTemplateDefinition>>
  • callToolAsync(string toolName, array arguments = []): PromiseInterface<CallToolResult>
  • readResourceAsync(string $uri): PromiseInterface<ReadResourceResult>
  • getPromptAsync(string promptName, array arguments = []): PromiseInterface<GetPromptResult>
  • subscribeResourceAsync(string $uri): PromiseInterface<void>
  • unsubscribeResourceAsync(string $uri): PromiseInterface<void>
  • setLogLevelAsync(string $level): PromiseInterface<void>
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2025-05-07,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 开源技术小栈 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 简介
  • MCP介绍
  • 特性
  • 要求
  • 安装
  • 快速上手:简单同步使用(Stdio)
  • 配置
    • ClientCapabilities
    • ServerConfig
    • 从数组/JSON加载配置
    • ClientBuilder
  • 使用方法
    • 1. 初始化连接
    • 2. 发出请求
    • 3. 断开连接
  • 可用的客户端方法
    • 连接与生命周期
    • MCP操作(同步)
    • MCP操作(异步)
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档