Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
56 changes: 56 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
name: PHP CI

on:
push:
branches: [ main ]
pull_request:
branches: [ main ]

jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
php-version: ['8.1', '8.2', '8.3', '8.4']

steps:
- uses: actions/checkout@v4

- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php-version }}
extensions: mbstring, xml, ctype, iconv, pcntl # Add extensions required by the bundle or composer dependencies if known, pcntl is listed in composer.json
coverage: none # Set to none, xdebug, or pcov if needed later
tools: composer:v2
env:
COMPOSER_TOKEN: ${{ secrets.GITHUB_TOKEN }}

- name: Validate composer.json and composer.lock
run: composer validate --strict

- name: Cache Composer packages
id: composer-cache
uses: actions/cache@v4
with:
path: vendor
key: ${{ runner.os }}-php-${{ matrix.php-version }}-${{ hashFiles('**/composer.lock') }}
restore-keys: |
${{ runner.os }}-php-${{ matrix.php-version }}-

- name: Install Dependencies
run: composer install --prefer-dist --no-progress

- name: Run PHP CS Fixer (Docker)
uses: docker://ghcr.io/php-cs-fixer/php-cs-fixer:3-php8.3
with:
args: fix . --dry-run --diff

- name: Run PHPStan (Docker)
uses: docker://ghcr.io/phpstan/phpstan:2
with:
args: analyse . -c phpstan.neon --memory-limit=2G
continue-on-error: true # Allow the workflow to continue even if PHPStan finds errors

