mirror of
https://github.com/symfony/ai.git
synced 2026-03-23 23:42:18 +01:00
[Mate] Add AI Mate MCP integration with custom Symfony AI features capability
This commit is contained in:
committed by
Christopher Hertel
parent
efac2cac26
commit
e246bdd1d0
1
demo/.mcp.json
Symbolic link
1
demo/.mcp.json
Symbolic link
@@ -0,0 +1 @@
|
||||
mcp.json
|
||||
@@ -92,6 +92,97 @@ Each chat type follows the pattern:
|
||||
### Session Management
|
||||
Chat history stored in Symfony sessions with component-specific keys (e.g., 'blog-chat', 'stream-chat').
|
||||
|
||||
## Available MCP Tools
|
||||
|
||||
**IMPORTANT**: This project includes the Symfony AI Mate MCP server with powerful debugging and inspection tools. **USE THESE TOOLS PROACTIVELY** whenever working with Symfony AI features, logs, or system information.
|
||||
|
||||
### Symfony AI Inspection Tools
|
||||
|
||||
**`symfony-ai-features`** - **USE THIS FIRST** when analyzing or modifying AI configuration
|
||||
- Detects and lists all AI platforms, agents, tools, stores, vectorizers, indexers, retrievers, and multi-agent setups
|
||||
- Provides summary counts and detailed configuration information
|
||||
- Analyzes `config/packages/ai.yaml` and `composer.json`
|
||||
- Use when: Starting any AI-related task, debugging agent configuration, understanding project AI capabilities
|
||||
|
||||
```
|
||||
Example: Before modifying agents, call symfony-ai-features to understand current setup
|
||||
```
|
||||
|
||||
**`symfony-services`** - Get complete list of Symfony services
|
||||
- Lists all registered services in the DI container
|
||||
- Use when: Debugging dependency injection, finding service IDs, understanding available services
|
||||
|
||||
### Monolog/Logging Tools
|
||||
|
||||
**`monolog-search`** - Search logs by text term
|
||||
- Parameters: `term` (required), `level`, `channel`, `environment`, `from`, `to`, `limit` (default: 100)
|
||||
- Use when: Debugging errors, finding specific log messages, tracking events
|
||||
|
||||
**`monolog-search-regex`** - Search logs using regex patterns
|
||||
- Parameters: `pattern` (required), `level`, `channel`, `environment`, `limit`
|
||||
- Use when: Complex log searching, pattern matching, finding formatted data
|
||||
|
||||
**`monolog-context-search`** - Search logs by context field
|
||||
- Parameters: `key` (required), `value` (required), `level`, `environment`, `limit`
|
||||
- Use when: Finding logs with specific context data (user_id, request_id, etc.)
|
||||
|
||||
**`monolog-tail`** - Get last N log entries (like `tail -f`)
|
||||
- Parameters: `lines` (default: 50), `level`, `environment`
|
||||
- Use when: Checking recent activity, monitoring real-time logs, debugging current issues
|
||||
|
||||
**`monolog-list-files`** - List available log files
|
||||
- Parameters: `environment` (optional)
|
||||
- Use when: Finding log locations, checking available environments
|
||||
|
||||
**`monolog-list-channels`** - List all log channels
|
||||
- Use when: Understanding logging structure, finding channel names for filtering
|
||||
|
||||
**`monolog-by-level`** - Filter logs by severity level
|
||||
- Parameters: `level` (required: DEBUG, INFO, WARNING, ERROR, CRITICAL, etc.), `environment`, `limit`
|
||||
- Use when: Finding errors, warnings, or specific severity issues
|
||||
|
||||
### System Information Tools
|
||||
|
||||
**`php-version`** - Get PHP version
|
||||
- Use when: Checking compatibility, debugging version-specific issues
|
||||
|
||||
**`php-extensions`** - List installed PHP extensions
|
||||
- Use when: Verifying required extensions, debugging extension-related issues
|
||||
|
||||
**`operating-system`** - Get current OS
|
||||
- Use when: Platform-specific debugging, environment verification
|
||||
|
||||
**`operating-system-family`** - Get OS family (Windows, Linux, Darwin)
|
||||
- Use when: Cross-platform compatibility checks
|
||||
|
||||
### Tool Usage Guidelines
|
||||
|
||||
**Proactive Usage Patterns**:
|
||||
1. **Before modifying AI config**: Call `symfony-ai-features` to understand current setup
|
||||
2. **When debugging errors**: Use `monolog-tail` and `monolog-search` to find relevant logs
|
||||
3. **When analyzing agents**: Use `symfony-ai-features` to see all agents, tools, and configurations
|
||||
4. **When troubleshooting**: Combine log tools with system info tools for complete context
|
||||
5. **When adding new features**: Check existing services with `symfony-services`
|
||||
|
||||
**Example Workflows**:
|
||||
|
||||
```bash
|
||||
# Investigating AI agent issues
|
||||
1. symfony-ai-features (get agent configuration)
|
||||
2. monolog-search term:"agent" (find agent-related logs)
|
||||
3. monolog-by-level level:"ERROR" (check for errors)
|
||||
|
||||
# Debugging application errors
|
||||
1. monolog-tail lines:100 (recent activity)
|
||||
2. monolog-by-level level:"ERROR" (find errors)
|
||||
3. monolog-context-search key:"exception" (error details)
|
||||
|
||||
# Understanding project structure
|
||||
1. symfony-ai-features (AI capabilities)
|
||||
2. symfony-services (available services)
|
||||
3. php-extensions (installed extensions)
|
||||
```
|
||||
|
||||
## Development Notes
|
||||
|
||||
- Uses PHP 8.4+ with strict typing and modern PHP features
|
||||
|
||||
@@ -142,3 +142,72 @@ npx @modelcontextprotocol/inspector php bin/console mcp:server
|
||||
```
|
||||
|
||||
Which opens a web UI to interactively test the MCP server.
|
||||
|
||||
## AI Mate - MCP Development Assistant
|
||||
|
||||
[Symfony AI Mate](https://github.com/symfony/ai-mate) is an MCP (Model Context Protocol) server that provides AI
|
||||
assistants with Symfony-specific development capabilities.
|
||||
|
||||
### Installation & Setup
|
||||
|
||||
**This demo is already configured!** For new projects you can set up AI Mate as follows:
|
||||
|
||||
```shell
|
||||
# Install AI Mate
|
||||
composer require --dev symfony/ai-mate
|
||||
|
||||
# Initialize configuration
|
||||
vendor/bin/mate init
|
||||
|
||||
# Discover available tools
|
||||
vendor/bin/mate discover
|
||||
```
|
||||
|
||||
### MCP Client Configuration
|
||||
|
||||
The `mcp.json` file in the project root enables automatic MCP client detection:
|
||||
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"symfony-ai-mate": {
|
||||
"command": "./vendor/bin/mate",
|
||||
"args": ["serve"]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
For other projects, add AI Mate to your MCP client settings (e.g., `~/.claude/mcp.json`, IDE settings, etc.).
|
||||
|
||||
### Custom Capability Example
|
||||
|
||||
This demo includes a **`symfony-ai-features`** tool (see `mate/SymfonyAiFeaturesTool.php`) that analyzes the project's
|
||||
AI configuration and reports all available platforms, agents, tools, stores, and packages.
|
||||
|
||||
**Try it in your MCP-enabled chat:**
|
||||
|
||||
> "Which Symfony AI features are available in this demo?"
|
||||
>
|
||||
> "What AI agents are configured in this project?"
|
||||
>
|
||||
> "Show me all the Symfony AI tools and their configuration"
|
||||
>
|
||||
> "What is the current PHP version used in this project?"
|
||||
>
|
||||
> "Is the php extension intl installed?"
|
||||
|
||||
The AI assistant will use the `symfony-ai-features` and other MCP tool to provide detailed information about project
|
||||
internals.
|
||||
|
||||
### Creating Custom Tools
|
||||
|
||||
Create tools in `mate/src/` and register them in `mate/config.php`. See the
|
||||
[AI Mate documentation](https://symfony.com/doc/current/ai/components/mate.html) for detailed guides.
|
||||
|
||||
### Testing
|
||||
|
||||
```shell
|
||||
# Test with MCP Inspector
|
||||
npx @modelcontextprotocol/inspector ./vendor/bin/mate serve
|
||||
```
|
||||
|
||||
@@ -49,6 +49,9 @@
|
||||
"phpstan/phpstan": "^2.1.32",
|
||||
"phpstan/phpstan-strict-rules": "^2.0.7",
|
||||
"phpunit/phpunit": "^12.1",
|
||||
"symfony/ai-mate": "@dev",
|
||||
"symfony/ai-monolog-mate-extension": "@dev",
|
||||
"symfony/ai-symfony-mate-extension": "@dev",
|
||||
"symfony/browser-kit": "^8.0",
|
||||
"symfony/css-selector": "^8.0",
|
||||
"symfony/debug-bundle": "^8.0",
|
||||
@@ -80,7 +83,8 @@
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"App\\Tests\\": "tests/"
|
||||
"App\\Tests\\": "tests/",
|
||||
"App\\Mate\\": "mate/src/"
|
||||
}
|
||||
},
|
||||
"config": {
|
||||
@@ -95,6 +99,14 @@
|
||||
"symfony": {
|
||||
"allow-contrib": false,
|
||||
"require": "8.0.*"
|
||||
},
|
||||
"ai-mate": {
|
||||
"scan-dirs": [
|
||||
"mate/src"
|
||||
],
|
||||
"includes": [
|
||||
"config.php"
|
||||
]
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
// This file is auto-generated and is for apps only. Bundles SHOULD NOT rely on its content.
|
||||
|
||||
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
|
||||
|
||||
/**
|
||||
@@ -369,6 +371,7 @@ namespace Symfony\Component\DependencyInjection\Loader\Configurator;
|
||||
* }>,
|
||||
* pinecone?: array<string, array{ // Default: []
|
||||
* client?: string, // Default: "Probots\\Pinecone\\Client"
|
||||
* index_name: string,
|
||||
* namespace?: string,
|
||||
* filter?: list<scalar|null>,
|
||||
* top_k?: int,
|
||||
|
||||
0
demo/mate/.env
Normal file
0
demo/mate/.env
Normal file
1
demo/mate/.gitignore
vendored
Normal file
1
demo/mate/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
.env.local
|
||||
22
demo/mate/config.php
Normal file
22
demo/mate/config.php
Normal file
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
// User's service configuration file
|
||||
// This file is loaded into the Symfony DI container
|
||||
|
||||
use App\Mate\SymfonyAiFeaturesTool;
|
||||
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
|
||||
|
||||
use function Symfony\Component\DependencyInjection\Loader\Configurator\param;
|
||||
|
||||
return static function (ContainerConfigurator $container): void {
|
||||
$container->parameters()
|
||||
// Override default parameters here
|
||||
// ->set('mate.cache_dir', sys_get_temp_dir().'/mate')
|
||||
// ->set('mate.env_file', ['.env']) // This will load mate/.env and mate/.env.local
|
||||
;
|
||||
|
||||
$container->services()
|
||||
->set(SymfonyAiFeaturesTool::class)
|
||||
->arg('$projectDir', param('mate.root_dir'))
|
||||
->tag('mcp.capability');
|
||||
};
|
||||
10
demo/mate/extensions.php
Normal file
10
demo/mate/extensions.php
Normal file
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
// This file is managed by 'mate discover'
|
||||
// You can manually edit to enable/disable extensions
|
||||
|
||||
return [
|
||||
'symfony/ai-mate' => ['enabled' => true],
|
||||
'symfony/ai-monolog-mate-extension' => ['enabled' => true],
|
||||
'symfony/ai-symfony-mate-extension' => ['enabled' => true],
|
||||
];
|
||||
426
demo/mate/src/SymfonyAiFeaturesTool.php
Normal file
426
demo/mate/src/SymfonyAiFeaturesTool.php
Normal file
@@ -0,0 +1,426 @@
|
||||
<?php
|
||||
|
||||
namespace App\Mate;
|
||||
|
||||
use Mcp\Capability\Attribute\McpTool;
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
|
||||
/**
|
||||
* @author Johannes Wachter <johannes@sulu.io>
|
||||
*/
|
||||
class SymfonyAiFeaturesTool
|
||||
{
|
||||
public function __construct(
|
||||
private string $projectDir,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{
|
||||
* success: bool,
|
||||
* summary: array<string, int>,
|
||||
* platforms?: array<int, array<string, mixed>>,
|
||||
* agents?: array<int, array<string, mixed>>,
|
||||
* stores?: array<int, array<string, mixed>>,
|
||||
* tools?: array<int, array<string, mixed>>,
|
||||
* multi_agent_setups?: array<int, array<string, mixed>>,
|
||||
* indexers?: array<int, array<string, mixed>>,
|
||||
* retrievers?: array<int, array<string, mixed>>,
|
||||
* vectorizers?: array<int, array<string, mixed>>,
|
||||
* installed_packages?: array<int, array<string, string>>,
|
||||
* error?: string,
|
||||
* message?: string
|
||||
* }
|
||||
*/
|
||||
#[McpTool('symfony-ai-features', 'Detects and lists all available Symfony AI features, platforms, agents, tools, and configurations in this project')]
|
||||
public function getFeatures(bool $includeDetails = true): array
|
||||
{
|
||||
$configPath = $this->projectDir . '/config/packages/ai.yaml';
|
||||
$composerPath = $this->projectDir . '/composer.json';
|
||||
|
||||
if (!file_exists($configPath)) {
|
||||
return [
|
||||
'success' => false,
|
||||
'error' => 'AI configuration file not found',
|
||||
'summary' => [],
|
||||
];
|
||||
}
|
||||
|
||||
try {
|
||||
$config = Yaml::parseFile($configPath);
|
||||
$aiConfig = $config['ai'] ?? [];
|
||||
|
||||
$composer = json_decode(file_get_contents($composerPath), true);
|
||||
|
||||
$features = [
|
||||
'platforms' => $this->detectPlatforms($aiConfig, $includeDetails),
|
||||
'agents' => $this->detectAgents($aiConfig, $includeDetails),
|
||||
'stores' => $this->detectStores($aiConfig, $includeDetails),
|
||||
'tools' => $this->detectTools($aiConfig, $includeDetails),
|
||||
'multi_agent_setups' => $this->detectMultiAgentSetups($aiConfig, $includeDetails),
|
||||
'indexers' => $this->detectIndexers($aiConfig, $includeDetails),
|
||||
'retrievers' => $this->detectRetrievers($aiConfig, $includeDetails),
|
||||
'vectorizers' => $this->detectVectorizers($aiConfig, $includeDetails),
|
||||
'installed_packages' => $this->detectInstalledPackages($composer),
|
||||
];
|
||||
|
||||
return [
|
||||
'success' => true,
|
||||
'summary' => $this->generateSummary($features),
|
||||
...$features,
|
||||
];
|
||||
} catch (\Exception $e) {
|
||||
return [
|
||||
'success' => false,
|
||||
'error' => 'Failed to parse configuration',
|
||||
'message' => $e->getMessage(),
|
||||
'summary' => [],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int, array<string, mixed>>
|
||||
*/
|
||||
private function detectPlatforms(array $config, bool $includeDetails): array
|
||||
{
|
||||
$platforms = [];
|
||||
$platformConfig = $config['platform'] ?? [];
|
||||
|
||||
foreach ($platformConfig as $name => $settings) {
|
||||
$platform = [
|
||||
'name' => $name,
|
||||
'configured' => true,
|
||||
];
|
||||
|
||||
if ($includeDetails) {
|
||||
$platform['has_api_key'] = isset($settings['api_key']);
|
||||
if (isset($settings['api_key'])) {
|
||||
$platform['api_key_env_var'] = $this->extractEnvVar($settings['api_key']);
|
||||
}
|
||||
}
|
||||
|
||||
$platforms[] = $platform;
|
||||
}
|
||||
|
||||
return $platforms;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int, array<string, mixed>>
|
||||
*/
|
||||
private function detectAgents(array $config, bool $includeDetails): array
|
||||
{
|
||||
$agents = [];
|
||||
$agentConfig = $config['agent'] ?? [];
|
||||
|
||||
foreach ($agentConfig as $name => $settings) {
|
||||
$agent = [
|
||||
'name' => $name,
|
||||
'platform' => $settings['platform'] ?? 'unknown',
|
||||
'model' => is_array($settings['model'] ?? null)
|
||||
? ($settings['model']['name'] ?? 'unknown')
|
||||
: ($settings['model'] ?? 'unknown'),
|
||||
];
|
||||
|
||||
if ($includeDetails) {
|
||||
$agent['has_custom_prompt'] = isset($settings['prompt']);
|
||||
$agent['tools_enabled'] = ($settings['tools'] ?? null) !== false;
|
||||
|
||||
if ($agent['tools_enabled'] && is_array($settings['tools'] ?? null)) {
|
||||
$agent['tools'] = $this->parseTools($settings['tools']);
|
||||
}
|
||||
|
||||
if (isset($settings['prompt'])) {
|
||||
if (is_array($settings['prompt'])) {
|
||||
$agent['prompt_type'] = isset($settings['prompt']['file']) ? 'file' : 'config';
|
||||
if (isset($settings['prompt']['file'])) {
|
||||
$agent['prompt_source'] = basename($settings['prompt']['file']);
|
||||
}
|
||||
} else {
|
||||
$agent['prompt_type'] = 'inline';
|
||||
$agent['prompt_length'] = strlen($settings['prompt']);
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($settings['model']['options'])) {
|
||||
$agent['model_options'] = $settings['model']['options'];
|
||||
}
|
||||
|
||||
$agent['include_sources'] = $settings['include_sources'] ?? false;
|
||||
}
|
||||
|
||||
$agents[] = $agent;
|
||||
}
|
||||
|
||||
return $agents;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int, array<string, mixed>>
|
||||
*/
|
||||
private function detectStores(array $config, bool $includeDetails): array
|
||||
{
|
||||
$stores = [];
|
||||
$storeConfig = $config['store'] ?? [];
|
||||
|
||||
foreach ($storeConfig as $type => $instances) {
|
||||
foreach ($instances as $name => $settings) {
|
||||
$store = [
|
||||
'name' => $name,
|
||||
'type' => $type,
|
||||
];
|
||||
|
||||
if ($includeDetails && isset($settings['collection'])) {
|
||||
$store['collection'] = $settings['collection'];
|
||||
}
|
||||
|
||||
$stores[] = $store;
|
||||
}
|
||||
}
|
||||
|
||||
return $stores;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int, array<string, mixed>>
|
||||
*/
|
||||
private function detectTools(array $config, bool $includeDetails): array
|
||||
{
|
||||
$tools = [];
|
||||
$agentConfig = $config['agent'] ?? [];
|
||||
$toolsIndex = [];
|
||||
|
||||
foreach ($agentConfig as $agentName => $settings) {
|
||||
if (isset($settings['tools']) && is_array($settings['tools'])) {
|
||||
foreach ($settings['tools'] as $tool) {
|
||||
$toolInfo = $this->parseTool($tool);
|
||||
$toolKey = $toolInfo['class'] ?? $toolInfo['agent'] ?? $toolInfo['name'] ?? 'unknown';
|
||||
|
||||
if (!isset($toolsIndex[$toolKey])) {
|
||||
$toolInfo['used_by_agents'] = [$agentName];
|
||||
$toolsIndex[$toolKey] = $toolInfo;
|
||||
} else {
|
||||
$toolsIndex[$toolKey]['used_by_agents'][] = $agentName;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return array_values($toolsIndex);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int, array<string, mixed>>
|
||||
*/
|
||||
private function detectMultiAgentSetups(array $config, bool $includeDetails): array
|
||||
{
|
||||
$setups = [];
|
||||
$multiAgentConfig = $config['multi_agent'] ?? [];
|
||||
|
||||
foreach ($multiAgentConfig as $name => $settings) {
|
||||
$setup = [
|
||||
'name' => $name,
|
||||
'orchestrator' => $settings['orchestrator'] ?? null,
|
||||
'fallback' => $settings['fallback'] ?? null,
|
||||
];
|
||||
|
||||
if ($includeDetails && isset($settings['handoffs'])) {
|
||||
$setup['handoffs'] = $settings['handoffs'];
|
||||
$setup['handoff_count'] = count($settings['handoffs']);
|
||||
}
|
||||
|
||||
$setups[] = $setup;
|
||||
}
|
||||
|
||||
return $setups;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int, array<string, mixed>>
|
||||
*/
|
||||
private function detectIndexers(array $config, bool $includeDetails): array
|
||||
{
|
||||
$indexers = [];
|
||||
$indexerConfig = $config['indexer'] ?? [];
|
||||
|
||||
foreach ($indexerConfig as $name => $settings) {
|
||||
$indexer = [
|
||||
'name' => $name,
|
||||
'loader' => $this->extractClassName($settings['loader'] ?? 'unknown'),
|
||||
'source' => $settings['source'] ?? null,
|
||||
];
|
||||
|
||||
if ($includeDetails) {
|
||||
$indexer['has_filters'] = !empty($settings['filters']);
|
||||
$indexer['has_transformers'] = !empty($settings['transformers']);
|
||||
$indexer['vectorizer'] = $settings['vectorizer'] ?? null;
|
||||
$indexer['store'] = $settings['store'] ?? null;
|
||||
|
||||
if (!empty($settings['transformers'])) {
|
||||
$indexer['transformers'] = array_map(
|
||||
fn($t) => $this->extractClassName($t),
|
||||
$settings['transformers']
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$indexers[] = $indexer;
|
||||
}
|
||||
|
||||
return $indexers;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int, array<string, mixed>>
|
||||
*/
|
||||
private function detectRetrievers(array $config, bool $includeDetails): array
|
||||
{
|
||||
$retrievers = [];
|
||||
$retrieverConfig = $config['retriever'] ?? [];
|
||||
|
||||
foreach ($retrieverConfig as $name => $settings) {
|
||||
$retriever = [
|
||||
'name' => $name,
|
||||
'vectorizer' => $settings['vectorizer'] ?? null,
|
||||
'store' => $settings['store'] ?? null,
|
||||
];
|
||||
|
||||
$retrievers[] = $retriever;
|
||||
}
|
||||
|
||||
return $retrievers;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int, array<string, mixed>>
|
||||
*/
|
||||
private function detectVectorizers(array $config, bool $includeDetails): array
|
||||
{
|
||||
$vectorizers = [];
|
||||
$vectorizerConfig = $config['vectorizer'] ?? [];
|
||||
|
||||
foreach ($vectorizerConfig as $name => $settings) {
|
||||
$vectorizer = [
|
||||
'name' => $name,
|
||||
'platform' => $settings['platform'] ?? 'unknown',
|
||||
'model' => $settings['model'] ?? 'unknown',
|
||||
];
|
||||
|
||||
$vectorizers[] = $vectorizer;
|
||||
}
|
||||
|
||||
return $vectorizers;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int, array<string, string>>
|
||||
*/
|
||||
private function detectInstalledPackages(array $composer): array
|
||||
{
|
||||
$packages = [];
|
||||
$allDeps = array_merge(
|
||||
$composer['require'] ?? [],
|
||||
$composer['require-dev'] ?? []
|
||||
);
|
||||
|
||||
foreach ($allDeps as $package => $version) {
|
||||
if (str_starts_with($package, 'symfony/ai-')) {
|
||||
$packages[] = [
|
||||
'name' => $package,
|
||||
'version' => $version,
|
||||
'type' => $this->categorizePackage($package),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return $packages;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int, array<string, mixed>>
|
||||
*/
|
||||
private function parseTools(array $tools): array
|
||||
{
|
||||
return array_map(fn($tool) => $this->parseTool($tool), $tools);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
private function parseTool(mixed $tool): array
|
||||
{
|
||||
if (is_string($tool)) {
|
||||
return [
|
||||
'type' => 'service',
|
||||
'class' => $this->extractClassName($tool),
|
||||
'full_class' => $tool,
|
||||
];
|
||||
}
|
||||
|
||||
if (is_array($tool)) {
|
||||
if (isset($tool['agent'])) {
|
||||
return [
|
||||
'type' => 'sub_agent',
|
||||
'agent' => $tool['agent'],
|
||||
'name' => $tool['name'] ?? null,
|
||||
'description' => $tool['description'] ?? null,
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
'type' => 'service',
|
||||
'service' => $tool['service'] ?? null,
|
||||
'name' => $tool['name'] ?? null,
|
||||
'description' => $tool['description'] ?? null,
|
||||
'method' => $tool['method'] ?? null,
|
||||
];
|
||||
}
|
||||
|
||||
return ['type' => 'unknown'];
|
||||
}
|
||||
|
||||
private function extractClassName(string $classOrService): string
|
||||
{
|
||||
$parts = explode('\\', $classOrService);
|
||||
return end($parts);
|
||||
}
|
||||
|
||||
private function extractEnvVar(string $value): ?string
|
||||
{
|
||||
if (preg_match('/%env\(([^)]+)\)%/', $value, $matches)) {
|
||||
return $matches[1];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private function categorizePackage(string $package): string
|
||||
{
|
||||
return match (true) {
|
||||
str_contains($package, 'platform') => 'platform',
|
||||
str_contains($package, 'store') => 'store',
|
||||
str_contains($package, 'tool') => 'tool',
|
||||
str_contains($package, 'bundle') => 'bundle',
|
||||
str_contains($package, 'mate') => 'development',
|
||||
default => 'other',
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, int>
|
||||
*/
|
||||
private function generateSummary(array $features): array
|
||||
{
|
||||
return [
|
||||
'total_platforms' => count($features['platforms']),
|
||||
'total_agents' => count($features['agents']),
|
||||
'total_stores' => count($features['stores']),
|
||||
'total_tools' => count($features['tools']),
|
||||
'total_multi_agent_setups' => count($features['multi_agent_setups']),
|
||||
'total_indexers' => count($features['indexers']),
|
||||
'total_retrievers' => count($features['retrievers']),
|
||||
'total_vectorizers' => count($features['vectorizers']),
|
||||
'total_packages' => count($features['installed_packages']),
|
||||
];
|
||||
}
|
||||
}
|
||||
10
demo/mcp.json
Normal file
10
demo/mcp.json
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"mcpServers": {
|
||||
"symfony-ai-mate": {
|
||||
"command": "./vendor/bin/mate",
|
||||
"args": [
|
||||
"serve"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user