refactor: susun semula struktur folder — Laravel source ke src/
This commit is contained in:
347
vendor/laravel/mcp/src/Server.php
vendored
Normal file
347
vendor/laravel/mcp/src/Server.php
vendored
Normal file
@@ -0,0 +1,347 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Laravel\Mcp;
|
||||
|
||||
use Illuminate\Container\Container;
|
||||
use Illuminate\Support\Str;
|
||||
use Laravel\Mcp\Events\SessionInitialized;
|
||||
use Laravel\Mcp\Server\AppResource;
|
||||
use Laravel\Mcp\Server\Attributes\Instructions;
|
||||
use Laravel\Mcp\Server\Attributes\Name;
|
||||
use Laravel\Mcp\Server\Attributes\Version;
|
||||
use Laravel\Mcp\Server\Concerns\ReadsAttributes;
|
||||
use Laravel\Mcp\Server\Contracts\Method;
|
||||
use Laravel\Mcp\Server\Contracts\Transport;
|
||||
use Laravel\Mcp\Server\Exceptions\JsonRpcException;
|
||||
use Laravel\Mcp\Server\Methods\CallTool;
|
||||
use Laravel\Mcp\Server\Methods\CompletionComplete;
|
||||
use Laravel\Mcp\Server\Methods\GetPrompt;
|
||||
use Laravel\Mcp\Server\Methods\Initialize;
|
||||
use Laravel\Mcp\Server\Methods\ListPrompts;
|
||||
use Laravel\Mcp\Server\Methods\ListResources;
|
||||
use Laravel\Mcp\Server\Methods\ListResourceTemplates;
|
||||
use Laravel\Mcp\Server\Methods\ListTools;
|
||||
use Laravel\Mcp\Server\Methods\Ping;
|
||||
use Laravel\Mcp\Server\Methods\ReadResource;
|
||||
use Laravel\Mcp\Server\Prompt;
|
||||
use Laravel\Mcp\Server\Resource;
|
||||
use Laravel\Mcp\Server\ServerContext;
|
||||
use Laravel\Mcp\Server\Testing\PendingTestResponse;
|
||||
use Laravel\Mcp\Server\Testing\TestResponse;
|
||||
use Laravel\Mcp\Server\Tool;
|
||||
use Laravel\Mcp\Server\Transport\JsonRpcNotification;
|
||||
use Laravel\Mcp\Server\Transport\JsonRpcRequest;
|
||||
use Laravel\Mcp\Server\Transport\JsonRpcResponse;
|
||||
use stdClass;
|
||||
use Throwable;
|
||||
|
||||
/**
|
||||
* @mixin PendingTestResponse
|
||||
*/
|
||||
abstract class Server
|
||||
{
|
||||
use ReadsAttributes;
|
||||
|
||||
public const CAPABILITY_TOOLS = 'tools';
|
||||
|
||||
public const CAPABILITY_RESOURCES = 'resources';
|
||||
|
||||
public const CAPABILITY_PROMPTS = 'prompts';
|
||||
|
||||
public const CAPABILITY_COMPLETIONS = 'completions';
|
||||
|
||||
public const CAPABILITY_UI = 'io.modelcontextprotocol/ui';
|
||||
|
||||
protected string $name = 'Laravel MCP Server';
|
||||
|
||||
protected string $version = '0.0.1';
|
||||
|
||||
protected string $instructions = <<<'MARKDOWN'
|
||||
This MCP server lets AI agents interact with our Laravel application.
|
||||
MARKDOWN;
|
||||
|
||||
/**
|
||||
* @var array<int, string>
|
||||
*/
|
||||
protected array $supportedProtocolVersion = [
|
||||
'2025-11-25',
|
||||
'2025-06-18',
|
||||
'2025-03-26',
|
||||
'2024-11-05',
|
||||
];
|
||||
|
||||
/**
|
||||
* @var array<string, array<string, bool>|stdClass|string>
|
||||
*/
|
||||
protected array $capabilities = [
|
||||
self::CAPABILITY_TOOLS => [
|
||||
'listChanged' => false,
|
||||
],
|
||||
self::CAPABILITY_RESOURCES => [
|
||||
'listChanged' => false,
|
||||
],
|
||||
self::CAPABILITY_PROMPTS => [
|
||||
'listChanged' => false,
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* @var array<int, Tool|class-string<Tool>>
|
||||
*/
|
||||
protected array $tools = [];
|
||||
|
||||
/**
|
||||
* @var array<int, Resource|class-string<Resource>>
|
||||
*/
|
||||
protected array $resources = [];
|
||||
|
||||
/**
|
||||
* @var array<int, Prompt|class-string<Prompt>>
|
||||
*/
|
||||
protected array $prompts = [];
|
||||
|
||||
public int $maxPaginationLength = 50;
|
||||
|
||||
public int $defaultPaginationLength = 15;
|
||||
|
||||
/**
|
||||
* @var array<string, class-string<Method>>
|
||||
*/
|
||||
protected array $methods = [
|
||||
'tools/list' => ListTools::class,
|
||||
'tools/call' => CallTool::class,
|
||||
'resources/list' => ListResources::class,
|
||||
'resources/read' => ReadResource::class,
|
||||
'resources/templates/list' => ListResourceTemplates::class,
|
||||
'prompts/list' => ListPrompts::class,
|
||||
'prompts/get' => GetPrompt::class,
|
||||
'completion/complete' => CompletionComplete::class,
|
||||
'ping' => Ping::class,
|
||||
];
|
||||
|
||||
public function __construct(
|
||||
protected Transport $transport,
|
||||
) {
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Add or modify a server capability.
|
||||
*
|
||||
* Using dot notation like "feature.enabled" will create a nested capability array.
|
||||
* Passing a single key like "anotherFeature" will register an empty object capability.
|
||||
*/
|
||||
public function addCapability(string $key, bool $value = true): void
|
||||
{
|
||||
if (str_contains($key, '.')) {
|
||||
[$root, $child] = explode('.', $key, 2);
|
||||
$existing = $this->capabilities[$root] ?? [];
|
||||
|
||||
if (! is_array($existing)) {
|
||||
$existing = [];
|
||||
}
|
||||
|
||||
$existing[$child] = $value;
|
||||
$this->capabilities[$root] = $existing;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Represent empty capability as an object when JSON encoded
|
||||
$this->capabilities[$key] = (object) [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a custom JSON-RPC method handler.
|
||||
*
|
||||
* @param class-string<Method> $handler
|
||||
*/
|
||||
public function addMethod(string $method, string $handler): void
|
||||
{
|
||||
$this->methods[$method] = $handler;
|
||||
}
|
||||
|
||||
public function start(): void
|
||||
{
|
||||
$this->boot();
|
||||
$this->detectUiCapability();
|
||||
|
||||
$this->transport->onReceive($this->handle(...));
|
||||
}
|
||||
|
||||
protected function boot(): void
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
public function handle(string $rawMessage): void
|
||||
{
|
||||
$context = $this->createContext();
|
||||
|
||||
try {
|
||||
$jsonRequest = json_decode($rawMessage, true);
|
||||
|
||||
if (json_last_error() !== JSON_ERROR_NONE) {
|
||||
throw new JsonRpcException('Parse error: Invalid JSON was received by the server.', -32700);
|
||||
}
|
||||
|
||||
$request = isset($jsonRequest['id'])
|
||||
? JsonRpcRequest::from($jsonRequest, $this->transport->sessionId())
|
||||
: JsonRpcNotification::from($jsonRequest);
|
||||
|
||||
if ($request instanceof JsonRpcNotification) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($request->method === 'initialize') {
|
||||
$this->handleInitializeMessage($request, $context);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (! isset($this->methods[$request->method])) {
|
||||
throw new JsonRpcException(
|
||||
"The method [{$request->method}] was not found.",
|
||||
-32601,
|
||||
$request->id,
|
||||
);
|
||||
}
|
||||
|
||||
$this->handleMessage($request, $context);
|
||||
} catch (JsonRpcException $e) {
|
||||
$this->transport->send($e->toJsonRpcResponse()->toJson());
|
||||
} catch (Throwable $e) {
|
||||
report($e);
|
||||
|
||||
$config = Container::getInstance()->make('config');
|
||||
|
||||
if ($config->get('app.debug', false)) {
|
||||
throw $e;
|
||||
}
|
||||
|
||||
$jsonRpcResponse = JsonRpcResponse::error(
|
||||
$request->id ?? null,
|
||||
-32603,
|
||||
'Something went wrong while processing the request.',
|
||||
);
|
||||
|
||||
$this->transport->send($jsonRpcResponse->toJson());
|
||||
}
|
||||
}
|
||||
|
||||
public function createContext(): ServerContext
|
||||
{
|
||||
$name = $this->resolveAttribute(Name::class);
|
||||
$version = $this->resolveAttribute(Version::class);
|
||||
$instructions = $this->resolveAttribute(Instructions::class);
|
||||
|
||||
return new ServerContext(
|
||||
supportedProtocolVersions: $this->supportedProtocolVersion,
|
||||
serverCapabilities: $this->capabilities,
|
||||
serverName: $name !== null ? $name->value : $this->name,
|
||||
serverVersion: $version !== null ? $version->value : $this->version,
|
||||
instructions: $instructions !== null ? $instructions->value : $this->instructions,
|
||||
maxPaginationLength: $this->maxPaginationLength,
|
||||
defaultPaginationLength: $this->defaultPaginationLength,
|
||||
tools: $this->tools,
|
||||
resources: $this->resources,
|
||||
prompts: $this->prompts,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws JsonRpcException
|
||||
*/
|
||||
protected function handleMessage(JsonRpcRequest $request, ServerContext $context): void
|
||||
{
|
||||
$response = $this->runMethodHandle($request, $context);
|
||||
|
||||
if (! is_iterable($response)) {
|
||||
$this->transport->send($response->toJson());
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->transport->stream(function () use ($response): void {
|
||||
foreach ($response as $message) {
|
||||
$this->transport->send($message->toJson());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @return iterable<JsonRpcResponse>|JsonRpcResponse
|
||||
*
|
||||
* @throws JsonRpcException
|
||||
*/
|
||||
protected function runMethodHandle(JsonRpcRequest $request, ServerContext $context): iterable|JsonRpcResponse
|
||||
{
|
||||
$container = Container::getInstance();
|
||||
|
||||
/** @var Method $methodClass */
|
||||
$methodClass = $container->make(
|
||||
$this->methods[$request->method],
|
||||
);
|
||||
|
||||
$container->instance('mcp.request', $request->toRequest());
|
||||
|
||||
try {
|
||||
$response = $methodClass->handle($request, $context);
|
||||
} finally {
|
||||
$container->forgetInstance('mcp.request');
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
protected function handleInitializeMessage(JsonRpcRequest $request, ServerContext $context): void
|
||||
{
|
||||
$response = (new Initialize)->handle($request, $context);
|
||||
|
||||
$sessionId = $this->generateSessionId();
|
||||
|
||||
Container::getInstance()->make('events')->dispatch(new SessionInitialized(
|
||||
sessionId: $sessionId,
|
||||
clientInfo: $request->params['clientInfo'] ?? null,
|
||||
protocolVersion: $request->params['protocolVersion'] ?? null,
|
||||
clientCapabilities: $request->params['capabilities'] ?? null,
|
||||
));
|
||||
|
||||
$this->transport->send($response->toJson(), $sessionId);
|
||||
}
|
||||
|
||||
protected function generateSessionId(): string
|
||||
{
|
||||
return Str::uuid()->toString();
|
||||
}
|
||||
|
||||
protected function detectUiCapability(): void
|
||||
{
|
||||
if (array_key_exists(self::CAPABILITY_UI, $this->capabilities)) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($this->resources as $resource) {
|
||||
if (is_subclass_of($resource, AppResource::class)) {
|
||||
$this->addCapability(self::CAPABILITY_UI);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<array-key, mixed> $arguments
|
||||
*/
|
||||
public static function __callStatic(string $name, array $arguments): PendingTestResponse|TestResponse
|
||||
{
|
||||
$pendingTestResponse = new PendingTestResponse(
|
||||
Container::getInstance(),
|
||||
static::class,
|
||||
);
|
||||
|
||||
return $pendingTestResponse->$name(...$arguments);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user