- name: Run PHPUnit
run: vendor/bin/phpunit
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,5 @@
vendor/
.idea
.php-cs-fixer.cache
composer.lock
.phpunit.cache
47 changes: 24 additions & 23 deletions Command/ProfilerCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@
->addArgument('token', InputArgument::OPTIONAL, 'Profiler token (required for show action)')
->addOption('limit', 'l', InputOption::VALUE_OPTIONAL, 'Number of profiles to show when listing', 20)
->addOption('collector', 'c', InputOption::VALUE_OPTIONAL, 'Specific collector to display for show action')
->setHelp(<<<EOT
->setHelp(
<<<EOT
The <info>%command.name%</info> command provides basic interaction with the Symfony profiler.

Available actions:
Expand All @@ -51,7 +52,7 @@
{
$io = new SymfonyStyle($input, $output);
$action = $input->getArgument('action');

try {
switch ($action) {
case 'list':
Expand All @@ -69,22 +70,22 @@
return Command::FAILURE;
}
}

private function executeList(InputInterface $input, OutputInterface $output, SymfonyStyle $io): int
{
$limit = (int) $input->getOption('limit');
$io->title(sprintf('Listing the %d most recent profiles', $limit));

$tokens = $this->profiler->find(null, null, $limit, null, null, null);

if (count($tokens) === 0) {
$io->warning('No profiles found');
return Command::SUCCESS;
}

$table = new Table($output);
$table->setHeaders(['Token', 'IP', 'Method', 'URL', 'Time', 'Status']);

foreach ($tokens as $token) {
$profile = $this->profiler->loadProfile($token['token']);
if ($profile) {
Expand All @@ -98,27 +99,27 @@
]);
}
}

$table->render();
return Command::SUCCESS;
}

private function executeShow(InputInterface $input, OutputInterface $output, SymfonyStyle $io): int
{
$token = $input->getArgument('token');
if (!$token) {
$io->error('Token argument is required for show action');
return Command::INVALID;
}

$profile = $this->profiler->loadProfile($token);
if (!$profile) {
$io->error(sprintf('No profile found for token "%s"', $token));
return Command::FAILURE;
}

$io->title(sprintf('Profile for "%s"', $token));

$io->section('Profile Information');
$io->definitionList(
['Token' => $profile->getToken()],
Expand All @@ -128,16 +129,16 @@
['Time' => date('Y-m-d H:i:s', $profile->getTime())],
['Status' => $profile->getStatusCode()]
);

$collectorName = $input->getOption('collector');
if ($collectorName) {
// Display specific collector
$collector = $profile->getCollector($collectorName);
if (!$collector) {

Check failure on line 137 in Command/ProfilerCommand.php

View workflow job for this annotation

GitHub Actions / build (8.4)

Negated boolean expression is always false.

Check failure on line 137 in Command/ProfilerCommand.php

View workflow job for this annotation

GitHub Actions / build (8.1)

Negated boolean expression is always false.

Check failure on line 137 in Command/ProfilerCommand.php

View workflow job for this annotation

GitHub Actions / build (8.2)

Negated boolean expression is always false.

Check failure on line 137 in Command/ProfilerCommand.php

View workflow job for this annotation

GitHub Actions / build (8.3)

Negated boolean expression is always false.
$io->error(sprintf('No collector named "%s" found', $collectorName));
return Command::FAILURE;
}

$io->section(sprintf('Collector: %s', $collectorName));
if (method_exists($collector, 'getData')) {
$data = $collector->getData();
Expand All @@ -154,37 +155,37 @@
// List available collectors
$io->section('Available Collectors');
$collectors = $profile->getCollectors();

$table = new Table($output);
$table->setHeaders(['Collector', 'Data']);

foreach ($collectors as $collector) {
$table->addRow([
$collector->getName(),
sprintf('Use --collector=%s to view details', $collector->getName())
]);
}

$table->render();
}

return Command::SUCCESS;
}

private function executePurge(InputInterface $input, OutputInterface $output, SymfonyStyle $io): int
{
if (!$io->confirm('Are you sure you want to purge all profiler data?', false)) {
$io->note('Operation cancelled');
return Command::SUCCESS;
}

$this->profiler->purge();
$io->success('All profiler data has been purged');

return Command::SUCCESS;
}

private function displayArrayData(array $data, OutputInterface $output, SymfonyStyle $io, int $level = 0): void

Check failure on line 188 in Command/ProfilerCommand.php

View workflow job for this annotation

GitHub Actions / build (8.4)

Method Killerwolf\MCPProfilerBundle\Command\ProfilerCommand::displayArrayData() has parameter $data with no value type specified in iterable type array.

Check failure on line 188 in Command/ProfilerCommand.php

View workflow job for this annotation

GitHub Actions / build (8.1)

Method Killerwolf\MCPProfilerBundle\Command\ProfilerCommand::displayArrayData() has parameter $data with no value type specified in iterable type array.

Check failure on line 188 in Command/ProfilerCommand.php

View workflow job for this annotation

GitHub Actions / build (8.2)

Method Killerwolf\MCPProfilerBundle\Command\ProfilerCommand::displayArrayData() has parameter $data with no value type specified in iterable type array.

Check failure on line 188 in Command/ProfilerCommand.php

View workflow job for this annotation

GitHub Actions / build (8.3)

Method Killerwolf\MCPProfilerBundle\Command\ProfilerCommand::displayArrayData() has parameter $data with no value type specified in iterable type array.
{
foreach ($data as $key => $value) {
if (is_array($value)) {
Expand All @@ -195,4 +196,4 @@
}
}
}
}
}
12 changes: 7 additions & 5 deletions Command/RunMCPServerCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
{
$buffer = '';

while (true) {

Check failure on line 38 in Command/RunMCPServerCommand.php

View workflow job for this annotation

GitHub Actions / build (8.4)

While loop condition is always true.

Check failure on line 38 in Command/RunMCPServerCommand.php

View workflow job for this annotation

GitHub Actions / build (8.1)

While loop condition is always true.

Check failure on line 38 in Command/RunMCPServerCommand.php

View workflow job for this annotation

GitHub Actions / build (8.2)

While loop condition is always true.

Check failure on line 38 in Command/RunMCPServerCommand.php

View workflow job for this annotation

GitHub Actions / build (8.3)

While loop condition is always true.
$line = fgets(STDIN);
if (false === $line) {
usleep(1000);
Expand All @@ -46,7 +46,9 @@
$lines = explode("\n", $buffer);
$buffer = array_pop($lines);
foreach ($lines as $line) {
if (empty(trim($line))) continue; // Skip empty lines
if (empty(trim($line))) {
continue;
} // Skip empty lines
$this->processLine($output, $line);
}
}
Expand All @@ -54,7 +56,7 @@

// This is unreachable due to the infinite loop, but kept for structure
// @codeCoverageIgnoreStart
return Command::SUCCESS;

Check failure on line 59 in Command/RunMCPServerCommand.php

View workflow job for this annotation

GitHub Actions / build (8.4)

Unreachable statement - code above always terminates.

Check failure on line 59 in Command/RunMCPServerCommand.php

View workflow job for this annotation

GitHub Actions / build (8.1)

Unreachable statement - code above always terminates.

Check failure on line 59 in Command/RunMCPServerCommand.php

View workflow job for this annotation

GitHub Actions / build (8.2)

Unreachable statement - code above always terminates.

Check failure on line 59 in Command/RunMCPServerCommand.php

View workflow job for this annotation

GitHub Actions / build (8.3)

Unreachable statement - code above always terminates.
// @codeCoverageIgnoreEnd
}

Expand Down Expand Up @@ -86,7 +88,7 @@
$output->writeln(json_encode($response, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE));
}

