|
106 | 106 | use ApiPlatform\Laravel\State\SwaggerUiProcessor; |
107 | 107 | use ApiPlatform\Laravel\State\SwaggerUiProvider; |
108 | 108 | use ApiPlatform\Laravel\State\ValidateProvider; |
| 109 | +use ApiPlatform\Mcp\Capability\Registry\Loader as McpLoader; |
| 110 | +use ApiPlatform\Mcp\Metadata\Operation\Factory\OperationMetadataFactory as McpOperationMetadataFactory; |
| 111 | +use ApiPlatform\Mcp\Routing\IriConverter as McpIriConverter; |
| 112 | +use ApiPlatform\Mcp\Server\Handler; |
| 113 | +use ApiPlatform\Mcp\State\StructuredContentProcessor; |
109 | 114 | use ApiPlatform\Metadata\IdentifiersExtractor; |
110 | 115 | use ApiPlatform\Metadata\IdentifiersExtractorInterface; |
111 | 116 | use ApiPlatform\Metadata\InflectorInterface; |
|
147 | 152 | use ApiPlatform\State\CallableProcessor; |
148 | 153 | use ApiPlatform\State\CallableProvider; |
149 | 154 | use ApiPlatform\State\ErrorProvider; |
150 | | -use ApiPlatform\State\ObjectMapper\ObjectMapper; |
151 | 155 | use ApiPlatform\State\Pagination\Pagination; |
152 | 156 | use ApiPlatform\State\Pagination\PaginationOptions; |
153 | 157 | use ApiPlatform\State\Processor\AddLinkHeaderProcessor; |
|
164 | 168 | use ApiPlatform\State\Provider\ReadProvider; |
165 | 169 | use ApiPlatform\State\ProviderInterface; |
166 | 170 | use ApiPlatform\State\SerializerContextBuilderInterface; |
| 171 | +use Http\Discovery\Psr17Factory; |
167 | 172 | use Illuminate\Config\Repository as ConfigRepository; |
168 | 173 | use Illuminate\Contracts\Foundation\Application; |
169 | 174 | use Illuminate\Routing\Router; |
170 | 175 | use Illuminate\Support\ServiceProvider; |
| 176 | +use Mcp\Capability\Registry; |
| 177 | +use Mcp\Server; |
| 178 | +use Mcp\Server\Builder; |
| 179 | +use Mcp\Server\Session\InMemorySessionStore; |
171 | 180 | use Negotiation\Negotiator; |
172 | 181 | use PHPStan\PhpDocParser\Parser\PhpDocParser; |
173 | 182 | use Psr\Log\LoggerInterface; |
| 183 | +use Symfony\AI\McpBundle\Controller\McpController; |
| 184 | +use Symfony\Bridge\PsrHttpMessage\Factory\HttpFoundationFactory; |
| 185 | +use Symfony\Bridge\PsrHttpMessage\Factory\PsrHttpFactory; |
| 186 | +use Symfony\Component\HttpFoundation\RequestStack; |
174 | 187 | use Symfony\Component\ObjectMapper\Metadata\ObjectMapperMetadataFactoryInterface; |
175 | 188 | use Symfony\Component\ObjectMapper\Metadata\ReflectionObjectMapperMetadataFactory; |
| 189 | +use Symfony\Component\ObjectMapper\ObjectMapper; |
176 | 190 | use Symfony\Component\ObjectMapper\ObjectMapperInterface; |
177 | 191 | use Symfony\Component\PropertyAccess\PropertyAccessorInterface; |
178 | 192 | use Symfony\Component\PropertyInfo\Extractor\PhpStanExtractor; |
@@ -427,11 +441,9 @@ public function register(): void |
427 | 441 | $this->app->singleton(ObjectMapperMetadataFactoryInterface::class, ReflectionObjectMapperMetadataFactory::class); |
428 | 442 |
|
429 | 443 | $this->app->singleton(ObjectMapper::class, static function (Application $app) { |
430 | | - if (!$app->bound('api_platform.object_mapper')) { |
431 | | - return null; |
432 | | - } |
433 | | - |
434 | | - return new ObjectMapper($app->make('api_platform.object_mapper')); |
| 444 | + return new ObjectMapper( |
| 445 | + $app->make(ObjectMapperMetadataFactoryInterface::class) |
| 446 | + ); |
435 | 447 | }); |
436 | 448 |
|
437 | 449 | $this->app->extend(ProviderInterface::class, static function (ProviderInterface $inner, Application $app) { |
@@ -891,6 +903,8 @@ public function register(): void |
891 | 903 | $this->registerGraphQl(); |
892 | 904 | } |
893 | 905 |
|
| 906 | + $this->registerMcp(); |
| 907 | + |
894 | 908 | $this->app->singleton(JsonApiEntrypointNormalizer::class, static function (Application $app) { |
895 | 909 | return new JsonApiEntrypointNormalizer( |
896 | 910 | $app->make(ResourceMetadataCollectionFactoryInterface::class), |
@@ -1039,6 +1053,126 @@ public function register(): void |
1039 | 1053 | } |
1040 | 1054 | } |
1041 | 1055 |
|
| 1056 | + private function registerMcp(): void |
| 1057 | + { |
| 1058 | + if (!class_exists(McpController::class)) { |
| 1059 | + return; |
| 1060 | + } |
| 1061 | + |
| 1062 | + $this->app->singleton(Registry::class, static function (Application $app) { |
| 1063 | + return new Registry( |
| 1064 | + null, // event dispatcher (todo) |
| 1065 | + $app->make(LoggerInterface::class) |
| 1066 | + ); |
| 1067 | + }); |
| 1068 | + |
| 1069 | + $this->app->singleton(McpLoader::class, static function (Application $app) { |
| 1070 | + return new McpLoader( |
| 1071 | + $app->make(ResourceNameCollectionFactoryInterface::class), |
| 1072 | + $app->make(ResourceMetadataCollectionFactoryInterface::class), |
| 1073 | + $app->make(SchemaFactoryInterface::class) |
| 1074 | + ); |
| 1075 | + }); |
| 1076 | + $this->app->tag(McpLoader::class, 'mcp.loader'); |
| 1077 | + |
| 1078 | + // TODO: add more stores? |
| 1079 | + $this->app->singleton('mcp.session.store', static function () { |
| 1080 | + return new InMemorySessionStore(3600); |
| 1081 | + }); |
| 1082 | + |
| 1083 | + $this->app->singleton(Builder::class, static function (Application $app) { |
| 1084 | + $config = $app['config']; |
| 1085 | + |
| 1086 | + $builder = Server::builder() |
| 1087 | + ->setServerInfo( |
| 1088 | + $config->get('api-platform.title', 'API Platform'), |
| 1089 | + $config->get('api-platform.version', '1.0.0'), |
| 1090 | + $config->get('api-platform.description', 'My awesome API'), |
| 1091 | + [], // icons |
| 1092 | + null // website_url todo |
| 1093 | + ) |
| 1094 | + ->setPaginationLimit(100) |
| 1095 | + ->setRegistry($app->make(Registry::class)) |
| 1096 | + ->setSession($app->make('mcp.session.store')); |
| 1097 | + |
| 1098 | + foreach ($app->tagged('mcp.loader') as $loader) { |
| 1099 | + $builder->addLoader($loader); |
| 1100 | + } |
| 1101 | + |
| 1102 | + $builder->addRequestHandler($app->make(Handler::class)); |
| 1103 | + |
| 1104 | + return $builder; |
| 1105 | + }); |
| 1106 | + |
| 1107 | + $this->app->singleton(Server::class, static function (Application $app) { |
| 1108 | + return $app->make(Builder::class)->build(); |
| 1109 | + }); |
| 1110 | + |
| 1111 | + $this->app->singleton(McpOperationMetadataFactory::class, static function (Application $app) { |
| 1112 | + return new McpOperationMetadataFactory( |
| 1113 | + $app->make(ResourceNameCollectionFactoryInterface::class), |
| 1114 | + $app->make(ResourceMetadataCollectionFactoryInterface::class) |
| 1115 | + ); |
| 1116 | + }); |
| 1117 | + |
| 1118 | + $this->app->singleton('api_platform.mcp.state_processor.write', static function (Application $app) { |
| 1119 | + return new WriteProcessor( |
| 1120 | + null, |
| 1121 | + $app->make(CallableProcessor::class) |
| 1122 | + ); |
| 1123 | + }); |
| 1124 | + |
| 1125 | + $this->app->singleton(StructuredContentProcessor::class, static function (Application $app) { |
| 1126 | + return new StructuredContentProcessor( |
| 1127 | + $app->make(SerializerInterface::class), |
| 1128 | + $app->make(SerializerContextBuilderInterface::class), |
| 1129 | + $app->make('api_platform.mcp.state_processor.write') |
| 1130 | + ); |
| 1131 | + }); |
| 1132 | + |
| 1133 | + $this->app->singleton(RequestStack::class, static function (Application $app) { |
| 1134 | + return new RequestStack(); |
| 1135 | + }); |
| 1136 | + |
| 1137 | + $this->app->singleton(Handler::class, static function (Application $app) { |
| 1138 | + return new Handler( |
| 1139 | + $app->make(McpOperationMetadataFactory::class), |
| 1140 | + $app->make(ProviderInterface::class), |
| 1141 | + $app->make(StructuredContentProcessor::class), |
| 1142 | + $app->make(RequestStack::class), |
| 1143 | + $app->make(LoggerInterface::class) |
| 1144 | + ); |
| 1145 | + }); |
| 1146 | + |
| 1147 | + $this->app->extend(IriConverterInterface::class, static function (IriConverterInterface $iriConverter) { |
| 1148 | + return new McpIriConverter($iriConverter); |
| 1149 | + }); |
| 1150 | + |
| 1151 | + // Register Symfony MCP controller |
| 1152 | + $this->app->singleton(McpController::class, static function (Application $app) { |
| 1153 | + if (!class_exists('Http\Discovery\Psr17Factory')) { |
| 1154 | + throw new \RuntimeException('PSR-17 HTTP factory implementation not available. Please install php-http/discovery.'); |
| 1155 | + } |
| 1156 | + |
| 1157 | + $psr17Factory = new Psr17Factory(); |
| 1158 | + $psrHttpFactory = new PsrHttpFactory( |
| 1159 | + $psr17Factory, |
| 1160 | + $psr17Factory, |
| 1161 | + $psr17Factory, |
| 1162 | + $psr17Factory |
| 1163 | + ); |
| 1164 | + $httpFoundationFactory = new HttpFoundationFactory(); |
| 1165 | + |
| 1166 | + return new McpController( |
| 1167 | + $app->make(Server::class), |
| 1168 | + $psrHttpFactory, |
| 1169 | + $httpFoundationFactory, |
| 1170 | + $psr17Factory, |
| 1171 | + $psr17Factory |
| 1172 | + ); |
| 1173 | + }); |
| 1174 | + } |
| 1175 | + |
1042 | 1176 | private function registerGraphQl(): void |
1043 | 1177 | { |
1044 | 1178 | $this->app->singleton(GraphQlItemNormalizer::class, static function (Application $app) { |
|
0 commit comments