feat: add MCP server for the website chatbot over a mirrored TYPO3 content feed

This commit is contained in:
fromme committed 2026-10-08 11:46:58 +02:00
1 parent ff7ddf7a9e
commit 1b4384e3f3
52 files changed
+5117 -4

No files matched your search

+83
View File
@@ -0,0 +1,83 @@
<?php
declare(strict_types=1);
namespace App\Command;
use App\ContentFeed\ContentSyncer;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Command\LockableTrait;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
#[AsCommand(
name: 'app:content:sync',
description: 'Mirrors the TYPO3 content feed for the chatbot MCP server.',
)]
/**
* Console entrypoint for the content feed sync.
*
* Meant to run every 15 minutes, plus once a week with --refresh in the night window.
* Outside 01:00-05:00 Europe/Berlin a run fetches at most 50 pages; --ignore-window lifts
* that for a deliberate manual run, such as the first sync on staging.
*/
class ContentSyncCommand extends Command
{
use LockableTrait;
public function __construct(private readonly ContentSyncer $syncer)
{
parent::__construct();
}
protected function configure(): void
{
$this
->addOption('refresh', null, InputOption::VALUE_NONE, sprintf('Also refetch items stored more than %d days ago, oldest first', ContentSyncer::REFRESH_AFTER_DAYS))
->addOption('ignore-window', null, InputOption::VALUE_NONE, sprintf('Fetch every due page, not just %d, outside the night window', ContentSyncer::PAGES_OUTSIDE_WINDOW))
;
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);
if (!$this->lock()) {
$io->warning('Another content sync is running.');
return Command::SUCCESS;
}
try {
$report = $this->syncer->run((bool) $input->getOption('refresh'), (bool) $input->getOption('ignore-window'));
} finally {
$this->release();
}
$io->definitionList(
['Added' => $report->added],
['Deleted' => $report->deleted],
['Records fetched' => $report->records],
['Pages fetched' => $report->pages],
['Failed' => $report->failed],
['Pages deferred' => $report->deferred],
);
if (null !== $report->pagePhaseEndedBy) {
$io->warning('Page phase ended early: '.$report->pagePhaseEndedBy);
}
if (null !== $report->stoppedBy) {
$io->error('Run stopped: '.$report->stoppedBy);
return Command::FAILURE;
}
$io->success('Content feed synced.');
return Command::SUCCESS;
}
}