private function sendInitialize(): array

Check failure on line 91 in Command/RunMCPServerCommand.php

View workflow job for this annotation

GitHub Actions / build (8.4)

Method Killerwolf\MCPProfilerBundle\Command\RunMCPServerCommand::sendInitialize() return type has no value type specified in iterable type array.

Check failure on line 91 in Command/RunMCPServerCommand.php

View workflow job for this annotation

GitHub Actions / build (8.1)

Method Killerwolf\MCPProfilerBundle\Command\RunMCPServerCommand::sendInitialize() return type has no value type specified in iterable type array.

Check failure on line 91 in Command/RunMCPServerCommand.php

View workflow job for this annotation

GitHub Actions / build (8.2)

Method Killerwolf\MCPProfilerBundle\Command\RunMCPServerCommand::sendInitialize() return type has no value type specified in iterable type array.

Check failure on line 91 in Command/RunMCPServerCommand.php

View workflow job for this annotation

GitHub Actions / build (8.3)

Method Killerwolf\MCPProfilerBundle\Command\RunMCPServerCommand::sendInitialize() return type has no value type specified in iterable type array.
{
return [
'result' => [
Expand All @@ -105,7 +107,7 @@
}

// Hardcoded tool list based on original services.yaml
private function sendToolsList(): array

Check failure on line 110 in Command/RunMCPServerCommand.php

View workflow job for this annotation

GitHub Actions / build (8.4)

Method Killerwolf\MCPProfilerBundle\Command\RunMCPServerCommand::sendToolsList() return type has no value type specified in iterable type array.

Check failure on line 110 in Command/RunMCPServerCommand.php

View workflow job for this annotation

GitHub Actions / build (8.1)

Method Killerwolf\MCPProfilerBundle\Command\RunMCPServerCommand::sendToolsList() return type has no value type specified in iterable type array.

Check failure on line 110 in Command/RunMCPServerCommand.php

View workflow job for this annotation

GitHub Actions / build (8.2)

Method Killerwolf\MCPProfilerBundle\Command\RunMCPServerCommand::sendToolsList() return type has no value type specified in iterable type array.

Check failure on line 110 in Command/RunMCPServerCommand.php

View workflow job for this annotation

GitHub Actions / build (8.3)

Method Killerwolf\MCPProfilerBundle\Command\RunMCPServerCommand::sendToolsList() return type has no value type specified in iterable type array.
{
$schemaBase = ['type' => 'object', '$schema' => 'http://json-schema.org/draft-07/schema#'];
$tokenInput = [
Expand Down Expand Up @@ -146,35 +148,35 @@
}

// Manually handle tool calls
private function callTool(array $params): array

Check failure on line 151 in Command/RunMCPServerCommand.php

View workflow job for this annotation

GitHub Actions / build (8.4)

Method Killerwolf\MCPProfilerBundle\Command\RunMCPServerCommand::callTool() return type has no value type specified in iterable type array.

Check failure on line 151 in Command/RunMCPServerCommand.php

View workflow job for this annotation

GitHub Actions / build (8.4)

Method Killerwolf\MCPProfilerBundle\Command\RunMCPServerCommand::callTool() has parameter $params with no value type specified in iterable type array.

Check failure on line 151 in Command/RunMCPServerCommand.php

View workflow job for this annotation

GitHub Actions / build (8.1)

Method Killerwolf\MCPProfilerBundle\Command\RunMCPServerCommand::callTool() return type has no value type specified in iterable type array.

Check failure on line 151 in Command/RunMCPServerCommand.php

View workflow job for this annotation

GitHub Actions / build (8.1)

Method Killerwolf\MCPProfilerBundle\Command\RunMCPServerCommand::callTool() has parameter $params with no value type specified in iterable type array.

Check failure on line 151 in Command/RunMCPServerCommand.php

View workflow job for this annotation

GitHub Actions / build (8.2)

Method Killerwolf\MCPProfilerBundle\Command\RunMCPServerCommand::callTool() return type has no value type specified in iterable type array.

Check failure on line 151 in Command/RunMCPServerCommand.php

View workflow job for this annotation

GitHub Actions / build (8.2)

Method Killerwolf\MCPProfilerBundle\Command\RunMCPServerCommand::callTool() has parameter $params with no value type specified in iterable type array.

Check failure on line 151 in Command/RunMCPServerCommand.php

View workflow job for this annotation

GitHub Actions / build (8.3)

Method Killerwolf\MCPProfilerBundle\Command\RunMCPServerCommand::callTool() return type has no value type specified in iterable type array.

Check failure on line 151 in Command/RunMCPServerCommand.php

View workflow job for this annotation

GitHub Actions / build (8.3)

Method Killerwolf\MCPProfilerBundle\Command\RunMCPServerCommand::callTool() has parameter $params with no value type specified in iterable type array.
{
$name = $params['name'] ?? null;
$arguments = $params['arguments'] ?? [];

if (!$this->profiler && $name !== 'example:hello') {
return $this->sendApplicationError(new \LogicException('Profiler service is not available.'));
return $this->sendApplicationError(new \LogicException('Profiler service is not available.'));
}

try {
$result = match ($name) {
'profiler:list' => (new ProfilerList($this->profiler, null, $this->parameterBag))->execute(

Check failure on line 162 in Command/RunMCPServerCommand.php

View workflow job for this annotation

GitHub Actions / build (8.4)

Class Killerwolf\MCPProfilerBundle\Tools\ProfilerList constructor invoked with 3 parameters, 1-2 required.

Check failure on line 162 in Command/RunMCPServerCommand.php

View workflow job for this annotation

GitHub Actions / build (8.1)

Class Killerwolf\MCPProfilerBundle\Tools\ProfilerList constructor invoked with 3 parameters, 1-2 required.

Check failure on line 162 in Command/RunMCPServerCommand.php

View workflow job for this annotation

GitHub Actions / build (8.2)

Class Killerwolf\MCPProfilerBundle\Tools\ProfilerList constructor invoked with 3 parameters, 1-2 required.

Check failure on line 162 in Command/RunMCPServerCommand.php

View workflow job for this annotation

GitHub Actions / build (8.3)

Class Killerwolf\MCPProfilerBundle\Tools\ProfilerList constructor invoked with 3 parameters, 1-2 required.
$arguments['limit'] ?? 10
),
'profiler:get_collectors' => (new ProfilerGetAllCollectorByToken($this->profiler, null))->execute(

Check failure on line 165 in Command/RunMCPServerCommand.php

View workflow job for this annotation

GitHub Actions / build (8.4)

Class Killerwolf\MCPProfilerBundle\Tools\ProfilerGetAllCollectorByToken constructor invoked with 2 parameters, 1 required.

Check failure on line 165 in Command/RunMCPServerCommand.php

View workflow job for this annotation

GitHub Actions / build (8.1)

Class Killerwolf\MCPProfilerBundle\Tools\ProfilerGetAllCollectorByToken constructor invoked with 2 parameters, 1 required.

Check failure on line 165 in Command/RunMCPServerCommand.php

View workflow job for this annotation

GitHub Actions / build (8.2)

Class Killerwolf\MCPProfilerBundle\Tools\ProfilerGetAllCollectorByToken constructor invoked with 2 parameters, 1 required.

Check failure on line 165 in Command/RunMCPServerCommand.php

View workflow job for this annotation

GitHub Actions / build (8.3)

Class Killerwolf\MCPProfilerBundle\Tools\ProfilerGetAllCollectorByToken constructor invoked with 2 parameters, 1 required.
$arguments['token'] ?? ''
),
'profiler:get_collector' => (new ProfilerGetOneCollectorByToken($this->profiler, null))->execute(
$arguments['token'] ?? '',
$arguments['collector'] ?? ''
),
'profiler:get_by_token' => (new ProfilerGetByTokenTool($this->profiler, null, $this->parameterBag))->execute(
'profiler:get_by_token' => (new ProfilerGetByTokenTool($this->profiler, null, $this->parameterBag))->execute(
$arguments['token'] ?? ''
),
default => null, // Will be handled below
};

if ($result === null && $name !== null) {
return $this->sendProtocolError(\sprintf('Tool "%s" not found', $name));
return $this->sendProtocolError(\sprintf('Tool "%s" not found', $name));
}

// Format successful result
Expand Down Expand Up @@ -215,4 +217,4 @@
],
];
}
}
}
2 changes: 1 addition & 1 deletion DependencyInjection/Configuration.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,4 @@ public function getConfigTreeBuilder(): TreeBuilder

return $treeBuilder;
}
}
}
2 changes: 1 addition & 1 deletion DependencyInjection/MCPProfilerExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -43,4 +43,4 @@ public function getAlias(): string
{
return 'killerwolf_mcp_profiler';
}
}
}
8 changes: 4 additions & 4 deletions MCPProfilerBundle.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@ class MCPProfilerBundle extends Bundle
public function build(ContainerBuilder $container): void
{
parent::build($container);

// Compiler pass removed as tools are now injected via !tagged_iterator in services.yaml
}

