Search by

ldkafka / yii3-mcp-server

ldkafka

Model Context Protocol (MCP) server framework for Yii3 applications - expose custom tools to AI assistants over stdio or Streamable HTTP

v1.6.0 2026-11-02 06:33 UTC

This package is auto-updated.

Last update: 2026-11-02 06:45:51 UTC


README

Build AI-powered tools for your Yii3 application with Model Context Protocol

License PHP Version Yii Version

A framework for building Model Context Protocol (MCP) servers in Yii3 applications. Enable AI assistants like GitHub Copilot, Claude Code and Claude Desktop to interact with your application through custom tools, over stdio (a local process) or Streamable HTTP (a remote endpoint).

What is MCP?

Model Context Protocol is an open protocol that enables AI assistants to interact with external tools and data sources. Think of it as an API for AI - instead of REST endpoints for humans, MCP provides a standardized way for AI to:

  • Query your database
  • Read project files
  • Execute commands
  • Analyze data
  • Interact with your application

Features

  • Framework, not an app - Integrate into any Yii3 project
  • Tool-based architecture - Build custom tools by implementing McpToolInterface
  • Simple API - One interface (McpToolInterface) is all you need to create powerful AI tools
  • Two transports - stdio for editors that spawn a local process, and Streamable HTTP (PSR-15 handler + bearer-token middleware) for remote clients; both serve the same McpServer
  • Spec-compliant core, every protocol version - MCP 2026-07-28 (stateless: server/discover, per-request _meta, resultType, cache hints, Mcp-Method/Mcp-Name header checks) side by side with the initialize-based 2024-11-05 to 2025-11-25; tools/list with title, annotations, _meta and outputSchema, tools/call with isError results, JSON-RPC error codes. Modern answers are tested against the official 2026-07-28 JSON schema
  • Argument validation - tools/call arguments are checked against the tool's inputSchema (required, enum, types, ranges; lenient about numeric strings) and clear mismatches are answered with -32602 listing the violations, before the tool runs
  • Named tokens and per-caller scopes - bearer tokens can be keyed by caller name; limit each caller to a list of tools, and let tools that implement McpCallerAwareInterface know who calls
  • Observable - every tool call is logged with its caller, duration and result size; failures with their exception
  • PSR everywhere - PSR-3 logging, PSR-7/15/17 HTTP; no framework lock-in: the console command is optional, and the core runs in any framework (Yii2 included) through McpServer::dispatchPayload()
  • OAuth-ready - BearerChallenge builds the WWW-Authenticate values of an OAuth-protected endpoint (401 with resource_metadata, 403 insufficient_scope)
  • Type-safe - Full PHP 8.1+ type declarations and PHPDoc
  • Production-ready - Security patterns, error handling, validation
  • Tested - PHPUnit suite, CI on PHP 8.1 to 8.4
  • Easy integration - Works with VS Code, Claude Code, Cursor, and other MCP clients
  • Well-documented - Comprehensive guides and examples

Quick Start

1. Install via Composer

composer require ldkafka/yii3-mcp-server

The stdio command (mcp:serve) needs symfony/console, and yiisoft/yii-console to run it in a Yii3 console application. Yii3 application templates already include both; otherwise:

composer require yiisoft/yii-console

2. Copy Configuration Templates

Configure based on your Yii3 app structure:

# For yiisoft/app template (uses config/console/ and config/common/di/):
cp vendor/ldkafka/yii3-mcp-server/config/commands.php config/console/commands.php
cp vendor/ldkafka/yii3-mcp-server/config/di/mcp-template.php config/common/di/mcp.php

# For custom Yii3 apps (uses config/ root level):
# cp vendor/ldkafka/yii3-mcp-server/config/commands.php config/commands.php
# cp vendor/ldkafka/yii3-mcp-server/config/di-console.php config/di-console.php

Note: The standard yiisoft/app template uses config/console/ for console commands and config/common/di/ for DI configuration.

Or manually edit config/console/commands.php (for yiisoft/app template):

use YiiMcp\McpServer\Command\McpCommand;

return [
    'hello' => Console\HelloCommand::class,
    'mcp:serve' => McpCommand::class, // Add this line
];

3. Configure DI Container

Create config/common/di/mcp.php (for yiisoft/app template):

<?php

declare(strict_types=1);

use YiiMcp\McpServer\McpServer;

return [
    McpServer::class => static fn() => new McpServer([
        // Add your tool instances here
        // new MyCustomTool(),
    ]),
];

For custom tool registration, optionally create config/di-console.php to define tool dependencies.

4. Install Optional Dependencies (If Using Database Tools)

composer require yiisoft/db-mysql yiisoft/cache-file

5. Configure Database (Optional - For MySQL Query Tool)

Create config/environments/dev/params.local.php:

<?php

declare(strict_types=1);

return [
    'mcp' => [
        'db' => [
            'dsn' => 'mysql:host=localhost;dbname=your_database',
            'username' => 'your_username',
            'password' => 'your_password',
        ],
    ],
];

