feat: tools and limits settings tabs

Adds Einstellungen → LIT MCP → Tools (direct/indirect/disabled per
ability, only ExposurePolicy-allowed modes offered) and → Limits
(upload size, log retention, snapshot count), wired into Plugin.php.

Ruling Q11b (carried from task 6): overview.md notes that direct
visibility is configurable; hints in src/ that name a tool which is
direct by default but can be switched to indirect (content-get,
content-query, content-types, post-publish, media-search) now use the
neutral "lit/<name> (direkt: lit-<name>)" form so they stay correct
after an admin override. lit/guide (always direct) and the
media-preview/-download DIRECT_ONLY tools are unaffected.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01At3V6SjwbMe6eMzVGW3Hs7
This commit is contained in:
2026-09-26 03:47:40 +02:00
co-authored by Claude Opus 5.5
parent eee1840411
commit bca35f6166
14 changed files with 330 additions and 22 deletions
+119
View File
@@ -0,0 +1,119 @@
<?php
declare(strict_types=1);
namespace LIT\MCP\Admin;
use LIT\MCP\Abilities\ExposurePolicy;
use LIT\MCP\Integrations\IntegrationRegistry;
/**
* Tab "Tools": direct/indirect/disabled exposure per ability (spec §8.4). Only the modes
* ExposurePolicy::isAllowed() allows for a given ability are offered; "Standard" saves nothing,
* so a later version's changed default takes effect automatically.
*/
final class ToolsTab implements AdminTab
{
public const GROUP = 'lit_mcp_tools';
public function __construct(private IntegrationRegistry $registry, private ExposurePolicy $policy)
{
}
public function slug(): string
{
return 'tools';
}
public function label(): string
{
return __('Tools', 'wp-lit-mcp');
}
public function registerSettings(): void
{
register_setting(self::GROUP, ExposurePolicy::OPTION, [
'type' => 'array',
'sanitize_callback' => [ExposurePolicy::class, 'sanitize'],
'default' => [],
]);
}
public function registerActions(): void
{
}
public function render(): void
{
echo '<p>'
. esc_html__('Direkt: sofort als eigenes Tool sichtbar.', 'wp-lit-mcp') . ' '
. esc_html__('Indirekt: nur über die Meta-Tools (discover/execute) erreichbar.', 'wp-lit-mcp') . ' '
. esc_html__('Deaktiviert: gar nicht verfügbar.', 'wp-lit-mcp')
. '</p>';
echo '<p>'
. esc_html__(
'Die Server-Anweisungen beim Verbinden nennen immer die aktuell direkt sichtbaren Tools.',
'wp-lit-mcp'
) . ' '
. esc_html__(
'Änderungen gelten also ab der nächsten Verbindung; in claude.ai ggf. den Connector neu verbinden.',
'wp-lit-mcp'
)
. '</p>';
echo '<p>' . esc_html__(
'Das Divi-Code-Modul ist immer erlaubt (nur nachrangig) und hat keine eigene Einstellung.',
'wp-lit-mcp'
) . '</p>';
echo '<form method="post" action="options.php">';
settings_fields(self::GROUP);
echo '<table class="widefat striped"><thead><tr><th>' . esc_html__('Tool', 'wp-lit-mcp') . '</th><th>'
. esc_html__('Einstellung', 'wp-lit-mcp') . '</th></tr></thead><tbody>';
foreach ($this->registry->abilities() as $ability) {
$name = $ability->name();
$current = $this->policy->mode($ability);
$isDefault = $current === $this->policy->defaultMode($ability);
printf(
'<tr><td><strong>%s</strong><br><code>%s</code></td><td><select name="%s[%s]">',
esc_html($ability->label()),
esc_html($name),
esc_attr(ExposurePolicy::OPTION),
esc_attr($name)
);
printf(
'<option value=""%s>%s</option>',
selected($isDefault, true, false),
esc_html(sprintf(
/* translators: %s: default mode */
__('Standard (%s)', 'wp-lit-mcp'),
$this->modeLabel($this->policy->defaultMode($ability))
))
);
foreach (ExposurePolicy::MODES as $mode) {
if (! ExposurePolicy::isAllowed($name, $mode)) {
continue;
}
printf(
'<option value="%s"%s>%s</option>',
esc_attr($mode),
selected(! $isDefault && $current === $mode, true, false),
esc_html($this->modeLabel($mode))
);
}
echo '</select></td></tr>';
}
echo '</tbody></table>';
submit_button();
echo '</form>';
}
private function modeLabel(string $mode): string
{
return match ($mode) {
ExposurePolicy::DIRECT => __('direkt', 'wp-lit-mcp'),
ExposurePolicy::INDIRECT => __('indirekt', 'wp-lit-mcp'),
default => __('deaktiviert', 'wp-lit-mcp'),
};
}
}