/**
* Returns the bundle's container extension
*/
Expand All @@ -23,7 +23,7 @@ public function getContainerExtension(): ?\Symfony\Component\DependencyInjection
if (null === $this->extension) {
$this->extension = new MCPProfilerExtension();
}

return $this->extension;
}
}
}
12 changes: 6 additions & 6 deletions Tools/ProfilerGetAllCollectorByToken.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@

use Symfony\Component\HttpKernel\Profiler\Profiler;


class ProfilerGetAllCollectorByToken { // Remove implements ToolInterface
class ProfilerGetAllCollectorByToken
{
private ?Profiler $profiler = null;

// Inject the Profiler service
Expand All @@ -18,14 +18,14 @@ public function __construct(?Profiler $profiler)
public function execute(string $token): string
{
if (!$this->profiler) {
return json_encode(['error' => 'Profiler service not available.']);
return json_encode(['error' => 'Profiler service not available.']);
}

// Load the profile for the given token
try {
$profile = $this->profiler->loadProfile($token);
} catch (\Exception $e) {
return json_encode(['error' => "Error loading profile for token {$token}: " . $e->getMessage()]);
return json_encode(['error' => "Error loading profile for token {$token}: " . $e->getMessage()]);
}

if (!$profile) {
Expand All @@ -37,7 +37,7 @@ public function execute(string $token): string
$collectorNames = array_keys($profile->getCollectors());
return json_encode($collectorNames, JSON_PRETTY_PRINT);
} catch (\Exception $e) {
return json_encode(['error' => "Error getting collector names for token {$token}: " . $e->getMessage()]);
return json_encode(['error' => "Error getting collector names for token {$token}: " . $e->getMessage()]);
}
}
}
}
Loading