From ab06a399b044af031326011d476532e4436bd4be Mon Sep 17 00:00:00 2001 From: Snider Date: Sat, 8 Aug 2026 12:33:59 +0100 Subject: [PATCH 1/2] feat(mcp): bind the resource registry under mcp's provider key wip commit before merging main --- php/Boot.php | 17 +++++++ .../AgentResourceProviderAliasTest.php | 51 +++++++++++++++++++ 2 files changed, 68 insertions(+) create mode 100644 php/tests/Feature/Mcp/Resources/AgentResourceProviderAliasTest.php diff --git a/php/Boot.php b/php/Boot.php index 14565c4a..61898e04 100644 --- a/php/Boot.php +++ b/php/Boot.php @@ -115,6 +115,23 @@ public function register(): void ]), ); + // Alias the registry under dappcore/mcp's AgentResourceProvider name so + // that package's HTTP plans:// and sessions:// endpoints resolve it. + // + // A string, not ::class: this package keeps its own copy of Core\Mcp + // rather than depending on dappcore/mcp, so the interface cannot be + // named here — referencing it would resolve against this repo's own + // Core\Mcp tree, where it does not exist. mcp accepts a provider + // structurally for exactly this reason; the registry already satisfies + // its one method, read(). + // + // Harmless when mcp is absent: nothing resolves the alias, and binding + // a container key costs nothing. + $this->app->alias( + AgentResourceRegistry::class, + 'Core\\Mcp\\Resources\\Contracts\\AgentResourceProvider', + ); + $this->app->singleton(ForgejoService::class, function ($app) { return new ForgejoService( baseUrl: (string) config('agentic.forge_url', 'https://forge.lthn.ai'), diff --git a/php/tests/Feature/Mcp/Resources/AgentResourceProviderAliasTest.php b/php/tests/Feature/Mcp/Resources/AgentResourceProviderAliasTest.php new file mode 100644 index 00000000..68818d4f --- /dev/null +++ b/php/tests/Feature/Mcp/Resources/AgentResourceProviderAliasTest.php @@ -0,0 +1,51 @@ +bound(PROVIDER_KEY))->toBeTrue() + ->and(app(PROVIDER_KEY))->toBeInstanceOf(AgentResourceRegistry::class); +}); + +it('is the same instance as the registry itself', function (): void { + // An alias, not a second binding: two registries would drift. + expect(app(PROVIDER_KEY))->toBe(app(AgentResourceRegistry::class)); +}); + +it('satisfies the one method mcp calls on it', function (): void { + AgentPlan::factory()->create(['slug' => 'aliased-plan', 'title' => 'Aliased Plan']); + + $contents = app(PROVIDER_KEY)->read('plans://all'); + + expect($contents)->not->toBeNull() + ->and($contents['mimeType'])->toBe('text/markdown') + ->and($contents['text'])->toContain('aliased-plan'); +}); + +it('reports nothing for a uri it does not serve', function (): void { + // mcp turns null into a clean not-found rather than a body saying so. + expect(app(PROVIDER_KEY)->read('nonsense://thing'))->toBeNull(); +}); From dddb3cde9e2379b9c2b471a0a1b951b20936d9c9 Mon Sep 17 00:00:00 2001 From: Snider Date: Sat, 8 Aug 2026 12:37:31 +0100 Subject: [PATCH 2/2] feat(mcp): bind the resource registry under mcp's provider key MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit dappcore/mcp's McpApiController resolves its plans:// and sessions:// content from whatever is bound under Core\Mcp\Resources\Contracts\AgentResourceProvider. Nothing was, so those two endpoint families answered not-found — which I described as an interim waiting on this package to depend on mcp. It never would: this package keeps its own copy of Core\Mcp by design, so the interim was permanent and therefore a decision nobody had made. The registry is aliased under that name. A string, not ::class, because the interface cannot be named here — writing Core\Mcp\Resources\Contracts\ AgentResourceProvider::class would resolve against this repo's own Core\Mcp tree, where it does not exist. mcp accepts a provider structurally for exactly this reason, and the registry already satisfies its one method, read(). An alias rather than a second binding, so both names resolve to the same instance and cannot drift. Harmless when mcp is absent: nothing looks the key up, and binding it costs nothing. Four tests: the key is bound and resolves to the registry, it is the same instance as the registry itself, it answers read() with real plan content, and it reports null for a URI it does not serve. Suite 131 failed / 1194 passed, from 131 / 1190 — the four new tests, no change to the existing failures. Co-Authored-By: Virgil