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

+85
View File
@@ -0,0 +1,85 @@
<?php
declare(strict_types=1);
namespace App\Chatbot\Search;
use App\Repository\ContentDocumentRepository;
/**
* Full-text search over the mirrored website content.
*
* Ranked by relevance, weighted by type in the order people need them: what they book
* (products, hotels), where they go (destinations, concepts), general pages, then blog
* posts. The weights are multipliers on the relevance score, so a much better match of a
* lower type still wins.
*/
class ContentSearch
{
/**
* The tool's `type` values. `destination` covers region, country and city.
*/
public const TYPES = [
'page' => ['page'],
'product' => ['product'],
'hotel' => ['hotel'],
'destination' => ['region', 'country', 'city'],
'concept' => ['concept'],
'news' => ['news'],
];
private const TYPE_WEIGHTS = [
'product' => 1.4,
'hotel' => 1.4,
'region' => 1.2,
'country' => 1.2,
'city' => 1.2,
'concept' => 1.2,
'page' => 1.0,
'news' => 0.8,
];
public function __construct(private readonly ContentDocumentRepository $repository)
{
}
/**
* @param string|null $type a key of TYPES
*
* @return list<array{type: string, title: string, url: string, site: ?string, breadcrumb: ?string, excerpt: string}>
*/
public function search(string $query, ?string $type, ?string $site, int $limit): array
{
$booleanQuery = ContentSearchQuery::booleanQuery($query);
if (null === $booleanQuery) {
return [];
}
$rows = $this->repository->searchFulltext($booleanQuery, null === $type ? [] : self::TYPES[$type], $site, self::TYPE_WEIGHTS, $limit);
$terms = ContentSearchQuery::terms($query);
return array_map(static fn (array $row): array => [
'type' => self::publicType($row['type']),
'title' => $row['title'],
'url' => $row['url'],
'site' => $row['site'],
'breadcrumb' => $row['breadcrumb'],
'excerpt' => Excerpt::around($row['body_md'], $terms),
], $rows);
}
/**
* The tool's name for a stored type.
*/
public static function publicType(string $type): string
{
foreach (self::TYPES as $public => $types) {
if (in_array($type, $types, true)) {
return $public;
}
}
return $type;
}
}