Skip to content
Open
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Klavis Strata MCP API Key
# Get your API key from: https://strata.klavis.ai/
KLAVIS_STRATA_API_KEY=your-api-key-here
27 changes: 27 additions & 0 deletions config/api.php
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,33 @@

],

/*
|--------------------------------------------------------------------------
| MCP Transports
|--------------------------------------------------------------------------
|
| Model Context Protocol (MCP) transports configuration. Define your
| MCP servers and their connection settings here. Supported transport
| types include 'http' for HTTP/HTTPS connections.
|
*/

'mcp' => [
'transports' => [
'klavis-strata' => [
'type' => 'http',
'url' => 'https://strata.klavis.ai/mcp/?strata_id=3befb976-1fc9-4ff0-9e87-a173b12657c6',
'options' => [
'timeout' => 30,
'verify' => true,
'headers' => [
'Authorization' => 'Bearer ' . env('KLAVIS_STRATA_API_KEY'),
],
],
],
],
],

/*
|--------------------------------------------------------------------------
| Response Transformer
Expand Down
37 changes: 37 additions & 0 deletions src/Contract/Mcp/Transport.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?php

namespace Dingo\Api\Contract\Mcp;

interface Transport
{
/**
* Connect to the MCP server and establish a session.
*
* @return mixed
*/
public function connect();

/**
* Send a request to the MCP server.
*
* @param string $method
* @param array $params
*
* @return mixed
*/
public function send($method, array $params = []);

/**
* Close the connection to the MCP server.
*
* @return void
*/
public function disconnect();

/**
* Check if the transport is connected.
*
* @return bool
*/
public function isConnected();
}
18 changes: 18 additions & 0 deletions src/Facade/MCP.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

namespace Dingo\Api\Facade;

use Illuminate\Support\Facades\Facade;

class MCP extends Facade
{
/**
* Get the registered name of the component.
*
* @return string
*/
protected static function getFacadeAccessor()
{
return 'api.mcp';
}
}
82 changes: 82 additions & 0 deletions src/Mcp/Mcp.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
<?php

namespace Dingo\Api\Mcp;

use Illuminate\Container\Container;

class Mcp
{
/**
* Illuminate container instance.
*
* @var \Illuminate\Container\Container
*/
protected $container;

/**
* Array of available MCP transports.
*
* @var array
*/
protected $transports = [];

/**
* Create a new MCP instance.
*
* @param \Illuminate\Container\Container $container
* @param array $transports
*
* @return void
*/
public function __construct(Container $container, array $transports = [])
{
$this->container = $container;
$this->transports = $transports;
}

/**
* Get a transport by name.
*
* @param string $name
*
* @throws \InvalidArgumentException
*
* @return \Dingo\Api\Contract\Mcp\Transport
*/
public function transport($name)
{
if (!isset($this->transports[$name])) {
throw new \InvalidArgumentException("MCP transport [{$name}] is not registered.");
}

return $this->transports[$name];
}

/**
* Get all registered transports.
*
* @return array
*/
public function getTransports()
{
return $this->transports;
}

/**
* Register a new transport.
*
* @param string $name
* @param \Dingo\Api\Contract\Mcp\Transport|callable $transport
* Either a transport instance or a callable that returns a transport.
* If a callable is provided, it will be invoked with the container.
*
* @return void
*/
public function registerTransport($name, $transport)
{
if (is_callable($transport)) {
$transport = call_user_func($transport, $this->container);
}
$this->transports[$name] = $transport;
}
}
179 changes: 179 additions & 0 deletions src/Mcp/Transport/HttpTransport.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
<?php

namespace Dingo\Api\Mcp\Transport;

use Dingo\Api\Contract\Mcp\Transport;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\GuzzleException;

class HttpTransport implements Transport
{
/**
* The HTTP client instance.
*
* @var \GuzzleHttp\Client
*/
protected $client;

/**
* The MCP server URL.
*
* @var string
*/
protected $url;

/**
* The transport name/identifier.
*
* @var string
*/
protected $name;

/**
* Connection state.
*
* @var bool
*/
protected $connected = false;

/**
* Additional headers for requests.
*
* @var array
*/
protected $headers = [];

/**
* Create a new HTTP transport instance.
*
* @param string $name
* @param string $url
* @param array $options
*/
public function __construct($name, $url, array $options = [])
{
$this->name = $name;
$this->url = $url;
$this->headers = $options['headers'] ?? [];

$this->client = new Client([
'base_uri' => $url,
'timeout' => $options['timeout'] ?? 30,
'verify' => $options['verify'] ?? true,
]);
}

/**
* Connect to the MCP server and establish a session.
*
* @return mixed
*/
public function connect()
{
try {
// Test connection with a ping or initialization request
$response = $this->client->get('', [
'headers' => array_merge([
'Accept' => 'application/json',
'Content-Type' => 'application/json',
], $this->headers),
]);

$this->connected = $response->getStatusCode() === 200;

return $this->connected;
} catch (GuzzleException $e) {
$this->connected = false;
throw new \RuntimeException(
"Failed to connect to MCP server [{$this->name}] at {$this->url}: {$e->getMessage()}"
);
}
}

/**
* Send a request to the MCP server.
*
* @param string $method
* @param array $params
*
* @return mixed
*/
public function send($method, array $params = [])
{
if (!$this->connected) {
$this->connect();
}

try {
$response = $this->client->post('', [
'headers' => array_merge([
'Accept' => 'application/json',
'Content-Type' => 'application/json',
], $this->headers),
'json' => [
'jsonrpc' => '2.0',
'method' => $method,
'params' => $params,
'id' => uniqid('mcp_', true),
],
]);

$body = json_decode($response->getBody()->getContents(), true);
if (json_last_error() !== JSON_ERROR_NONE) {
throw new \RuntimeException('Invalid JSON response from MCP server');
}

if (isset($body['error'])) {
throw new \RuntimeException(
"MCP server error: {$body['error']['message']} (code: {$body['error']['code']})"
);
}

return $body['result'] ?? null;
} catch (GuzzleException $e) {
throw new \RuntimeException(
"Failed to send request to MCP server [{$this->name}]: {$e->getMessage()}"
);
}
}

/**
* Close the connection to the MCP server.
*
* @return void
*/
public function disconnect()
{
$this->connected = false;
}

/**
* Check if the transport is connected.
*
* @return bool
*/
public function isConnected()
{
return $this->connected;
}

/**
* Get the transport name.
*
* @return string
*/
public function getName()
{
return $this->name;
}

/**
* Get the transport URL.
*
* @return string
*/
public function getUrl()
{
return $this->url;
}
}
3 changes: 3 additions & 0 deletions src/Provider/DingoServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ public function register()

$this->app->register(HttpServiceProvider::class);

$this->app->register(McpServiceProvider::class);

$this->registerExceptionHandler();

$this->registerDispatcher();
Expand Down Expand Up @@ -102,6 +104,7 @@ protected function registerClassAliases()
'api.limiting' => \Dingo\Api\Http\RateLimit\Handler::class,
'api.transformer' => \Dingo\Api\Transformer\Factory::class,
'api.url' => \Dingo\Api\Routing\UrlGenerator::class,
'api.mcp' => \Dingo\Api\Mcp\Mcp::class,
'api.exception' => [\Dingo\Api\Exception\Handler::class, \Dingo\Api\Contract\Debug\ExceptionHandler::class],
];

Expand Down
Loading