1010use PhpMcp \Schema \Content \TextResourceContents ;
1111use PhpMcp \Schema \Resource ;
1212use Psr \Container \ContainerInterface ;
13+ use Throwable ;
1314
1415class RegisteredResource extends RegisteredElement
1516{
@@ -44,14 +45,14 @@ public function read(ContainerInterface $container, string $uri): array
4445 *
4546 * @param mixed $readResult The raw result from the resource handler method.
4647 * @param string $uri The URI of the resource that was read.
47- * @param ?string $defaultMimeType The default MIME type from the ResourceDefinition.
48+ * @param ?string $mimeType The MIME type from the ResourceDefinition.
4849 * @return array<TextResourceContents|BlobResourceContents> Array of ResourceContents objects.
4950 *
5051 * @throws \RuntimeException If the result cannot be formatted.
5152 *
5253 * Supported result types:
53- * - EmbeddedResource : Used as-is
54- * - ResourceContent: Embedded resource is extracted
54+ * - ResourceContent : Used as-is
55+ * - EmbeddedResource: Resource is extracted from the EmbeddedResource
5556 * - string: Converted to text content with guessed or provided MIME type
5657 * - stream resource: Read and converted to blob with provided MIME type
5758 * - array with 'blob' key: Used as blob content
@@ -60,7 +61,7 @@ public function read(ContainerInterface $container, string $uri): array
6061 * - array: Converted to JSON if MIME type is application/json or contains 'json'
6162 * For other MIME types, will try to convert to JSON with a warning
6263 */
63- protected function formatResult (mixed $ readResult , string $ uri , ?string $ defaultMimeType ): array
64+ protected function formatResult (mixed $ readResult , string $ uri , ?string $ mimeType ): array
6465 {
6566 if ($ readResult instanceof ResourceContents) {
6667 return [$ readResult ];
@@ -70,16 +71,54 @@ protected function formatResult(mixed $readResult, string $uri, ?string $default
7071 return [$ readResult ->resource ];
7172 }
7273
73- if (is_array ($ readResult ) && ! empty ($ readResult ) && $ readResult [array_key_first ($ readResult )] instanceof ResourceContents) {
74- return $ readResult ;
75- }
74+ if (is_array ($ readResult )) {
75+ if (empty ($ readResult )) {
76+ return [TextResourceContents::make ($ uri , 'application/json ' , '[] ' )];
77+ }
7678
77- if (is_array ($ readResult ) && ! empty ($ readResult ) && $ readResult [array_key_first ($ readResult )] instanceof EmbeddedResource) {
78- return array_map (fn ($ item ) => $ item ->resource , $ readResult );
79+ $ allAreResourceContents = true ;
80+ $ hasResourceContents = false ;
81+ $ allAreEmbeddedResource = true ;
82+ $ hasEmbeddedResource = false ;
83+
84+ foreach ($ readResult as $ item ) {
85+ if ($ item instanceof ResourceContents) {
86+ $ hasResourceContents = true ;
87+ $ allAreEmbeddedResource = false ;
88+ } elseif ($ item instanceof EmbeddedResource) {
89+ $ hasEmbeddedResource = true ;
90+ $ allAreResourceContents = false ;
91+ } else {
92+ $ allAreResourceContents = false ;
93+ $ allAreEmbeddedResource = false ;
94+ }
95+ }
96+
97+ if ($ allAreResourceContents && $ hasResourceContents ) {
98+ return $ readResult ;
99+ }
100+
101+ if ($ allAreEmbeddedResource && $ hasEmbeddedResource ) {
102+ return array_map (fn ($ item ) => $ item ->resource , $ readResult );
103+ }
104+
105+ if ($ hasResourceContents || $ hasEmbeddedResource ) {
106+ $ result = [];
107+ foreach ($ readResult as $ item ) {
108+ if ($ item instanceof ResourceContents) {
109+ $ result [] = $ item ;
110+ } elseif ($ item instanceof EmbeddedResource) {
111+ $ result [] = $ item ->resource ;
112+ } else {
113+ $ result = array_merge ($ result , $ this ->formatResult ($ item , $ uri , $ mimeType ));
114+ }
115+ }
116+ return $ result ;
117+ }
79118 }
80119
81120 if (is_string ($ readResult )) {
82- $ mimeType = $ defaultMimeType ?? $ this ->guessMimeTypeFromString ($ readResult );
121+ $ mimeType = $ mimeType ?? $ this ->guessMimeTypeFromString ($ readResult );
83122
84123 return [TextResourceContents::make ($ uri , $ mimeType , $ readResult )];
85124 }
@@ -88,7 +127,7 @@ protected function formatResult(mixed $readResult, string $uri, ?string $default
88127 $ result = BlobResourceContents::fromStream (
89128 $ uri ,
90129 $ readResult ,
91- $ defaultMimeType ?? 'application/octet-stream '
130+ $ mimeType ?? 'application/octet-stream '
92131 );
93132
94133 @fclose ($ readResult );
@@ -97,36 +136,40 @@ protected function formatResult(mixed $readResult, string $uri, ?string $default
97136 }
98137
99138 if (is_array ($ readResult ) && isset ($ readResult ['blob ' ]) && is_string ($ readResult ['blob ' ])) {
100- $ mimeType = $ readResult ['mimeType ' ] ?? $ defaultMimeType ?? 'application/octet-stream ' ;
139+ $ mimeType = $ readResult ['mimeType ' ] ?? $ mimeType ?? 'application/octet-stream ' ;
101140
102141 return [BlobResourceContents::make ($ uri , $ mimeType , $ readResult ['blob ' ])];
103142 }
104143
105144 if (is_array ($ readResult ) && isset ($ readResult ['text ' ]) && is_string ($ readResult ['text ' ])) {
106- $ mimeType = $ readResult ['mimeType ' ] ?? $ defaultMimeType ?? 'text/plain ' ;
145+ $ mimeType = $ readResult ['mimeType ' ] ?? $ mimeType ?? 'text/plain ' ;
107146
108147 return [TextResourceContents::make ($ uri , $ mimeType , $ readResult ['text ' ])];
109148 }
110149
111150 if ($ readResult instanceof \SplFileInfo && $ readResult ->isFile () && $ readResult ->isReadable ()) {
112- return [BlobResourceContents::fromSplFileInfo ($ uri , $ readResult , $ defaultMimeType )];
151+ if ($ mimeType && str_contains (strtolower ($ mimeType ), 'text ' )) {
152+ return [TextResourceContents::make ($ uri , $ mimeType , file_get_contents ($ readResult ->getPathname ()))];
153+ }
154+
155+ return [BlobResourceContents::fromSplFileInfo ($ uri , $ readResult , $ mimeType )];
113156 }
114157
115158 if (is_array ($ readResult )) {
116- if ($ defaultMimeType && (str_contains (strtolower ($ defaultMimeType ), 'json ' ) ||
117- $ defaultMimeType === 'application/json ' )) {
159+ if ($ mimeType && (str_contains (strtolower ($ mimeType ), 'json ' ) ||
160+ $ mimeType === 'application/json ' )) {
118161 try {
119162 $ jsonString = json_encode ($ readResult , JSON_THROW_ON_ERROR | JSON_PRETTY_PRINT );
120163
121- return [TextResourceContents::make ($ uri , $ defaultMimeType , $ jsonString )];
164+ return [TextResourceContents::make ($ uri , $ mimeType , $ jsonString )];
122165 } catch (\JsonException $ e ) {
123166 throw new \RuntimeException ("Failed to encode array as JSON for URI ' {$ uri }': {$ e ->getMessage ()}" );
124167 }
125168 }
126169
127170 try {
128171 $ jsonString = json_encode ($ readResult , JSON_THROW_ON_ERROR | JSON_PRETTY_PRINT );
129- $ mimeType = 'application/json ' ;
172+ $ mimeType = $ mimeType ?? 'application/json ' ;
130173
131174 return [TextResourceContents::make ($ uri , $ mimeType , $ jsonString )];
132175 } catch (\JsonException $ e ) {
@@ -141,24 +184,48 @@ protected function formatResult(mixed $readResult, string $uri, ?string $default
141184 private function guessMimeTypeFromString (string $ content ): string
142185 {
143186 $ trimmed = ltrim ($ content );
187+
144188 if (str_starts_with ($ trimmed , '< ' ) && str_ends_with (rtrim ($ content ), '> ' )) {
145- // Looks like HTML or XML? Prefer text/plain unless sure.
146- if (stripos ($ trimmed , '<html ' ) !== false ) {
189+ if (str_contains ($ trimmed , '<html ' )) {
147190 return 'text/html ' ;
148191 }
149- if (stripos ($ trimmed , '<?xml ' ) !== false ) {
192+ if (str_contains ($ trimmed , '<?xml ' )) {
150193 return 'application/xml ' ;
151- } // or text/xml
194+ }
152195
153- return 'text/plain ' ; // Default for tag-like structures
196+ return 'text/plain ' ;
154197 }
198+
155199 if (str_starts_with ($ trimmed , '{ ' ) && str_ends_with (rtrim ($ content ), '} ' )) {
156200 return 'application/json ' ;
157201 }
202+
158203 if (str_starts_with ($ trimmed , '[ ' ) && str_ends_with (rtrim ($ content ), '] ' )) {
159204 return 'application/json ' ;
160205 }
161206
162- return 'text/plain ' ; // Default
207+ return 'text/plain ' ;
208+ }
209+
210+ public function toArray (): array
211+ {
212+ return [
213+ 'schema ' => $ this ->schema ->toArray (),
214+ ...parent ::toArray (),
215+ ];
216+ }
217+
218+ public static function fromArray (array $ data ): self |false
219+ {
220+ try {
221+ return new self (
222+ Resource::fromArray ($ data ['schema ' ]),
223+ $ data ['handlerClass ' ],
224+ $ data ['handlerMethod ' ],
225+ $ data ['isManual ' ] ?? false ,
226+ );
227+ } catch (Throwable $ e ) {
228+ return false ;
229+ }
163230 }
164231}
0 commit comments