Then edit config/environments/dev/params.php to load it:

<?php

declare(strict_types=1);

$params = [];

// Load local params if exists (gitignored credentials)
$localParams = __DIR__ . '/params.local.php';
if (file_exists($localParams)) {
    $params = array_merge($params, require $localParams);
}

return $params;

Add to .gitignore:

/config/environments/*/params.local.php

6. Set APP_ENV (Yii3 Framework Requirement)

Create .env in project root:

APP_ENV=dev
APP_DEBUG=1

Or for Docker environments, add fallback to yii entry point (before autoload):

#!/usr/bin/env php
<?php

declare(strict_types=1);

// Default to 'dev' if APP_ENV not set (for Docker environments)
if (empty($_ENV['APP_ENV']) && empty(getenv('APP_ENV'))) {
    putenv('APP_ENV=dev');
    $_ENV['APP_ENV'] = 'dev';
}

7. Rebuild Config Cache

composer yii-config-rebuild

8. Verify Installation

php yii list

You should see mcp:serve in the command list.

9. Run the Server

php yii mcp:serve

10. Create Your Own Tools

The power of this framework is in creating custom tools by implementing McpToolInterface:

use YiiMcp\McpServer\Contract\McpToolInterface;

class MyCustomTool implements McpToolInterface
{
    public function getName(): string
    {
        return 'my_custom_tool';
    }

    public function getDescription(): string
    {
        return 'Does something useful for AI assistants';
    }

    public function getInputSchema(): array
    {
        return [
            'type' => 'object',
            'properties' => [
                'input' => ['type' => 'string', 'description' => 'Input parameter'],
            ],
            'required' => ['input'],
        ];
    }

    public function execute(array $arguments): array
    {
        // Your tool logic here. Return MCP content; throw on failure and the server
        // reports it to the assistant as an `isError` result.
        return [
            'content' => [
                ['type' => 'text', 'text' => 'Received: ' . $arguments['input']],
            ],
        ];
    }
}

Then register your tool in config/common/di/mcp.php:

return [
    McpServer::class => static fn() => new McpServer([
        new MyCustomTool(),
    ]),
];

See docs/CREATING_TOOLS.md for detailed guide.

11. Integrate with Your Editor

Add to VS Code settings.json:

{
  "github.copilot.chat.mcp.servers": {
    "my-yii3-app": {
      "command": "php",
      "args": ["yii", "mcp:serve"],
      "cwd": "/path/to/your/project"
    }
  }
}

Or, for Claude Code, add to the project's .mcp.json:

{
  "mcpServers": {
    "my-yii3-app": {
      "command": "php",
      "args": ["yii", "mcp:serve"]
    }
  }
}

That's it! Your AI assistant can now use your tools.

12. Optional: Serve Over HTTP

The same server can be exposed as a remote endpoint (POST /mcp) protected by a bearer token, so assistants on other machines can use your tools without a local PHP process. See docs/HTTP_TRANSPORT.md. It also shows how to call the core from a framework without PSR-7 (a Yii2 controller) and how to answer OAuth-protected requests with BearerChallenge.

Example: Database Query Tool

The package includes MysqlQueryTool as a reference implementation:

use YiiMcp\McpServer\Contract\McpToolInterface;
use Yiisoft\Db\Connection\ConnectionInterface;

class MysqlQueryTool implements McpToolInterface
{
    public function __construct(private ConnectionInterface $db) {}
    
    public function getName(): string {
        return 'query_database';
    }
    
    public function getDescription(): string {
        return 'Execute read-only SQL queries';
    }
    
    public function getInputSchema(): array {
        return [
            'type' => 'object',
            'properties' => [
                'sql' => ['type' => 'string', 'description' => 'SQL SELECT query']
            ],
            'required' => ['sql']
        ];
    }
    
    public function execute(array $args): array {
        // Validate, execute, return results
    }
}

Ask Copilot: "What tables are in the database?" and it will use this tool automatically!

Creating Custom Tools

Implement McpToolInterface:

use YiiMcp\McpServer\Contract\McpToolInterface;

class MyCustomTool implements McpToolInterface
{
    public function getName(): string {
        return 'my_tool';
    }
    
    public function getDescription(): string {
        return 'What this tool does for the AI';
    }
    
    public function getInputSchema(): array {
        return [
            'type' => 'object',
            'properties' => [
                'param' => ['type' => 'string']
            ]
        ];
    }
    
    public function execute(array $args): array {
        // Your logic here
        return [
            'content' => [
                ['type' => 'text', 'text' => 'Result data']
            ]
        ];
    }
}

Register in DI:

McpServer::class => [
    '__construct()' => [
        'tools' => [
            Reference::to(MyCustomTool::class),
        ],
    ],
],

Documentation

πŸ“– Editor Integration - VS Code, Claude Code, Docker, WSL, SSH setup
πŸ“– Creating Tools - Build custom tools guide
πŸ“– Installation - Detailed setup instructions
πŸ“– HTTP Transport - Remote endpoint, authentication, reverse proxy, client config
πŸ“– Changelog - Release notes

Deployment Scenarios

Local Development

{
  "command": "php",
  "args": ["yii", "mcp:serve"],
  "cwd": "${workspaceFolder}"
}

Docker

{
  "command": "docker",
  "args": ["exec", "-i", "app-container", "php", "yii", "mcp:serve"]
}

WSL (Windows)

{
  "command": "wsl",
  "args": ["bash", "-c", "cd /mnt/c/project && php yii mcp:serve"]
}

Remote (HTTP)

{
  "type": "http",
  "url": "https://example.test/mcp",
  "headers": { "Authorization": "Bearer ${MCP_TOKEN}" }
}

See EDITOR_INTEGRATION.md for more scenarios and HTTP_TRANSPORT.md for serving the endpoint.

Security

Read-Only by Default: The example MysqlQueryTool enforces read-only operations:

// Only allows: SELECT, SHOW, DESCRIBE, EXPLAIN, CHECKSUM TABLE
MysqlQueryTool::isReadOnlyStatement($sql); // matches MysqlQueryTool::READ_ONLY_STATEMENT

Recommendations:

  • Use dedicated read-only database users
  • Store credentials in params-local.php (gitignored)
  • Validate all tool inputs
  • Log tool usage for auditing

Requirements

  • PHP: 8.1 or higher (ext-json)
  • Console (optional): symfony/console for the stdio command, and yiisoft/yii-console ^2.0 to run it in Yii3
  • HTTP: any PSR-15 stack for McpHttpHandler, or any framework through McpServer::dispatchPayload()
  • PSR interfaces: psr/log, psr/http-message, psr/http-factory, psr/http-server-handler, psr/http-server-middleware
  • Optional: yiisoft/db + yiisoft/db-mysql for the database tool, yiisoft/router to route the HTTP endpoint

Protocol versions

Version How a client uses it What the server answers
2026-07-28 (modern) No handshake. Every request carries params._meta["io.modelcontextprotocol/protocolVersion"] and .../clientCapabilities; over HTTP also the MCP-Protocol-Version, Mcp-Method and (for tools/call) Mcp-Name headers, which must match the body server/discover, tools/list, tools/call. Every result has resultType: "complete" and the server's identity in _meta["io.modelcontextprotocol/serverInfo"]; server/discover and tools/list carry ttlMs and cacheScope. ping, logging/setLevel and initialize are unknown methods (-32601, HTTP 404)
2025-11-25, 2025-06-18, 2025-03-26, 2024-11-05 (legacy) initialize first, then MCP-Protocol-Version on later HTTP requests Exactly as in 1.5.0: initialize, ping, tools/list, tools/call

How a version is chosen: per request. A request whose params._meta names a modern version is served in it; any other request is legacy and served in the version agreed on initialize (the client's, when it is a legacy version this server speaks, otherwise 2025-11-25). A version the server does not speak, in the header or in _meta, is refused with -32022 (UnsupportedProtocolVersion, HTTP 400) listing ProtocolVersion::SUPPORTED; a modern request without its client capabilities gets -32602. Header rules and HTTP statuses: docs/HTTP_TRANSPORT.md.

Cache hints: new McpServer(..., cacheTtlMs: 0, cacheScope: McpServer::CACHE_PRIVATE) are the defaults: clients re-fetch whenever needed and never share a tool list across callers, which is right when tools vary per caller (scopes, caller-aware tools). A server whose list is the same for everyone may set a TTL and McpServer::CACHE_PUBLIC.

Architecture

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”           β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ AI Assistant β”‚           β”‚ AI Assistant β”‚   GitHub Copilot, Claude Code, Claude Desktop, ...
β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜           β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜
       β”‚ JSON-RPC over stdio     β”‚ JSON-RPC over HTTPS (bearer token)
       β–Ό                         β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”          β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚StdioTransportβ”‚          β”‚ McpHttpHandler β”‚   PSR-15, behind BearerTokenMiddleware
β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜          β””β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜
       β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                    β–Ό
          β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
          β”‚   McpServer     β”‚   Framework core: tool registry + JSON-RPC dispatch
          β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                   β”‚
      β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
      β–Ό            β–Ό          β–Ό             β–Ό
 β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
 β”‚MySQL     β”‚ β”‚File      β”‚ β”‚Cache   β”‚ β”‚Custom      β”‚
 β”‚Query Toolβ”‚ β”‚Read Tool β”‚ β”‚Tool    β”‚ β”‚Tools       β”‚
 β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Testing

composer install
composer test   # PHPUnit

The suite covers the JSON-RPC dispatcher, both transports and the authentication middleware; CI runs it on PHP 8.1 to 8.4.

Contributing

Contributions are welcome! Please:

  1. Fork the repository
  2. Create a feature branch
  3. Add tests for new features
  4. Submit a pull request

License

This project is licensed under the BSD-3-Clause License.

Support

Credits

Built with ❀️ for the Yii3 community.

Ready to empower your AI assistant? Install now and start building custom tools!

composer require ldkafka/yii3-mcp-server