mcp-1.x-dev/src/Form/McpPluginSettingsForm.php

src/Form/McpPluginSettingsForm.php
<?php

declare(strict_types=1);

namespace Drupal\mcp\Form;

use Drupal\Component\Utility\Html;
use Drupal\Core\Form\ConfigFormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\mcp\Plugin\McpInterface;
use Drupal\mcp\Plugin\McpPluginManager;
use Drupal\user\Entity\Role;
use Symfony\Component\DependencyInjection\ContainerInterface;

/**
 * Configure Model Context Protocol settings for this site.
 */
class McpPluginSettingsForm extends ConfigFormBase {

  /**
   * The MCP plugin manager.
   *
   * @var \Drupal\mcp\Plugin\McpPluginManager
   */
  protected McpPluginManager $pluginManager;

  /**
   * Constructs a new McpPluginSettingsForm.
   *
   * @param \Drupal\mcp\Plugin\McpPluginManager $plugin_manager
   *   The MCP plugin manager.
   */
  public function __construct(McpPluginManager $plugin_manager) {
    $this->pluginManager = $plugin_manager;
  }

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container): static {
    return new static(
      $container->get('plugin.manager.mcp')
    );
  }

  /**
   * The _title_callback for the mcp.plugin.settings route.
   *
   * @param \Drupal\mcp\Plugin\McpInterface $plugin
   *   The current MCP plugin.
   *
   * @return \Drupal\Core\StringTranslation\TranslatableMarkup
   *   The page title.
   */
  public function formTitle(McpInterface $plugin): TranslatableMarkup {
    $plugin_definitions = $plugin->getPluginDefinition();

    return $this->t(
      'Configure @plugin',
      ['@plugin' => $plugin_definitions['name']]
    );
  }

  /**
   * {@inheritdoc}
   */
  public function getFormId(): string {
    return 'mcp_mcp_plugin_settings';
  }

  /**
   * {@inheritdoc}
   */
  protected function getEditableConfigNames(): array {
    return ['mcp.settings'];
  }

  /**
   * {@inheritdoc}
   */
  public function buildForm(
    array $form,
    FormStateInterface $form_state,
    ?McpInterface $plugin = NULL,
  ): array {
    $plugin_config = $plugin->getConfiguration();
    $form = parent::buildForm($form, $form_state);

    $this->buildPluginSettingsSection($form, $plugin_config);
    $this->buildCustomConfigSection($form, $plugin, $form_state);
    $this->buildToolsSection($form, $plugin, $plugin_config);

    return $form;
  }

  /**
   * Build plugin settings section.
   */
  protected function buildPluginSettingsSection(
    array &$form,
    array $plugin_config,
  ): void {
    $form['plugin_settings'] = [
      '#type'  => 'fieldset',
      '#title' => $this->t('Plugin Settings'),
      '#tree'  => TRUE,
    ];

    $form['plugin_settings']['enabled'] = [
      '#type'          => 'checkbox',
      '#title'         => $this->t('Enable plugin'),
      '#description'   => $this->t('Enable or disable this plugin globally.'),
      '#default_value' => $plugin_config['enabled'] ?? TRUE,
    ];

    $form['plugin_settings']['roles'] = [
      '#type'          => 'checkboxes',
      '#title'         => $this->t('Allowed roles'),
      '#description'   => $this->t(
        'Select which roles can access this plugin. If none selected, all authenticated users can access.'
      ),
      '#options'       => $this->getRoleOptions(),
      '#default_value' => array_combine(
        $plugin_config['roles'] ?? ['authenticated'],
        $plugin_config['roles'] ?? ['authenticated']
      ),
      '#states'        => [
        'visible' => [
          ':input[name="plugin_settings[enabled]"]' => ['checked' => TRUE],
        ],
      ],
    ];
  }

  /**
   * Build custom configuration section.
   */
  protected function buildCustomConfigSection(
    array &$form,
    McpInterface $plugin,
    FormStateInterface $form_state,
  ): void {
    $custom_config_form = $plugin->buildConfigurationForm([], $form_state);
    if (!empty($custom_config_form)) {
      $form['plugin_settings']['config'] = [
        '#type'   => 'fieldset',
        '#title'  => $this->t('Additional Configuration'),
        '#tree'   => TRUE,
        '#states' => [
          'visible' => [
            ':input[name="plugin_settings[enabled]"]' => ['checked' => TRUE],
          ],
        ],
      ] + $custom_config_form;
    }
  }

  /**
   * Build tools configuration section.
   */
  protected function buildToolsSection(
    array &$form,
    McpInterface $plugin,
    array $plugin_config,
  ): void {
    $tools = $plugin->getTools();
    if (empty($tools)) {
      return;
    }

    // Attach the JavaScript library for search functionality.
    $form['#attached']['library'][] = 'mcp/mcp.admin';

    $form['tools_settings'] = [
      '#type'   => 'container',
      '#tree'   => TRUE,
      '#prefix' => '<h3>' . $this->t('Tool Settings') . '</h3>' .
      '<div class="description">' . $this->t(
          'Configure individual tools provided by this plugin.'
      ) . '</div>',
    ];

    // Add search field for tools.
    $form['tools_settings']['search'] = [
      '#type'        => 'textfield',
      '#title'       => $this->t('Search tools'),
      '#placeholder' => $this->t('Search by tool name or description...'),
      '#attributes'  => [
        'class'        => ['mcp-tools-search'],
        'autocomplete' => 'off',
      ],
      '#description' => $this->t(
        'Filter tools by name or description. Minimum 3 characters will auto-expand matching tools.'
      ),
      '#weight'      => -100,
    ];

    $form['tools_settings']['tools'] = [
      '#type' => 'vertical_tabs',
    ];

    $tools_config = $plugin_config['tools'] ?? [];
    $role_options = $this->getRoleOptions();

    foreach ($tools as $tool) {
      $this->buildSingleToolSection(
        $form['tools_settings'], $tool, $tools_config, $plugin_config,
        $plugin->getPluginId(), $role_options
      );
    }
  }

  /**
   * Build configuration for a single tool.
   */
  protected function buildSingleToolSection(
    array &$container,
    $tool,
    array $tools_config,
    array $plugin_config,
    string $plugin_id,
    array $role_options,
  ): void {
    $tool_name = $tool->name;
    $tool_config = $tools_config[$tool_name] ?? [];

    $container[$tool_name] = [
      '#type'  => 'details',
      '#title' => $tool_name,
      '#group' => 'tools_settings][tools',
    ];

    $this->buildToolForm(
      $container[$tool_name], $tool, $tool_config, $role_options,
      $plugin_config, $plugin_id
    );
  }

  /**
   * Build form elements for a single tool.
   */
  protected function buildToolForm(
    array &$container,
    $tool,
    array $tool_config,
    array $role_options,
    array $plugin_config,
    string $plugin_id,
  ): void {
    $tool_name = $tool->name;
    $plugin_enabled = $plugin_config['enabled'] ?? TRUE;

    // Add tool controls in order.
    $this->addToolEnabledField(
      $container, $tool_config, $plugin_enabled
    );
    $this->addToolInfoField(
      $container, $tool_name, $tool->description, $plugin_id
    );
    $this->addToolAnnotationsField($container, $tool);
    $this->addToolDescriptionField(
      $container, $tool->description, $tool_config, $plugin_enabled
    );
    $this->addToolRolesField(
      $container, $tool_config, $role_options, $plugin_enabled
    );
  }

  /**
   * Add enabled checkbox field.
   */
  protected function addToolEnabledField(
    array &$container,
    array $tool_config,
    bool $plugin_enabled,
  ): void {
    $container['enabled'] = [
      '#type'          => 'checkbox',
      '#title'         => $this->t('Enabled'),
      '#default_value' => $tool_config['enabled'] ?? TRUE,
      '#disabled'      => !$plugin_enabled,
      '#weight'        => -10,
    ];
  }

  /**
   * Add tool information display field.
   */
  protected function addToolInfoField(
    array &$container,
    string $tool_name,
    ?string $tool_description,
    string $plugin_id,
  ): void {
    $container['info'] = [
      '#type'   => 'container',
      '#weight' => -5,
      '#markup' => $this->formatToolInfo(
        $tool_name, $tool_description, $plugin_id
      ),
    ];
  }

  /**
   * Format tool information HTML.
   */
  protected function formatToolInfo(
    string $tool_name,
    ?string $tool_description,
    string $plugin_id,
  ): string {
    // Generate the machine name using the plugin's generateToolId method.
    $plugin = $this->pluginManager->createInstance($plugin_id);
    $machine_name = $plugin->generateToolId($plugin_id, $tool_name);

    return '<div>' .
      '<div>' . $this->t('Original Tool Information') . '</div>' .
      '<div><strong>' . $this->t('Original Name:') . '</strong> <code>'
      . Html::escape(
        $tool_name
      ) . '</code></div>' .
      '<div><strong>' . $this->t('Machine Name (For LLM):') . '</strong> <code>'
      . Html::escape(
        $machine_name
      ) . '</code></div>' .
      '<div><strong>' . $this->t('Description:') . '</strong> ' .
      Html::escape($tool_description ?? $this->t('No description available.'))
      . '</div>' .
      '</div>';
  }

  /**
   * Add tool annotations display field.
   */
  protected function addToolAnnotationsField(
    array &$container,
    $tool,
  ): void {
    if ($tool->annotations === NULL) {
      return;
    }

    $annotations = $tool->annotations;
    $hints = [];

    // Build list of annotation hints.
    if ($annotations->title !== NULL) {
      $hints[] = '<strong>' . $this->t('Title:') . '</strong> ' . Html::escape(
          $annotations->title
        );
    }
    if ($annotations->readOnlyHint !== NULL) {
      $hints[] = '<strong>' . $this->t('Read-only:') . '</strong> ' .
        ($annotations->readOnlyHint ? $this->t('Yes') : $this->t('No'));
    }
    if ($annotations->idempotentHint !== NULL) {
      $hints[] = '<strong>' . $this->t('Idempotent:') . '</strong> ' .
        ($annotations->idempotentHint ? $this->t('Yes') : $this->t('No'));
    }
    if ($annotations->destructiveHint !== NULL) {
      $hints[] = '<strong>' . $this->t('Destructive:') . '</strong> ' .
        ($annotations->destructiveHint ? $this->t('Yes') : $this->t('No'));
    }
    if ($annotations->openWorldHint !== NULL) {
      $hints[] = '<strong>' . $this->t('Open World:') . '</strong> ' .
        ($annotations->openWorldHint ? $this->t('Yes') : $this->t('No'));
    }

    // Only display if we have annotations.
    if (!empty($hints)) {
      $container['annotations'] = [
        '#type'   => 'container',
        '#weight' => -4,
        '#markup' => '<div class="mcp-tool-annotations">' .
        '<div><em>' . $this->t('Tool Behavior Hints:') . '</em></div>' .
        '<ul><li>' . implode('</li><li>', $hints) . '</li></ul>' .
        '</div>',
      ];
    }
  }

  /**
   * Add custom description field.
   */
  protected function addToolDescriptionField(
    array &$container,
    ?string $tool_description,
    array $tool_config,
    bool $plugin_enabled,
  ): void {
    $container['custom_description'] = [
      '#type'          => 'textarea',
      '#title'         => $this->t('Custom Description'),
      '#description'   => $this->t(
        'Custom description shown to MCP clients. Leave empty to use the original description.'
      ),
      '#default_value' => $tool_config['description'] ?? '',
      '#rows'          => 3,
      '#disabled'      => !$plugin_enabled,
    ];
  }

  /**
   * Add roles selection field.
   */
  protected function addToolRolesField(
    array &$container,
    array $tool_config,
    array $role_options,
    bool $plugin_enabled,
  ): void {
    $tool_roles = $tool_config['roles'] ?? [];
    $container['roles'] = [
      '#type'          => 'checkboxes',
      '#title'         => $this->t('Allowed roles'),
      '#description'   => $this->t(
        'Select roles that can access this tool. Leave empty to use plugin-level roles.'
      ),
      '#options'       => $role_options,
      '#default_value' => !empty($tool_roles) ? array_combine(
        $tool_roles, $tool_roles
      ) : [],
      '#disabled'      => !$plugin_enabled,
    ];
  }

  /**
   * Get available role options.
   */
  protected function getRoleOptions(): array {
    $role_options = [];
    foreach (Role::loadMultiple() as $role) {
      $role_options[$role->id()] = $role->label();
    }

    return $role_options;
  }

  /**
   * {@inheritdoc}
   */
  public function validateForm(
    array &$form,
    FormStateInterface $form_state,
  ): void {
    parent::validateForm($form, $form_state);

    // Get plugin from build info.
    $build_info = $form_state->getBuildInfo();
    $plugin = $build_info['args'][0] ?? NULL;
    if ($plugin instanceof McpInterface) {
      $plugin->validateConfigurationForm($form, $form_state);
    }
  }

  /**
   * {@inheritdoc}
   */
  public function submitForm(
    array &$form,
    FormStateInterface $form_state,
  ): void {
    // Get plugin from build info.
    $build_info = $form_state->getBuildInfo();
    $plugin = $build_info['args'][0] ?? NULL;
    if (!$plugin instanceof McpInterface) {
      return;
    }

    $plugin_id = $plugin->getPluginId();
    $config = $this->configFactory()->getEditable('mcp.settings');

    $plugin_config = $this->buildPluginConfiguration(
      $form_state, $plugin, $form
    );

    $config->set("plugins.$plugin_id", $plugin_config);
    $config->save();

    parent::submitForm($form, $form_state);
  }

  /**
   * Build plugin configuration from form values.
   */
  protected function buildPluginConfiguration(
    FormStateInterface $form_state,
    McpInterface $plugin,
    array $form,
  ): array {
    $plugin_settings = $form_state->getValue('plugin_settings');
    $tools_settings = $form_state->getValue('tools_settings');

    return [
      'enabled' => (bool) $plugin_settings['enabled'],
      'roles'   => array_values(array_filter($plugin_settings['roles'] ?? [])),
      'config'  => $this->extractCustomConfiguration(
        $plugin_settings, $plugin, $form, $form_state
      ),
      'tools'   => $this->extractToolsConfiguration($tools_settings),
    ];
  }

  /**
   * Extract custom plugin configuration.
   */
  protected function extractCustomConfiguration(
    array $plugin_settings,
    McpInterface $plugin,
    array $form,
    FormStateInterface $form_state,
  ): array {
    if (!isset($plugin_settings['config'])) {
      return [];
    }

    $form_state->setValue('config', $plugin_settings['config']);
    $plugin->submitConfigurationForm($form, $form_state);

    return $plugin->getConfiguration()['config'] ?? [];
  }

  /**
   * Extract tools configuration.
   */
  protected function extractToolsConfiguration(?array $tools_settings): array {
    if (empty($tools_settings)) {
      return [];
    }

    $tools_config = [];
    foreach ($tools_settings as $tool_name => $tool_settings) {
      // Skip non-tool elements.
      if ($tool_name === 'tools' || !is_array($tool_settings)) {
        continue;
      }

      $tools_config[$tool_name] = $this->extractSingleToolConfiguration(
        $tool_settings
      );
    }

    return $tools_config;
  }

  /**
   * Extract configuration for a single tool.
   */
  protected function extractSingleToolConfiguration(
    array $tool_settings,
  ): array {
    return [
      'enabled'     => (bool) ($tool_settings['enabled'] ?? FALSE),
      'roles'       => $this->extractToolRoles($tool_settings),
      'description' => trim($tool_settings['custom_description'] ?? ''),
    ];
  }

  /**
   * Extract tool roles.
   */
  protected function extractToolRoles(array $tool_settings): array {
    if (isset($tool_settings['roles']) && is_array($tool_settings['roles'])) {
      return array_values(array_filter($tool_settings['roles']));
    }

    return [];
  }

}

Главная | Обратная связь

drupal hosting | друпал хостинг | it patrol .inc