@@ -2,20 +2,24 @@ package main
22
33import (
44 "context"
5+ "encoding/json"
56 "flag"
67 "fmt"
78 "image"
89 "log"
910 "net/http"
1011 "os"
12+ "os/exec"
1113 "path/filepath"
1214 "regexp"
1315 "strings"
16+ "time"
1417
1518 _ "image/jpeg"
1619 _ "image/png"
1720
1821 "github.com/docker/mcp-registry/internal/licenses"
22+ "github.com/docker/mcp-registry/internal/mcp"
1923 "github.com/docker/mcp-registry/pkg/github"
2024 "github.com/docker/mcp-registry/pkg/servers"
2125 "gopkg.in/yaml.v3"
@@ -56,11 +60,15 @@ func run(name string) error {
5660 if err := isRemoteValid (name ); err != nil {
5761 return err
5862 }
59-
63+
6064 if err := isOAuthDynamicValid (name ); err != nil {
6165 return err
6266 }
6367
68+ if err := isPociValid (name ); err != nil {
69+ return err
70+ }
71+
6472 return nil
6573}
6674
@@ -147,13 +155,13 @@ func IsLicenseValid(name string) error {
147155 if err != nil {
148156 return err
149157 }
150-
158+
151159 // Skip license validation for remote servers without source
152160 if server .Source .Project == "" {
153161 fmt .Println ("✅ License validation skipped (remote server)" )
154162 return nil
155163 }
156-
164+
157165 repository , err := client .GetProjectRepository (ctx , server .Source .Project )
158166 if err != nil {
159167 return err
@@ -193,14 +201,14 @@ func isIconValid(name string) error {
193201 fmt .Println ("🛑 Icon is too large. It must be less than 2MB" )
194202 return nil
195203 }
196-
204+
197205 // Check content type for SVG support
198206 contentType := resp .Header .Get ("Content-Type" )
199207 if contentType == "image/svg+xml" {
200208 fmt .Println ("✅ Icon is valid (SVG)" )
201209 return nil
202210 }
203-
211+
204212 img , format , err := image .DecodeConfig (resp .Body )
205213 if err != nil {
206214 return err
@@ -250,10 +258,52 @@ func isRemoteValid(name string) error {
250258 return fmt .Errorf ("remote server transport_type must be one of: stdio, sse, streamable-http (got: %s)" , server .Remote .TransportType )
251259 }
252260
261+ if err := hasValidTools (server ); err != nil {
262+ return err
263+ }
264+
253265 fmt .Println ("✅ Remote is valid" )
254266 return nil
255267}
256268
269+ // Check that there is either a tools.json, dynamic tools, or can fetch remote tools
270+ func hasValidTools (server servers.Server ) error {
271+ defaultErr := fmt .Errorf ("server must have either a tools.json, dynamic tools, or can fetch remote tools" )
272+
273+ // Dynamic tools are valid
274+ if server .Dynamic != nil && server .Dynamic .Tools {
275+ fmt .Println ("✅ Dynamic tools are valid" )
276+ return nil
277+ }
278+
279+ // Tools.json is valid
280+ tools , err := readToolsJson (server .Name )
281+ if err == nil {
282+ toolCount := len (tools )
283+ fmt .Printf ("✅ tools.json is valid. Found %d tools.\n " , toolCount )
284+ return nil
285+ }
286+ if ! os .IsNotExist (err ) {
287+ fmt .Printf ("🛑 Tools.json could not be read: %v\n " , err )
288+ return defaultErr
289+ }
290+
291+ ctx , cancel := context .WithTimeout (context .Background (), 60 * time .Second )
292+ defer cancel ()
293+
294+ // Remote tools are valid
295+ remoteTools , err := mcp .RemoteTools (ctx , server )
296+ if err != nil {
297+ fmt .Printf ("🛑 Remote tools could not be fetched (if auth is required, specify \n dynamic:\n tools: true\n ): %v\n " , err )
298+ return defaultErr
299+ }
300+
301+ toolCount := len (remoteTools )
302+
303+ fmt .Printf ("✅ Remote tools are valid. Found %d tools.\n " , toolCount )
304+ return nil
305+ }
306+
257307// check if servers with OAuth have dynamic tools enabled
258308func isOAuthDynamicValid (name string ) error {
259309 server , err := readServerYaml (name )
@@ -284,3 +334,48 @@ func readServerYaml(name string) (servers.Server, error) {
284334 }
285335 return server , nil
286336}
337+
338+ func readToolsJson (name string ) ([]mcp.Tool , error ) {
339+ path := filepath .Join ("servers" , name , "tools.json" )
340+ buf , err := os .ReadFile (path )
341+ if err != nil {
342+ return nil , err
343+ }
344+
345+ var tools []mcp.Tool
346+ if err := json .Unmarshal (buf , & tools ); err != nil {
347+ return nil , err
348+ }
349+
350+ return tools , nil
351+ }
352+
353+ func isPociValid (name string ) error {
354+ server , err := readServerYaml (name )
355+ if err != nil {
356+ return err
357+ }
358+
359+ if server .Type != "poci" {
360+ return nil
361+ }
362+
363+ for _ , tool := range server .Tools {
364+ if tool .Container .Image != "" {
365+ if err := pullPociImage (tool .Container .Image ); err != nil {
366+ fmt .Printf ("🛑 Could not pull poci image %s: %v\n " , tool .Container .Image , err )
367+ return err
368+ }
369+ }
370+ }
371+
372+ fmt .Println ("✅ Poci image is valid" )
373+ return nil
374+ }
375+
376+ func pullPociImage (image string ) error {
377+ cmd := exec .Command ("docker" , "pull" , image )
378+ cmd .Stdout = os .Stdout
379+ cmd .Stderr = os .Stderr
380+ return cmd .Run ()
381+ }
0 commit comments