From bc232ebaf00617fd413a55dfe18df010c11aa7da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20San=20Miguel=20Garc=C3=ADa?= Date: Thu, 23 Apr 2026 10:20:11 -0600 Subject: [PATCH] fix: re-bootstrap ProcessWire on each MCP call for fresh DB state The MCP server bootstrapped ProcessWire once at startup and reused that instance for all subsequent tool calls. Fields, templates, and fieldgroups loaded into memory at boot were never refreshed, so changes made outside the server process (e.g. via migrations or the admin UI) were invisible until a full server restart. Now the container re-bootstraps ProcessWire before each tool/resource resolution, following PHP's share-nothing principle where each request sees the current database state. Co-Authored-By: Claude Opus 4.6 (1M context) --- src/Server/WireAwareContainer.php | 30 ++++++++++++++++++++---------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/src/Server/WireAwareContainer.php b/src/Server/WireAwareContainer.php index afa1468..51b80b4 100644 --- a/src/Server/WireAwareContainer.php +++ b/src/Server/WireAwareContainer.php @@ -5,43 +5,53 @@ namespace Elabx\ProcessWireMcp\Server; use ProcessWire\ProcessWire; +use Elabx\ProcessWireMcp\Bootstrap\ProcessWireBootstrap; use Elabx\ProcessWireMcp\Tool\ProcessWireMcpTool; use Elabx\ProcessWireMcp\Resource\ProcessWireMcpResource; use Psr\Container\ContainerInterface; /** - * PSR-11 container that resolves tool/resource classes with ProcessWire injected + * PSR-11 container that resolves tool/resource classes with ProcessWire injected. + * + * Re-bootstraps ProcessWire on every tool/resource call so each request sees + * current DB state (new fields, templates, pages added by migrations, etc.), + * following PHP's share-nothing principle. */ class WireAwareContainer implements ContainerInterface { private ProcessWire $wire; + private string $pwPath; private array $instances = []; public function __construct(ProcessWire $wire) { $this->wire = $wire; + $this->pwPath = rtrim($wire->wire('config')->paths->root, '/'); } public function get(string $id): mixed { + // Re-bootstrap ProcessWire so each call sees current DB state + $this->wire = ProcessWireBootstrap::boot($this->pwPath); + if (!isset($this->instances[$id])) { if (!class_exists($id)) { throw new class("Class not found: {$id}") extends \Exception implements \Psr\Container\NotFoundExceptionInterface {}; } $instance = new $id(); - - // Inject ProcessWire if it's one of our base classes - if ($instance instanceof ProcessWireMcpTool) { - $instance->setWire($this->wire); - } elseif ($instance instanceof ProcessWireMcpResource) { - $instance->setWire($this->wire); - } - $this->instances[$id] = $instance; } - return $this->instances[$id]; + // Always inject the fresh ProcessWire instance + $instance = $this->instances[$id]; + if ($instance instanceof ProcessWireMcpTool) { + $instance->setWire($this->wire); + } elseif ($instance instanceof ProcessWireMcpResource) { + $instance->setWire($this->wire); + } + + return $instance; } public function has(string $id): bool