在 AI 大模型迅猛发展的当下,其影响力已广泛渗透至生活的方方面面。无论是智能客服的高效交互、内容创作的灵感激发,还是图像生成的创意呈现、数据分析的精准洞察,AI 大模型都展现出了令人瞩目的强大能力。然而,繁荣背后,AI 大模型正面临着一个棘手难题 —— 数据孤岛问题。这些模型如同被困孤岛的巨人,虽潜力无限,却因难以获取多元数据,致使应用严重受限。
以日常场景为例,当你向大模型询问明天的天气和个人日程安排时,它可能因无法获取实时天气数据与你的日程信息而无法作答。这是由于大模型与传统系统及信息流集成度低,难以突破数据瓶颈。每个新数据源都需定制化集成,这极大地阻碍了真正互联系统的扩展。在这个信息爆炸的时代,数据堪称 AI 大模型发展的 “石油”,是关键驱动力。但如今,数据孤岛却束缚了大模型,使其难以充分释放潜力。
那么,是否存在一种有效方法打破数据孤岛,让大模型自由获取所需数据,实现更强大功能呢?答案或许就在 MCP(Model Context Protocol,模型上下文协议)。MCP 的出现,宛如一座桥梁,有望连接大模型与外部数据源,开创全新的 AI 应用生态。接下来,让我们一同深入探究 MCP 的奥秘。
MCP(Model Context Protocol,模型上下文协议),2024年11月底,由 Anthropic 推出的一种开放标准,旨在统一大型语言模型(LLM)与外部数据源和工具之间的通信协议。MCP 的主要目的在于解决当前 AI 模型因数据孤岛限制而无法充分发挥潜力的难题,MCP 使得 AI 应用能够安全地访问和操作本地及远程数据,为 AI 应用提供了连接万物的接口。
开源技术小栈Function Calling是AI模型调用函数的机制,MCP是一个标准协议,使AI模型与API无缝交互,而AI Agent是一个自主运行的智能系统,利用Function Calling和MCP来分析和执行任务,实现特定目标。
开源技术小栈总共分为了下面五个部分:
此包提供了模型上下文协议的 PHP 实现,允许应用程序LLMs以标准化方式为其提供上下文。它将提供上下文的关注点与实际LLM交互分开。
此 PHP SDK 实现了完整的 MCP 规范,因此可以轻松
基于官方的Python SDK的模型上下文协议。此 SDK 主要面向致力于前沿 AI 集成解决方案的开发人员。某些功能可能不完整,在生产使用之前,应由经验丰富的开发人员对实现进行全面测试和安全审查。
composer require logiscape/mcp-sdk-php
以下是创建提供提示的 MCP 服务器的完整示例:example_server.php
<?php
// A basic example server with a list of prompts for testing
require'vendor/autoload.php';
useMcp\Server\Server;
useMcp\Server\ServerRunner;
useMcp\Types\Prompt;
useMcp\Types\PromptArgument;
useMcp\Types\PromptMessage;
useMcp\Types\ListPromptsResult;
useMcp\Types\TextContent;
useMcp\Types\Role;
useMcp\Types\GetPromptResult;
useMcp\Types\GetPromptRequestParams;
// Create a server instance
$server = new Server('example-server');
// Register prompt handlers
$server->registerHandler('prompts/list', function($params) {
$prompt = new Prompt(
name: 'example-prompt',
description: 'An example prompt template',
arguments: [
new PromptArgument(
name: 'arg1',
description: 'Example argument',
required: true
)
]
);
returnnew ListPromptsResult([$prompt]);
});
$server->registerHandler('prompts/get', function(GetPromptRequestParams $params) {
$name = $params->name;
$arguments = $params->arguments;
if ($name !== 'example-prompt') {
thrownew \InvalidArgumentException("Unknown prompt: {$name}");
}
// Get argument value safely
$argValue = $arguments ? $arguments->arg1 : 'none';
$prompt = new Prompt(
name: 'example-prompt',
description: 'An example prompt template',
arguments: [
new PromptArgument(
name: 'arg1',
description: 'Example argument',
required: true
)
]
);
returnnew GetPromptResult(
messages: [
new PromptMessage(
role: Role::USER,
content: new TextContent(
text: "Example prompt text with argument: $argValue"
)
)
],
description: 'Example prompt'
);
});
// Create initialization options and run server
$initOptions = $server->createInitializationOptions();
$runner = new ServerRunner($server, $initOptions);
$runner->run();
下面介绍如何创建连接到示例服务器的客户端:example_client.php
<?php
// A basic example client that connects to example_server.php and outputs the prompts
require 'vendor/autoload.php';
use Mcp\Client\Client;
use Mcp\Client\Transport\StdioServerParameters;
use Mcp\Types\TextContent;
// Create server parameters for stdio connection
$serverParams = new StdioServerParameters(
command: 'php', // Executable
args: ['example_server.php'], // File path to the server
env: null // Optional environment variables
);
// Create client instance
$client = new Client();
try {
echo("Starting to connect\n");
// Connect to the server using stdio transport
$session = $client->connect(
commandOrUrl: $serverParams->getCommand(),
args: $serverParams->getArgs(),
env: $serverParams->getEnv()
);
echo("Starting to get available prompts\n");
// List available prompts
$promptsResult = $session->listPrompts();
// Output the list of prompts
if (!empty($promptsResult->prompts)) {
echo"Available prompts:\n";
foreach ($promptsResult->prompts as $prompt) {
echo" - Name: " . $prompt->name . "\n";
echo" Description: " . $prompt->description . "\n";
echo" Arguments:\n";
if (!empty($prompt->arguments)) {
foreach ($prompt->arguments as $argument) {
echo" - " . $argument->name . " (" . ($argument->required ? "required" : "optional") . "): " . $argument->description . "\n";
}
} else {
echo" (None)\n";
}
}
} else {
echo"No prompts available.\n";
}
} catch (\Exception $e) {
echo"Error: " . $e->getMessage() . "\n";
exit(1);
} finally {
// Close the server connection
if (isset($client)) {
$client->close();
}
}
以下是前面启用了详细日志记录的 MCP 服务器示例:
<?php
// A version of the basic example server with extra logging
// Enable comprehensive error logging
ini_set('display_errors', 0);
ini_set('display_startup_errors', 0);
ini_set('log_errors', 1);
ini_set('error_log', __DIR__ . '/php_errors.log'); // Logs errors to a file
error_reporting(E_ALL);
require'vendor/autoload.php';
useMcp\Server\Server;
useMcp\Server\ServerRunner;
useMcp\Types\Prompt;
useMcp\Types\PromptArgument;
useMcp\Types\PromptMessage;
useMcp\Types\ListPromptsResult;
useMcp\Types\TextContent;
useMcp\Types\Role;
useMcp\Types\GetPromptResult;
useMcp\Types\GetPromptRequestParams;
useMonolog\Logger;
useMonolog\Handler\StreamHandler;
useMonolog\Handler\RotatingFileHandler;
useMonolog\Formatter\LineFormatter;
// Create a logger
$logger = new Logger('mcp-server');
// Delete previous log
@unlink(__DIR__ . '/server_log.txt');
// Create a handler that writes to server_log.txt
$handler = new StreamHandler(__DIR__ . '/server_log.txt', Logger::DEBUG);
// Optional: Create a custom formatter to make logs more readable
$dateFormat = "Y-m-d H:i:s";
$output = "[%datetime%] %channel%.%level_name%: %message% %context% %extra%\n";
$formatter = new LineFormatter($output, $dateFormat);
$handler->setFormatter($formatter);
// Add the handler to the logger
$logger->pushHandler($handler);
// Create a server instance
$server = new Server('example-server', $logger);
// Register prompt handlers
$server->registerHandler('prompts/list', function($params) {
$prompt = new Prompt(
name: 'example-prompt',
description: 'An example prompt template',
arguments: [
new PromptArgument(
name: 'arg1',
description: 'Example argument',
required: true
)
]
);
returnnew ListPromptsResult([$prompt]);
});
$server->registerHandler('prompts/get', function(GetPromptRequestParams $params) {
$name = $params->name;
$arguments = $params->arguments;
if ($name !== 'example-prompt') {
thrownew \InvalidArgumentException("Unknown prompt: {$name}");
}
// Get argument value safely
$argValue = $arguments ? $arguments->arg1 : 'none';
$prompt = new Prompt(
name: 'example-prompt',
description: 'An example prompt template',
arguments: [
new PromptArgument(
name: 'arg1',
description: 'Example argument',
required: true
)
]
);
returnnew GetPromptResult(
messages: [
new PromptMessage(
role: Role::USER,
content: new TextContent(
text: "Example prompt text with argument: $argValue"
)
)
],
description: 'Example prompt'
);
});
// Create initialization options and run server
$initOptions = $server->createInitializationOptions();
$runner = new ServerRunner($server, $initOptions, $logger);
try {
$runner->run();
} catch (\Throwable $e) {
echo"An error occurred: " . $e->getMessage() . "\n";
$logger->error("Server run failed", ['exception' => $e]);
}
以下是创建启用了详细日志记录的客户端的方法:
<?php
// A version of the basic example client with extra logging and output
// Enable comprehensive error reporting and logging
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
ini_set('log_errors', 1);
ini_set('error_log', __DIR__ . '/php_errors.log'); // Logs errors to a file
error_reporting(E_ALL);
require'vendor/autoload.php';
useMcp\Client\Client;
useMcp\Client\Transport\StdioServerParameters;
useMcp\Types\TextContent;
useMonolog\Logger;
useMonolog\Handler\StreamHandler;
useMonolog\Handler\RotatingFileHandler;
useMonolog\Formatter\LineFormatter;
// Create a logger
$logger = new Logger('mcp-client');
// Delete previous log
@unlink(__DIR__ . '/client_log.txt');
// Create a handler that writes to client_log.txt
$handler = new StreamHandler(__DIR__ . '/client_log.txt', Logger::DEBUG);
// Optional: Create a custom formatter to make logs more readable
$dateFormat = "Y-m-d H:i:s";
$output = "[%datetime%] %channel%.%level_name%: %message% %context% %extra%\n";
$formatter = new LineFormatter($output, $dateFormat);
$handler->setFormatter($formatter);
// Add the handler to the logger
$logger->pushHandler($handler);
// Create server parameters for stdio connection
$serverParams = new StdioServerParameters(
command: 'php', // Executable
args: ['debug_example_server.php'], // File path to the server
env: null// Optional environment variables
);
echo("Creating client\n");
// Create client instance
$client = new Client($logger);
try {
echo("Starting to connect\n");
// Connect to the server using stdio transport
$session = $client->connect(
commandOrUrl: $serverParams->getCommand(),
args: $serverParams->getArgs(),
env: $serverParams->getEnv()
);
echo("Starting to get available prompts\n");
// List available prompts
$promptsResult = $session->listPrompts();
// Output the list of prompts
if (!empty($promptsResult->prompts)) {
echo"Available prompts:\n";
foreach ($promptsResult->prompts as $prompt) {
echo" - Name: " . $prompt->name . "\n";
echo" Description: " . $prompt->description . "\n";
echo" Arguments:\n";
if (!empty($prompt->arguments)) {
foreach ($prompt->arguments as $argument) {
echo" - " . $argument->name . " (" . ($argument->required ? "required" : "optional") . "): " . $argument->description . "\n";
}
} else {
echo" (None)\n";
}
}
} else {
echo"No prompts available.\n";
}
} catch (\Exception $e) {
echo"Error: " . $e->getMessage() . "\n";
exit(1);
} finally {
// Close the server connection
if (isset($client)) {
$client->close();
}
}