Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions php/Boot.php
Original file line number Diff line number Diff line change
Expand Up @@ -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'),
Expand Down
51 changes: 51 additions & 0 deletions php/tests/Feature/Mcp/Resources/AgentResourceProviderAliasTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<?php

// SPDX-License-Identifier: EUPL-1.2

declare(strict_types=1);

use Core\Mod\Agentic\Models\AgentPlan;
use Core\Mod\Agentic\Services\AgentResourceRegistry;

/*
|--------------------------------------------------------------------------
| AgentResourceProvider alias
|--------------------------------------------------------------------------
|
| dappcore/mcp's McpApiController resolves its plans:// and sessions:// content
| from whatever is bound under Core\Mcp\Resources\Contracts\AgentResourceProvider.
| This package keeps its own copy of Core\Mcp rather than depending on mcp, so
| it cannot name that interface — it binds the registry under the name as a
| string, and mcp accepts a provider structurally.
|
| Without this alias those two endpoint families answer not-found forever,
| which is what "interim" quietly meant before it was made a decision.
|
*/

const PROVIDER_KEY = 'Core\\Mcp\\Resources\\Contracts\\AgentResourceProvider';

it('binds the registry under the provider key mcp resolves', function (): void {
expect(app()->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();
});
Loading