diff --git a/.github/workflows/build-and-publish.yml b/.github/workflows/build-and-publish.yml index fd758182..77002598 100644 --- a/.github/workflows/build-and-publish.yml +++ b/.github/workflows/build-and-publish.yml @@ -69,6 +69,9 @@ jobs: - name: Validate registry entries run: task validate + - name: Validate catalog entries + run: task catalog:validate + build-and-release: name: Build and Release Registry runs-on: ubuntu-latest @@ -101,6 +104,41 @@ jobs: echo "Registry built successfully with $TOTAL_COUNT entries ($CONTAINER_COUNT container-based, $REMOTE_COUNT remote)" echo "Both ToolHive and official MCP formats generated" + - name: Build catalog files (from server.json) + run: | + task catalog:build + # Copy catalog output alongside legacy output for comparison + for registry_dir in build/*/; do + registry_name=$(basename "$registry_dir") + # Skip if not a catalog output directory (catalog outputs to build//) + [ -f "$registry_dir/registry.json" ] || continue + cp "$registry_dir/registry.json" "dist/catalog-${registry_name}-registry.json" + cp "$registry_dir/official-registry.json" "dist/catalog-${registry_name}-official-registry.json" + done + echo "Catalog built successfully" + + - name: Compare legacy and catalog outputs + run: | + echo "Comparing legacy and catalog outputs..." + # Compare toolhive format (ignore last_updated timestamp) + if diff <(jq 'del(.last_updated)' dist/registry.json) \ + <(jq 'del(.last_updated)' dist/catalog-toolhive-registry.json) > /dev/null 2>&1; then + echo "✅ ToolHive format: legacy and catalog outputs match" + else + echo "⚠️ ToolHive format: differences detected between legacy and catalog" + diff <(jq 'del(.last_updated)' dist/registry.json) \ + <(jq 'del(.last_updated)' dist/catalog-toolhive-registry.json) | head -30 || true + fi + # Compare upstream format (ignore meta.last_updated timestamp) + if diff <(jq 'del(.meta.last_updated)' dist/official-registry.json) \ + <(jq 'del(.meta.last_updated)' dist/catalog-toolhive-official-registry.json) > /dev/null 2>&1; then + echo "✅ Upstream format: legacy and catalog outputs match" + else + echo "⚠️ Upstream format: differences detected between legacy and catalog" + diff <(jq 'del(.meta.last_updated)' dist/official-registry.json) \ + <(jq 'del(.meta.last_updated)' dist/catalog-toolhive-official-registry.json) | head -30 || true + fi + - name: Validate JSON files run: | echo "Validating ToolHive format..." @@ -204,13 +242,18 @@ jobs: md5sum registry.json > registry.json.md5 sha256sum official-registry.json > official-registry.json.sha256 md5sum official-registry.json > official-registry.json.md5 + for f in catalog-*.json; do + sha256sum "$f" > "$f.sha256" + md5sum "$f" > "$f.md5" + done - name: Create tarball run: | cd dist tar -czf registry-${{ steps.metadata.outputs.version }}.tar.gz \ registry.json registry.json.sha256 registry.json.md5 \ - official-registry.json official-registry.json.sha256 official-registry.json.md5 + official-registry.json official-registry.json.sha256 official-registry.json.md5 \ + catalog-*.json catalog-*.json.sha256 catalog-*.json.md5 tar -tzf registry-${{ steps.metadata.outputs.version }}.tar.gz - name: Check if release exists @@ -312,6 +355,9 @@ jobs: dist/official-registry.json dist/official-registry.json.sha256 dist/official-registry.json.md5 + dist/catalog-*.json + dist/catalog-*.json.sha256 + dist/catalog-*.json.md5 dist/registry-${{ steps.metadata.outputs.version }}.tar.gz makeLatest: true artifactErrorsFailBuild: true @@ -328,8 +374,11 @@ jobs: gh release delete-asset "v${{ steps.metadata.outputs.version }}" official-registry.json --yes || true gh release delete-asset "v${{ steps.metadata.outputs.version }}" official-registry.json.sha256 --yes || true gh release delete-asset "v${{ steps.metadata.outputs.version }}" official-registry.json.md5 --yes || true + for f in catalog-*.json catalog-*.json.sha256 catalog-*.json.md5; do + gh release delete-asset "v${{ steps.metadata.outputs.version }}" "$(basename "$f")" --yes || true + done gh release delete-asset "v${{ steps.metadata.outputs.version }}" "registry-${{ steps.metadata.outputs.version }}.tar.gz" --yes || true - + # Upload new assets gh release upload "v${{ steps.metadata.outputs.version }}" \ dist/registry.json \ @@ -338,6 +387,9 @@ jobs: dist/official-registry.json \ dist/official-registry.json.sha256 \ dist/official-registry.json.md5 \ + dist/catalog-*.json \ + dist/catalog-*.json.sha256 \ + dist/catalog-*.json.md5 \ "dist/registry-${{ steps.metadata.outputs.version }}.tar.gz" \ --clobber @@ -359,13 +411,36 @@ jobs: go-version-file: 'go.mod' cache: true - - name: Build registry-builder - run: go build -o registry-builder ./cmd/registry-builder + - name: Install Task + uses: arduino/setup-task@v2 - - name: Build registry.json + - name: Build registry files (legacy) run: | mkdir -p build - ./registry-builder build -v + task build:registry + + - name: Build catalog files (from server.json) + run: task catalog:build + + - name: Compare legacy and catalog outputs + run: | + echo "Comparing legacy and catalog outputs..." + if diff <(jq 'del(.last_updated)' build/registry.json) \ + <(jq 'del(.last_updated)' build/toolhive/registry.json) > /dev/null 2>&1; then + echo "✅ ToolHive format: legacy and catalog outputs match" + else + echo "⚠️ ToolHive format: differences detected" + diff <(jq 'del(.last_updated)' build/registry.json) \ + <(jq 'del(.last_updated)' build/toolhive/registry.json) | head -30 || true + fi + if diff <(jq 'del(.meta.last_updated)' build/official-registry.json) \ + <(jq 'del(.meta.last_updated)' build/toolhive/official-registry.json) > /dev/null 2>&1; then + echo "✅ Upstream format: legacy and catalog outputs match" + else + echo "⚠️ Upstream format: differences detected" + diff <(jq 'del(.meta.last_updated)' build/official-registry.json) \ + <(jq 'del(.meta.last_updated)' build/toolhive/official-registry.json) | head -30 || true + fi - name: Generate PR comment run: | @@ -373,7 +448,7 @@ jobs: REMOTE_COUNT=$(jq '.remote_servers | length // 0' build/registry.json) TOTAL=$((CONTAINER_COUNT + REMOTE_COUNT)) SIZE=$(du -h build/registry.json | cut -f1) - + echo "## 📦 Registry Build Preview" > pr-comment.md echo "" >> pr-comment.md echo "✅ Registry built successfully!" >> pr-comment.md @@ -390,5 +465,8 @@ jobs: uses: actions/upload-artifact@v6 with: name: pr-registry-json - path: build/registry.json + path: | + build/registry.json + build/toolhive/registry.json + build/toolhive/official-registry.json retention-days: 7 diff --git a/Taskfile.yml b/Taskfile.yml index 39ba491f..c7a1bb6e 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -26,7 +26,7 @@ tasks: build: desc: Build all binaries - deps: [build:registry-builder, build:import-tool, build:update-tools] + deps: [build:registry-builder, build:catalog, build:import-tool, build:update-tools] build:registry-builder: desc: Build the registry-builder tool @@ -42,6 +42,20 @@ tasks: generates: - "{{.BUILD_DIR}}/registry-builder" + build:catalog: + desc: Build the catalog tool + cmds: + - echo "🔨 Building catalog..." + - mkdir -p {{.BUILD_DIR}} + - go build {{.LDFLAGS}} -o {{.BUILD_DIR}}/catalog ./cmd/catalog + sources: + - cmd/catalog/**/*.go + - internal/**/*.go + - go.mod + - go.sum + generates: + - "{{.BUILD_DIR}}/catalog" + build:import-tool: desc: Build the import-from-toolhive tool cmds: @@ -188,6 +202,25 @@ tasks: generates: - "{{.BUILD_DIR}}/official-registry.json" + catalog:build: + desc: Build registry files from server.json using catalog (both formats) + deps: [build:catalog] + cmds: + - echo "🏗️ Building catalog (both formats)..." + - ./{{.BUILD_DIR}}/catalog build --format all -v + sources: + - registries/**/server.json + generates: + - "{{.BUILD_DIR}}/*/registry.json" + - "{{.BUILD_DIR}}/*/official-registry.json" + + catalog:validate: + desc: Validate all server.json entries via catalog + deps: [build:catalog] + cmds: + - echo "✅ Validating catalog entries..." + - ./{{.BUILD_DIR}}/catalog validate -v + test: desc: Run tests cmds: diff --git a/cmd/catalog/main.go b/cmd/catalog/main.go new file mode 100644 index 00000000..1ffb5d4b --- /dev/null +++ b/cmd/catalog/main.go @@ -0,0 +1,269 @@ +// Package main provides the catalog CLI tool for building registry files +// from server.json entries. It discovers registries under a root directory +// and produces output for each one. +package main + +import ( + "fmt" + "os" + "path/filepath" + "strings" + + "github.com/spf13/cobra" + + internalregistry "github.com/stacklok/toolhive-registry/internal/registry" +) + +const ( + formatToolhive = "toolhive" + formatUpstream = "upstream" + formatAll = "all" + + defaultRegistries = "registries" + defaultOutputDir = "build" + + // Each registry is expected to have a "servers" subdirectory containing + // individual server directories with server.json files. + serversSubdir = "servers" +) + +var ( + version = "dev" + commit = "unknown" + date = "unknown" +) + +var ( + registriesDir string + outputDir string + format string + verbose bool +) + +var rootCmd = &cobra.Command{ + Use: "catalog", + Short: "Build the ToolHive catalog from server.json files", + Long: `catalog discovers registries under a root directory and builds +registry files from individual server.json entries for each one. + +Given a registries directory (default: registries/), it looks for +subdirectories containing a "servers/" folder with server.json files: + + registries/ + toolhive/ + servers/ + github/server.json + ... + +For each registry found, it produces output in the build directory: + + build/ + toolhive/ + registry.json + official-registry.json`, +} + +var buildCmd = &cobra.Command{ + Use: "build", + Short: "Build registry files for all discovered registries", + RunE: runBuild, +} + +var validateCmd = &cobra.Command{ + Use: "validate", + Short: "Validate all server.json entries across all registries", + RunE: runValidate, +} + +var versionCmd = &cobra.Command{ + Use: "version", + Short: "Print version information", + Run: func(*cobra.Command, []string) { + fmt.Printf("catalog %s\n", version) + fmt.Printf(" commit: %s\n", commit) + fmt.Printf(" built: %s\n", date) + }, +} + +func init() { + rootCmd.PersistentFlags().StringVarP( + ®istriesDir, "registries", "r", defaultRegistries, "Path to the registries root directory", + ) + rootCmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "Enable verbose output") + + buildCmd.Flags().StringVarP(&outputDir, "output-dir", "o", defaultOutputDir, "Output directory") + buildCmd.Flags().StringVarP(&format, "format", "f", formatAll, + fmt.Sprintf("Output format (%s, %s, %s)", formatToolhive, formatUpstream, formatAll)) + + rootCmd.AddCommand(buildCmd) + rootCmd.AddCommand(validateCmd) + rootCmd.AddCommand(versionCmd) + rootCmd.AddCommand(updateMetadataCmd) + rootCmd.AddCommand(updateToolsCmd) +} + +func main() { + if err := rootCmd.Execute(); err != nil { + fmt.Fprintf(os.Stderr, "Error: %v\n", err) + os.Exit(1) + } +} + +// registryInfo holds the name and loader for a discovered registry. +type registryInfo struct { + name string + loader *internalregistry.Loader +} + +// discoverRegistries walks the registries root directory, finds subdirectories +// that contain a "servers/" folder, and returns a loader for each. +func discoverRegistries() ([]registryInfo, error) { + entries, err := os.ReadDir(registriesDir) + if err != nil { + return nil, fmt.Errorf("failed to read registries directory %s: %w", registriesDir, err) + } + + var registries []registryInfo + for _, entry := range entries { + if !entry.IsDir() || strings.HasPrefix(entry.Name(), ".") { + continue + } + + serversPath := filepath.Join(registriesDir, entry.Name(), serversSubdir) + info, err := os.Stat(serversPath) + if err != nil || !info.IsDir() { + if verbose { + fmt.Printf("Skipping %s (no %s/ directory)\n", entry.Name(), serversSubdir) + } + continue + } + + loader := internalregistry.NewLoader(serversPath) + if err := loader.LoadAll(); err != nil { + return nil, fmt.Errorf("failed to load registry %q: %w", entry.Name(), err) + } + + if verbose { + fmt.Printf("Discovered registry %q with %d entries\n", entry.Name(), len(loader.GetEntries())) + } + + registries = append(registries, registryInfo{ + name: entry.Name(), + loader: loader, + }) + } + + if len(registries) == 0 { + return nil, fmt.Errorf("no registries found under %s", registriesDir) + } + + return registries, nil +} + +func runBuild(_ *cobra.Command, _ []string) error { + registries, err := discoverRegistries() + if err != nil { + return err + } + + formats := determineFormats(format) + + for _, reg := range registries { + regOutputDir := filepath.Join(outputDir, reg.name) + if err := os.MkdirAll(regOutputDir, 0750); err != nil { + return fmt.Errorf("failed to create output directory %s: %w", regOutputDir, err) + } + + for _, f := range formats { + if err := buildFormat(reg.loader, f, regOutputDir); err != nil { + return fmt.Errorf("failed to build %s format for registry %q: %w", f, reg.name, err) + } + } + + fmt.Printf("Built registry %q: %d entries [%s] -> %s\n", + reg.name, len(reg.loader.GetEntries()), strings.Join(formats, ", "), regOutputDir) + } + + return nil +} + +func runValidate(_ *cobra.Command, _ []string) error { + registries, err := discoverRegistries() + if err != nil { + return err + } + + for _, reg := range registries { + upstreamBuilder := internalregistry.NewBuilder(reg.loader) + if err := upstreamBuilder.ValidateAgainstSchema(); err != nil { + return fmt.Errorf("registry %q: upstream validation failed: %w", reg.name, err) + } + if verbose { + fmt.Printf(" %s upstream format: valid\n", reg.name) + } + + legacyBuilder := internalregistry.NewLegacyBuilder(reg.loader) + if err := legacyBuilder.ValidateAgainstSchema(); err != nil { + return fmt.Errorf("registry %q: toolhive validation failed: %w", reg.name, err) + } + if verbose { + fmt.Printf(" %s toolhive format: valid\n", reg.name) + } + + fmt.Printf("Registry %q: all %d entries valid (both formats)\n", reg.name, len(reg.loader.GetEntries())) + } + + return nil +} + +func determineFormats(f string) []string { + switch strings.ToLower(f) { + case formatAll: + return []string{formatToolhive, formatUpstream} + case formatUpstream: + return []string{formatUpstream} + case formatToolhive: + return []string{formatToolhive} + default: + return []string{formatAll} + } +} + +func buildFormat(loader *internalregistry.Loader, f string, outDir string) error { + switch f { + case formatToolhive: + return buildToolhive(loader, outDir) + case formatUpstream: + return buildUpstream(loader, outDir) + default: + return fmt.Errorf("unknown format: %s", f) + } +} + +func buildToolhive(loader *internalregistry.Loader, outDir string) error { + builder := internalregistry.NewLegacyBuilder(loader) + outPath := filepath.Join(outDir, "registry.json") + + if err := builder.WriteJSON(outPath); err != nil { + return fmt.Errorf("failed to write toolhive registry: %w", err) + } + + if verbose { + fmt.Printf(" wrote %s\n", outPath) + } + return nil +} + +func buildUpstream(loader *internalregistry.Loader, outDir string) error { + builder := internalregistry.NewBuilder(loader) + outPath := filepath.Join(outDir, "official-registry.json") + + if err := builder.WriteJSON(outPath); err != nil { + return fmt.Errorf("failed to write upstream registry: %w", err) + } + + if verbose { + fmt.Printf(" wrote %s\n", outPath) + } + return nil +} diff --git a/cmd/catalog/update_metadata.go b/cmd/catalog/update_metadata.go new file mode 100644 index 00000000..6e4329af --- /dev/null +++ b/cmd/catalog/update_metadata.go @@ -0,0 +1,142 @@ +package main + +import ( + "context" + "fmt" + "net/http" + "os" + "time" + + "github.com/spf13/cobra" + "github.com/stacklok/toolhive/pkg/container/verifier" + "github.com/stacklok/toolhive/pkg/registry/converters" + toolhiveRegistry "github.com/stacklok/toolhive/pkg/registry/registry" + + "github.com/stacklok/toolhive-registry/internal/metadata" + "github.com/stacklok/toolhive-registry/internal/serverjson" +) + +var ( + dryRunMetadata bool + githubToken string + verifyProvenance bool +) + +var updateMetadataCmd = &cobra.Command{ + Use: "update-metadata ", + Short: "Update GitHub stars and last_updated for a server.json file", + Long: `Fetches the latest GitHub stars for the specified server.json file +and updates its _meta extensions. Optionally verifies provenance.`, + Args: cobra.ExactArgs(1), + RunE: runUpdateMetadata, +} + +func init() { + updateMetadataCmd.Flags().BoolVarP( + &dryRunMetadata, "dry-run", "d", false, + "Show changes without writing", + ) + updateMetadataCmd.Flags().StringVarP( + &githubToken, "github-token", "t", "", + "GitHub API token (or set GITHUB_TOKEN env var)", + ) + updateMetadataCmd.Flags().BoolVar( + &verifyProvenance, "verify-provenance", false, + "Verify provenance before updating", + ) +} + +func runUpdateMetadata(_ *cobra.Command, args []string) error { + ctx := context.Background() + path := args[0] + + token := githubToken + if token == "" { + token = os.Getenv("GITHUB_TOKEN") + } + + sf, err := serverjson.LoadServerFile(path) + if err != nil { + return err + } + + ext, err := sf.GetExtensions() + if err != nil { + return err + } + + if ext.Metadata == nil { + ext.Metadata = &toolhiveRegistry.Metadata{} + } + + if verifyProvenance { + if err := verifyServerProvenance(sf, ext); err != nil { + return fmt.Errorf("provenance verification failed: %w", err) + } + } + + httpClient := &http.Client{Timeout: 10 * time.Second} + fetcher := metadata.NewFetcher(httpClient, token) + + repoURL := sf.RepositoryURL() + newStars, err := fetcher.FetchStars(ctx, repoURL) + if err != nil { + fmt.Printf("Warning: failed to fetch stars: %v\n", err) + newStars = ext.Metadata.Stars + } + + if dryRunMetadata { + fmt.Printf("[DRY RUN] %s: stars %d -> %d\n", + sf.ServerJSON.Name, ext.Metadata.Stars, newStars) + return nil + } + + fmt.Printf("Updating %s: stars %d -> %d\n", + sf.ServerJSON.Name, ext.Metadata.Stars, newStars) + + ext.Metadata.Stars = newStars + ext.Metadata.LastUpdated = time.Now().UTC().Format(time.RFC3339) + + return sf.UpdateExtensions(ext) +} + +func verifyServerProvenance( + sf *serverjson.ServerFile, + ext *toolhiveRegistry.ServerExtensions, +) error { + if ext.Provenance == nil { + if verbose { + fmt.Printf("No provenance information, skipping verification\n") + } + return nil + } + + if !sf.IsPackageServer() { + return fmt.Errorf("provenance verification is only supported for package servers") + } + + imgMeta, err := converters.ServerJSONToImageMetadata(&sf.ServerJSON) + if err != nil { + return fmt.Errorf("failed to convert for verification: %w", err) + } + + v, err := verifier.New(imgMeta) + if err != nil { + return fmt.Errorf("failed to create verifier: %w", err) + } + + isVerified, err := v.VerifyServer(imgMeta.Image, imgMeta) + if err != nil { + return fmt.Errorf("verification failed: %w", err) + } + + if !isVerified { + return fmt.Errorf("no verified signatures found") + } + + if verbose { + fmt.Println("Provenance verified successfully") + } + + return nil +} diff --git a/cmd/catalog/update_tools.go b/cmd/catalog/update_tools.go new file mode 100644 index 00000000..df9573f9 --- /dev/null +++ b/cmd/catalog/update_tools.go @@ -0,0 +1,186 @@ +package main + +import ( + "fmt" + "path/filepath" + "slices" + "sort" + "time" + + "github.com/google/go-cmp/cmp" + "github.com/spf13/cobra" + toolhiveRegistry "github.com/stacklok/toolhive/pkg/registry/registry" + + "github.com/stacklok/toolhive-registry/internal/serverjson" + "github.com/stacklok/toolhive-registry/internal/thvclient" +) + +var ( + dryRunTools bool + thvPath string +) + +var updateToolsCmd = &cobra.Command{ + Use: "update-tools ", + Short: "Update tools list by querying the running MCP server", + Long: `Starts a temporary MCP server from the server.json package definition, +queries it for available tools, and updates the tools list in the _meta extensions.`, + Args: cobra.ExactArgs(1), + RunE: runUpdateTools, +} + +func init() { + updateToolsCmd.Flags().BoolVarP( + &dryRunTools, "dry-run", "d", false, + "Show changes without writing", + ) + updateToolsCmd.Flags().StringVar( + &thvPath, "thv-path", "", + "Path to thv binary (searches PATH if empty)", + ) +} + +func runUpdateTools(_ *cobra.Command, args []string) error { + path := args[0] + + sf, err := serverjson.LoadServerFile(path) + if err != nil { + return err + } + + ext, err := sf.GetExtensions() + if err != nil { + return err + } + + currentTools := ext.Tools + serverName := filepath.Base(filepath.Dir(path)) + + if verbose { + fmt.Printf("Processing server: %s\n", serverName) + fmt.Printf("Current tools count: %d\n", len(currentTools)) + } + + newTools, err := fetchToolsFromServer(sf, serverName) + if err != nil { + return err + } + + return applyToolsUpdate(sf, ext, currentTools, newTools) +} + +// fetchToolsFromServer starts a temporary MCP server and queries its tools. +func fetchToolsFromServer( + sf *serverjson.ServerFile, serverName string, +) ([]string, error) { + client, err := thvclient.NewClient(thvPath, verbose) + if err != nil { + return nil, fmt.Errorf("failed to create thv client: %w", err) + } + + fmt.Printf("Starting temporary MCP server: %s\n", serverName) + tempName, err := client.RunServer(sf, serverName) + if err != nil { + return nil, fmt.Errorf("failed to run server: %w", err) + } + defer func() { + if stopErr := client.StopServer(tempName); stopErr != nil { + fmt.Printf( + "Warning: failed to stop server %s: %v\n", + tempName, stopErr, + ) + } + }() + + tools, err := client.ListTools(tempName) + if err != nil { + if logs, logErr := client.Logs(tempName); logErr == nil && logs != "" { + fmt.Printf("Server logs:\n%s\n", logs) + } + return nil, fmt.Errorf("failed to fetch tools: %w", err) + } + + fmt.Printf("Discovered %d tools\n", len(tools)) + return tools, nil +} + +// applyToolsUpdate compares and writes the updated tools list. +func applyToolsUpdate( + sf *serverjson.ServerFile, + ext *toolhiveRegistry.ServerExtensions, + currentTools, newTools []string, +) error { + if len(newTools) == 0 && len(currentTools) > 0 { + fmt.Printf( + "Warning: no tools detected but had %d previously. Keeping existing.\n", + len(currentTools), + ) + return fmt.Errorf("empty tools list detected") + } + + sort.Strings(currentTools) + sort.Strings(newTools) + + if slices.Equal(currentTools, newTools) { + fmt.Println("Tools list is already up to date") + return nil + } + + printToolsDiff(currentTools, newTools) + + if dryRunTools { + fmt.Println("[DRY RUN] Would update tools list") + return nil + } + + ext.Tools = newTools + if ext.Metadata == nil { + ext.Metadata = &toolhiveRegistry.Metadata{} + } + ext.Metadata.LastUpdated = time.Now().UTC().Format(time.RFC3339) + + if err := sf.UpdateExtensions(ext); err != nil { + return fmt.Errorf("failed to write server.json: %w", err) + } + + fmt.Println("Successfully updated tools list") + return nil +} + +func printToolsDiff(currentTools, newTools []string) { + if verbose { + diff := cmp.Diff(currentTools, newTools) + if diff != "" { + fmt.Printf("Diff:\n%s\n", diff) + } + } else { + showToolsSummary(currentTools, newTools) + } +} + +func showToolsSummary(current, newTools []string) { + added := diffSlices(newTools, current) + removed := diffSlices(current, newTools) + + if len(added) > 0 { + fmt.Printf(" Added (%d): %v\n", len(added), added) + } + if len(removed) > 0 { + fmt.Printf(" Removed (%d): %v\n", len(removed), removed) + } +} + +// diffSlices returns items in a that are not in b. +func diffSlices(a, b []string) []string { + set := make(map[string]struct{}, len(b)) + for _, v := range b { + set[v] = struct{}{} + } + var result []string + for _, v := range a { + if _, ok := set[v]; !ok { + result = append(result, v) + } + } + return result +} diff --git a/cmd/migrate/main.go b/cmd/migrate/main.go new file mode 100644 index 00000000..285f2bbd --- /dev/null +++ b/cmd/migrate/main.go @@ -0,0 +1,190 @@ +// Package main provides a one-time migration tool that converts spec.yaml files +// from the legacy registry format into individual server.json files using the +// upstream MCP ServerJSON format. +package main + +import ( + "encoding/json" + "fmt" + "log" + "os" + "path/filepath" + "sort" + "strings" + + upstream "github.com/modelcontextprotocol/registry/pkg/api/v0" + "github.com/spf13/cobra" + "github.com/stacklok/toolhive/pkg/registry/converters" + + "github.com/stacklok/toolhive-registry/pkg/legacy/registry" + "github.com/stacklok/toolhive-registry/pkg/legacy/types" +) + +var ( + source string + target string + dryRun bool + verbose bool +) + +var rootCmd = &cobra.Command{ + Use: "migrate", + Short: "Migrate spec.yaml files to server.json format", + Long: `migrate reads existing spec.yaml files from the legacy registry directory +and writes individual server.json files in the upstream MCP ServerJSON format. + +Output structure: //server.json`, + RunE: runMigrate, +} + +func init() { + rootCmd.Flags().StringVar(&source, "source", "registry", "Path to existing spec.yaml directory") + rootCmd.Flags().StringVar(&target, "target", "registries/toolhive/servers", "Output directory for server.json files") + rootCmd.Flags().BoolVar(&dryRun, "dry-run", false, "Show what would be written without writing") + rootCmd.Flags().BoolVar(&verbose, "verbose", false, "Log each conversion") +} + +func main() { + if err := rootCmd.Execute(); err != nil { + os.Exit(1) + } +} + +func runMigrate(_ *cobra.Command, _ []string) error { + loader := registry.NewLoader(source) + if err := loader.LoadAll(); err != nil { + return fmt.Errorf("failed to load registry entries: %w", err) + } + + entries := loader.GetEntries() + if len(entries) == 0 { + return fmt.Errorf("no entries found in %s", source) + } + + // Sort names for deterministic output + names := make([]string, 0, len(entries)) + for name := range entries { + names = append(names, name) + } + sort.Strings(names) + + var converted, errCount int + var errors []string + + for _, name := range names { + entry := entries[name] + + serverJSON, err := convertEntry(name, entry) + if err != nil { + errCount++ + msg := fmt.Sprintf(" SKIP %s: %v", name, err) + errors = append(errors, msg) + if verbose { + log.Println(msg) + } + continue + } + + if err := writeServerJSON(name, serverJSON); err != nil { + errCount++ + msg := fmt.Sprintf(" FAIL %s: %v", name, err) + errors = append(errors, msg) + if verbose { + log.Println(msg) + } + continue + } + + converted++ + if verbose { + log.Printf(" OK %s", name) + } + } + + // Summary + fmt.Printf("Migration complete: %d/%d converted", converted, len(entries)) + if dryRun { + fmt.Print(" (dry-run)") + } + fmt.Println() + + if errCount > 0 { + fmt.Printf("Errors: %d\n", errCount) + for _, e := range errors { + fmt.Println(e) + } + return fmt.Errorf("%d entries failed to convert", errCount) + } + + return nil +} + +func convertEntry(name string, entry *types.RegistryEntry) (*upstream.ServerJSON, error) { + var sj *upstream.ServerJSON + var err error + + switch { + case entry.IsImage(): + sj, err = converters.ImageMetadataToServerJSON(name, entry.ImageMetadata) + if err != nil { + return nil, fmt.Errorf("image conversion: %w", err) + } + if sj == nil { + return nil, fmt.Errorf("image conversion returned nil") + } + + case entry.IsRemote(): + sj, err = converters.RemoteServerMetadataToServerJSON(name, entry.RemoteServerMetadata) + if err != nil { + return nil, fmt.Errorf("remote conversion: %w", err) + } + if sj == nil { + return nil, fmt.Errorf("remote conversion returned nil") + } + + default: + return nil, fmt.Errorf("entry is neither image nor remote") + } + + // Convert name to reverse-DNS format (e.g. "github" -> "io.github.stacklok/github") + sj.Name = convertNameToReverseDNS(sj.Name) + + return sj, nil +} + +// convertNameToReverseDNS converts simple server names to reverse-DNS format. +func convertNameToReverseDNS(name string) string { + if strings.Contains(name, "/") { + return name + } + return "io.github.stacklok/" + name +} + +func writeServerJSON(name string, serverJSON *upstream.ServerJSON) error { + data, err := json.MarshalIndent(serverJSON, "", " ") + if err != nil { + return fmt.Errorf("marshal: %w", err) + } + // Ensure trailing newline + data = append(data, '\n') + + dir := filepath.Join(target, name) + path := filepath.Join(dir, "server.json") + + if dryRun { + if verbose { + log.Printf(" Would write %s (%d bytes)", path, len(data)) + } + return nil + } + + if err := os.MkdirAll(dir, 0750); err != nil { + return fmt.Errorf("mkdir: %w", err) + } + + if err := os.WriteFile(path, data, 0600); err != nil { + return fmt.Errorf("write: %w", err) + } + + return nil +} diff --git a/internal/metadata/fetcher.go b/internal/metadata/fetcher.go new file mode 100644 index 00000000..c3fe594d --- /dev/null +++ b/internal/metadata/fetcher.go @@ -0,0 +1,94 @@ +// Package metadata provides utilities for fetching server metadata +// (GitHub stars) from external APIs. +package metadata + +import ( + "context" + "encoding/json" + "fmt" + "io" + "net/http" + "strings" +) + +// HTTPClient is an interface for making HTTP requests, enabling testability. +type HTTPClient interface { + Do(req *http.Request) (*http.Response, error) +} + +// Fetcher retrieves metadata from external APIs. +type Fetcher struct { + client HTTPClient + githubToken string +} + +// NewFetcher creates a new Fetcher with the given HTTP client and optional GitHub token. +func NewFetcher(client HTTPClient, githubToken string) *Fetcher { + return &Fetcher{ + client: client, + githubToken: githubToken, + } +} + +// FetchStars returns the stargazers_count for a GitHub repository. +// The repoURL should be a GitHub repository URL like "https://github.com/owner/repo". +// Returns (0, nil) if the URL is empty or not a GitHub URL. +func (f *Fetcher) FetchStars(ctx context.Context, repoURL string) (int, error) { + if repoURL == "" { + return 0, nil + } + + if !strings.Contains(repoURL, "github.com") { + return 0, nil + } + + owner, repo, err := extractOwnerRepo(repoURL) + if err != nil { + return 0, fmt.Errorf("failed to parse repo URL: %w", err) + } + + apiURL := fmt.Sprintf("https://api.github.com/repos/%s/%s", owner, repo) + req, err := http.NewRequestWithContext(ctx, http.MethodGet, apiURL, nil) + if err != nil { + return 0, fmt.Errorf("failed to create request: %w", err) + } + + req.Header.Set("Accept", "application/vnd.github.v3+json") + if f.githubToken != "" { + req.Header.Set("Authorization", "token "+f.githubToken) + } + + resp, err := f.client.Do(req) + if err != nil { + return 0, fmt.Errorf("failed to send request: %w", err) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + body, _ := io.ReadAll(resp.Body) + return 0, fmt.Errorf( + "GitHub API returned %s: %s", resp.Status, string(body), + ) + } + + var repoInfo struct { + StargazersCount int `json:"stargazers_count"` + } + if err := json.NewDecoder(resp.Body).Decode(&repoInfo); err != nil { + return 0, fmt.Errorf("failed to parse response: %w", err) + } + + return repoInfo.StargazersCount, nil +} + +// extractOwnerRepo extracts the owner and repo from a GitHub repository URL. +func extractOwnerRepo(repoURL string) (string, string, error) { + repoURL = strings.TrimSuffix(repoURL, ".git") + parts := strings.Split(repoURL, "/") + if len(parts) < 2 { + return "", "", fmt.Errorf("invalid GitHub URL format: %s", repoURL) + } + owner := parts[len(parts)-2] + repo := parts[len(parts)-1] + return owner, repo, nil +} diff --git a/internal/metadata/fetcher_test.go b/internal/metadata/fetcher_test.go new file mode 100644 index 00000000..7561ebd7 --- /dev/null +++ b/internal/metadata/fetcher_test.go @@ -0,0 +1,179 @@ +package metadata + +import ( + "bytes" + "context" + "io" + "net/http" + "testing" +) + +type mockHTTPClient struct { + statusCode int + body string + err error +} + +func (m *mockHTTPClient) Do(_ *http.Request) (*http.Response, error) { + if m.err != nil { + return nil, m.err + } + return &http.Response{ + StatusCode: m.statusCode, + Body: io.NopCloser(bytes.NewBufferString(m.body)), + }, nil +} + +func TestFetchStars_Success(t *testing.T) { + t.Parallel() + mock := &mockHTTPClient{ + statusCode: http.StatusOK, + body: `{"stargazers_count": 42}`, + } + fetcher := NewFetcher(mock, "test-token") + + stars, err := fetcher.FetchStars(context.Background(), "https://github.com/owner/repo") + if err != nil { + t.Fatalf("FetchStars failed: %v", err) + } + if stars != 42 { + t.Errorf("expected 42 stars, got %d", stars) + } +} + +func TestFetchStars_EmptyURL(t *testing.T) { + t.Parallel() + fetcher := NewFetcher(nil, "") + + stars, err := fetcher.FetchStars(context.Background(), "") + if err != nil { + t.Fatalf("FetchStars failed: %v", err) + } + if stars != 0 { + t.Errorf("expected 0 stars for empty URL, got %d", stars) + } +} + +func TestFetchStars_NonGitHubURL(t *testing.T) { + t.Parallel() + fetcher := NewFetcher(nil, "") + + stars, err := fetcher.FetchStars(context.Background(), "https://gitlab.com/owner/repo") + if err != nil { + t.Fatalf("FetchStars failed: %v", err) + } + if stars != 0 { + t.Errorf("expected 0 stars for non-GitHub URL, got %d", stars) + } +} + +func TestFetchStars_APIError(t *testing.T) { + t.Parallel() + mock := &mockHTTPClient{ + statusCode: http.StatusForbidden, + body: `{"message": "rate limit exceeded"}`, + } + fetcher := NewFetcher(mock, "") + + _, err := fetcher.FetchStars(context.Background(), "https://github.com/owner/repo") + if err == nil { + t.Fatal("expected error for API error response") + } +} + +func TestFetchStars_NetworkError(t *testing.T) { + t.Parallel() + mock := &mockHTTPClient{ + err: io.ErrUnexpectedEOF, + } + fetcher := NewFetcher(mock, "") + + _, err := fetcher.FetchStars(context.Background(), "https://github.com/owner/repo") + if err == nil { + t.Fatal("expected error for network failure") + } +} + +func TestFetchStars_InvalidJSON(t *testing.T) { + t.Parallel() + mock := &mockHTTPClient{ + statusCode: http.StatusOK, + body: `not json`, + } + fetcher := NewFetcher(mock, "") + + _, err := fetcher.FetchStars(context.Background(), "https://github.com/owner/repo") + if err == nil { + t.Fatal("expected error for invalid JSON") + } +} + +func TestFetchStars_WithToken(t *testing.T) { + t.Parallel() + var capturedReq *http.Request + mock := &mockHTTPClient{ + statusCode: http.StatusOK, + body: `{"stargazers_count": 10}`, + } + + // Wrap to capture request + fetcher := NewFetcher(&requestCapture{ + client: mock, + lastReq: &capturedReq, + }, "my-secret-token") + + _, err := fetcher.FetchStars(context.Background(), "https://github.com/owner/repo") + if err != nil { + t.Fatalf("FetchStars failed: %v", err) + } + + if capturedReq == nil { + t.Fatal("request was not captured") + } + auth := capturedReq.Header.Get("Authorization") + if auth != "token my-secret-token" { + t.Errorf("expected Authorization header, got %q", auth) + } +} + +func TestExtractOwnerRepo(t *testing.T) { + t.Parallel() + tests := []struct { + url string + expectedOwner string + expectedRepo string + expectErr bool + }{ + {"https://github.com/owner/repo", "owner", "repo", false}, + {"https://github.com/owner/repo.git", "owner", "repo", false}, + {"https://github.com/org/my-repo", "org", "my-repo", false}, + {"invalid", "", "", true}, + } + + for _, tt := range tests { + owner, repo, err := extractOwnerRepo(tt.url) + if tt.expectErr && err == nil { + t.Errorf("expected error for %s", tt.url) + } + if !tt.expectErr && err != nil { + t.Errorf("unexpected error for %s: %v", tt.url, err) + } + if owner != tt.expectedOwner { + t.Errorf("for %s: expected owner %s, got %s", tt.url, tt.expectedOwner, owner) + } + if repo != tt.expectedRepo { + t.Errorf("for %s: expected repo %s, got %s", tt.url, tt.expectedRepo, repo) + } + } +} + +// requestCapture wraps an HTTPClient to capture the request. +type requestCapture struct { + client HTTPClient + lastReq **http.Request +} + +func (rc *requestCapture) Do(req *http.Request) (*http.Response, error) { + *rc.lastReq = req + return rc.client.Do(req) +} diff --git a/internal/registry/builder.go b/internal/registry/builder.go new file mode 100644 index 00000000..7f9b7df2 --- /dev/null +++ b/internal/registry/builder.go @@ -0,0 +1,93 @@ +package registry + +import ( + "encoding/json" + "fmt" + "os" + "path/filepath" + "time" + + upstream "github.com/modelcontextprotocol/registry/pkg/api/v0" + toolhiveRegistry "github.com/stacklok/toolhive/pkg/registry" + "github.com/stacklok/toolhive/pkg/registry/registry" +) + +// Builder assembles an UpstreamRegistry from loaded ServerJSON entries. +type Builder struct { + loader *Loader +} + +// NewBuilder creates a new Builder backed by the given Loader. +func NewBuilder(loader *Loader) *Builder { + return &Builder{ + loader: loader, + } +} + +// Build creates the UpstreamRegistry structure from loaded entries. +// Servers are ordered by directory name for deterministic output. +func (b *Builder) Build() *registry.UpstreamRegistry { + names := b.loader.GetSortedNames() + + servers := make([]upstream.ServerJSON, 0, len(names)) + for _, name := range names { + servers = append(servers, b.loader.GetEntries()[name]) + } + + return ®istry.UpstreamRegistry{ + Schema: "https://raw.githubusercontent.com/stacklok/toolhive/main/pkg/registry/data/upstream-registry.schema.json", + Version: "1.0.0", + Meta: registry.UpstreamMeta{ + LastUpdated: time.Now().UTC().Format(time.RFC3339), + }, + Data: registry.UpstreamData{ + Servers: servers, + Groups: []registry.UpstreamGroup{}, + }, + } +} + +// WriteJSON builds the registry, validates it, and writes JSON to the given path. +func (b *Builder) WriteJSON(path string) error { + builtRegistry := b.Build() + + if err := validateRegistry(builtRegistry); err != nil { + return fmt.Errorf("schema validation failed: %w", err) + } + + dir := filepath.Dir(path) + if err := os.MkdirAll(dir, 0750); err != nil { + return fmt.Errorf("failed to create directory: %w", err) + } + + data, err := json.MarshalIndent(builtRegistry, "", " ") + if err != nil { + return fmt.Errorf("failed to marshal JSON: %w", err) + } + + if err := os.WriteFile(path, data, 0600); err != nil { + return fmt.Errorf("failed to write file: %w", err) + } + + return nil +} + +// ValidateAgainstSchema builds the registry and validates it without writing. +func (b *Builder) ValidateAgainstSchema() error { + builtRegistry := b.Build() + return validateRegistry(builtRegistry) +} + +// validateRegistry validates a registry object against the upstream registry schema. +func validateRegistry(upstreamRegistry *registry.UpstreamRegistry) error { + registryJSON, err := json.Marshal(upstreamRegistry) + if err != nil { + return fmt.Errorf("failed to marshal registry: %w", err) + } + + if err := toolhiveRegistry.ValidateUpstreamRegistry(registryJSON); err != nil { + return fmt.Errorf("registry validation failed: %w", err) + } + + return nil +} diff --git a/internal/registry/builder_test.go b/internal/registry/builder_test.go new file mode 100644 index 00000000..98cea0ce --- /dev/null +++ b/internal/registry/builder_test.go @@ -0,0 +1,80 @@ +package registry + +import ( + "encoding/json" + "os" + "path/filepath" + "testing" + + "github.com/stacklok/toolhive/pkg/registry/registry" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func setupLoaderWithEntries(t *testing.T) *Loader { + t.Helper() + + dir := t.TempDir() + + require.NoError(t, os.MkdirAll(filepath.Join(dir, "alpha"), 0750)) + require.NoError(t, os.WriteFile(filepath.Join(dir, "alpha", "server.json"), []byte(minimalServerJSON), 0600)) + + require.NoError(t, os.MkdirAll(filepath.Join(dir, "beta"), 0750)) + require.NoError(t, os.WriteFile(filepath.Join(dir, "beta", "server.json"), []byte(minimalServerJSON2), 0600)) + + loader := NewLoader(dir) + require.NoError(t, loader.LoadAll()) + return loader +} + +func TestBuilder_Build(t *testing.T) { + t.Parallel() + + loader := setupLoaderWithEntries(t) + builder := NewBuilder(loader) + + result := builder.Build() + + assert.Equal(t, "https://raw.githubusercontent.com/stacklok/toolhive/main/pkg/registry/data/upstream-registry.schema.json", result.Schema) + assert.Equal(t, "1.0.0", result.Version) + assert.NotEmpty(t, result.Meta.LastUpdated) + assert.Len(t, result.Data.Servers, 2) + assert.Empty(t, result.Data.Groups) + + // Verify sorted order: alpha before beta + assert.Equal(t, "io.github.stacklok/test-server", result.Data.Servers[0].Name) + assert.Equal(t, "io.github.stacklok/another-server", result.Data.Servers[1].Name) +} + +func TestBuilder_WriteJSON(t *testing.T) { + t.Parallel() + + loader := setupLoaderWithEntries(t) + builder := NewBuilder(loader) + + outDir := t.TempDir() + outPath := filepath.Join(outDir, "output", "official-registry.json") + + err := builder.WriteJSON(outPath) + require.NoError(t, err) + + // Read the file back and verify it's valid JSON + data, err := os.ReadFile(outPath) + require.NoError(t, err) + + var reg registry.UpstreamRegistry + require.NoError(t, json.Unmarshal(data, ®)) + + assert.Equal(t, "1.0.0", reg.Version) + assert.Len(t, reg.Data.Servers, 2) +} + +func TestBuilder_ValidateAgainstSchema(t *testing.T) { + t.Parallel() + + loader := setupLoaderWithEntries(t) + builder := NewBuilder(loader) + + err := builder.ValidateAgainstSchema() + assert.NoError(t, err) +} diff --git a/internal/registry/legacy_builder.go b/internal/registry/legacy_builder.go new file mode 100644 index 00000000..81de4d0d --- /dev/null +++ b/internal/registry/legacy_builder.go @@ -0,0 +1,197 @@ +package registry + +import ( + "encoding/json" + "fmt" + "os" + "path/filepath" + "time" + + "github.com/stacklok/toolhive/pkg/permissions" + toolhiveRegistryPkg "github.com/stacklok/toolhive/pkg/registry" + "github.com/stacklok/toolhive/pkg/registry/converters" + toolhiveRegistry "github.com/stacklok/toolhive/pkg/registry/registry" +) + +const legacyRegistrySchema = "https://raw.githubusercontent.com/stacklok/toolhive/main/" + + "pkg/registry/data/toolhive-legacy-registry.schema.json" + +// LegacyBuilder assembles a legacy toolhive Registry from loaded ServerJSON entries. +// It converts each ServerJSON back to ImageMetadata or RemoteServerMetadata +// using the converters package. +type LegacyBuilder struct { + loader *Loader +} + +// NewLegacyBuilder creates a new LegacyBuilder backed by the given Loader. +func NewLegacyBuilder(loader *Loader) *LegacyBuilder { + return &LegacyBuilder{ + loader: loader, + } +} + +// Build creates the legacy toolhive Registry structure from loaded ServerJSON entries. +// Entries are processed in sorted order for deterministic output. +func (b *LegacyBuilder) Build() (*toolhiveRegistry.Registry, error) { + reg := &toolhiveRegistry.Registry{ + Version: "1.0.0", + LastUpdated: time.Now().UTC().Format(time.RFC3339), + Servers: make(map[string]*toolhiveRegistry.ImageMetadata), + RemoteServers: make(map[string]*toolhiveRegistry.RemoteServerMetadata), + } + + names := b.loader.GetSortedNames() + entries := b.loader.GetEntries() + + for _, name := range names { + server := entries[name] + + switch { + case len(server.Packages) > 0: + imgMeta, err := converters.ServerJSONToImageMetadata(&server) + if err != nil { + return nil, fmt.Errorf("failed to convert '%s' to image metadata: %w", name, err) + } + normalizeImageMetadata(imgMeta) + reg.Servers[name] = imgMeta + + case len(server.Remotes) > 0: + remoteMeta, err := converters.ServerJSONToRemoteServerMetadata(&server) + if err != nil { + return nil, fmt.Errorf("failed to convert '%s' to remote metadata: %w", name, err) + } + normalizeRemoteMetadata(remoteMeta) + reg.RemoteServers[name] = remoteMeta + } + } + + return reg, nil +} + +// registryWithSchema wraps the legacy Registry with a $schema field for JSON output. +type registryWithSchema struct { + Schema string `json:"$schema"` + *toolhiveRegistry.Registry +} + +// WriteJSON builds the legacy registry, validates it, and writes JSON to the given path. +func (b *LegacyBuilder) WriteJSON(path string) error { + reg, err := b.Build() + if err != nil { + return fmt.Errorf("failed to build registry: %w", err) + } + + if err := validateLegacyRegistry(reg); err != nil { + return fmt.Errorf("schema validation failed: %w", err) + } + + dir := filepath.Dir(path) + if err := os.MkdirAll(dir, 0750); err != nil { + return fmt.Errorf("failed to create directory: %w", err) + } + + wrapped := registryWithSchema{ + Schema: legacyRegistrySchema, + Registry: reg, + } + + data, err := json.MarshalIndent(wrapped, "", " ") + if err != nil { + return fmt.Errorf("failed to marshal JSON: %w", err) + } + + if err := os.WriteFile(path, data, 0600); err != nil { + return fmt.Errorf("failed to write file: %w", err) + } + + return nil +} + +// ValidateAgainstSchema builds the legacy registry and validates it without writing. +func (b *LegacyBuilder) ValidateAgainstSchema() error { + reg, err := b.Build() + if err != nil { + return fmt.Errorf("failed to build registry: %w", err) + } + return validateLegacyRegistry(reg) +} + +// validateLegacyRegistry validates a legacy registry against the toolhive schema. +func validateLegacyRegistry(reg *toolhiveRegistry.Registry) error { + registryJSON, err := json.Marshal(reg) + if err != nil { + return fmt.Errorf("failed to marshal registry: %w", err) + } + + if err := toolhiveRegistryPkg.ValidateRegistrySchema(registryJSON); err != nil { + return fmt.Errorf("registry validation failed: %w", err) + } + + return nil +} + +// normalizeImageMetadata applies defaults and initializes nil slices to empty +// so JSON output uses [] instead of null, matching the legacy builder behavior. +func normalizeImageMetadata(m *toolhiveRegistry.ImageMetadata) { + m.Name = "" + + if m.Tier == "" { + m.Tier = "Community" + } + if m.Status == "" { + m.Status = "Active" + } + if m.Tools == nil { + m.Tools = []string{} + } + if m.Tags == nil { + m.Tags = []string{} + } + if m.EnvVars == nil { + m.EnvVars = []*toolhiveRegistry.EnvVar{} + } + if m.Args == nil { + m.Args = []string{} + } + + if m.Permissions != nil { + if m.Permissions.Read == nil { + m.Permissions.Read = []permissions.MountDeclaration{} + } + if m.Permissions.Write == nil { + m.Permissions.Write = []permissions.MountDeclaration{} + } + if m.Permissions.Network != nil && m.Permissions.Network.Outbound != nil { + if m.Permissions.Network.Outbound.AllowHost == nil { + m.Permissions.Network.Outbound.AllowHost = []string{} + } + if m.Permissions.Network.Outbound.AllowPort == nil { + m.Permissions.Network.Outbound.AllowPort = []int{} + } + } + } +} + +// normalizeRemoteMetadata applies defaults and initializes nil slices to empty. +func normalizeRemoteMetadata(m *toolhiveRegistry.RemoteServerMetadata) { + m.Name = "" + + if m.Tier == "" { + m.Tier = "Community" + } + if m.Status == "" { + m.Status = "Active" + } + if m.Tools == nil { + m.Tools = []string{} + } + if m.Tags == nil { + m.Tags = []string{} + } + if m.EnvVars == nil { + m.EnvVars = []*toolhiveRegistry.EnvVar{} + } + if m.Headers == nil { + m.Headers = []*toolhiveRegistry.Header{} + } +} diff --git a/internal/registry/legacy_builder_test.go b/internal/registry/legacy_builder_test.go new file mode 100644 index 00000000..d5073575 --- /dev/null +++ b/internal/registry/legacy_builder_test.go @@ -0,0 +1,222 @@ +package registry + +import ( + "encoding/json" + "os" + "path/filepath" + "testing" + + toolhiveRegistry "github.com/stacklok/toolhive/pkg/registry/registry" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// Realistic container-based server.json (minimal but complete for converter) +const imageServerJSON = `{ + "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json", + "name": "io.github.stacklok/test-image", + "description": "A container-based test server", + "title": "test-image", + "repository": {"url": "https://github.com/example/test", "source": "github"}, + "version": "1.0.0", + "packages": [{ + "registryType": "oci", + "identifier": "ghcr.io/example/test:1.0.0", + "transport": {"type": "stdio"}, + "environmentVariables": [ + {"name": "API_KEY", "description": "The API key", "isRequired": true, "isSecret": true} + ] + }], + "_meta": { + "io.modelcontextprotocol.registry/publisher-provided": { + "io.github.stacklok": { + "ghcr.io/example/test:1.0.0": { + "status": "Active", + "tier": "Official", + "tools": ["do_thing"], + "tags": ["test"], + "metadata": {"stars": 10, "pulls": 5, "last_updated": "2026-01-01T00:00:00Z"}, + "permissions": { + "network": { + "outbound": {"insecure_allow_all": true} + } + } + } + } + } + } +}` + +// Realistic remote server.json (minimal but complete for converter) +const remoteServerJSON = `{ + "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json", + "name": "io.github.stacklok/test-remote", + "description": "A remote test server", + "title": "test-remote", + "repository": {"url": "https://github.com/example/remote", "source": "github"}, + "version": "1.0.0", + "remotes": [{ + "type": "streamable-http", + "url": "https://mcp.example.com/mcp" + }], + "_meta": { + "io.modelcontextprotocol.registry/publisher-provided": { + "io.github.stacklok": { + "https://mcp.example.com/mcp": { + "status": "Active", + "tier": "Official", + "tools": ["remote_tool"], + "tags": ["remote"], + "custom_metadata": {"author": "Example", "homepage": "https://example.com"} + } + } + } + } +}` + +func setupLegacyLoader(t *testing.T) *Loader { + t.Helper() + + dir := t.TempDir() + + require.NoError(t, os.MkdirAll(filepath.Join(dir, "test-image"), 0750)) + require.NoError(t, os.WriteFile(filepath.Join(dir, "test-image", "server.json"), []byte(imageServerJSON), 0600)) + + require.NoError(t, os.MkdirAll(filepath.Join(dir, "test-remote"), 0750)) + require.NoError(t, os.WriteFile(filepath.Join(dir, "test-remote", "server.json"), []byte(remoteServerJSON), 0600)) + + loader := NewLoader(dir) + require.NoError(t, loader.LoadAll()) + return loader +} + +func TestLegacyBuilder_Build(t *testing.T) { + t.Parallel() + + loader := setupLegacyLoader(t) + builder := NewLegacyBuilder(loader) + + reg, err := builder.Build() + require.NoError(t, err) + + assert.Equal(t, "1.0.0", reg.Version) + assert.NotEmpty(t, reg.LastUpdated) + + // Image server should be in Servers + assert.Len(t, reg.Servers, 1) + assert.Contains(t, reg.Servers, "test-image") + + imgMeta := reg.Servers["test-image"] + assert.Empty(t, imgMeta.Name, "name should be cleared — the map key serves as the name") + assert.Equal(t, "A container-based test server", imgMeta.Description) + assert.Equal(t, "stdio", imgMeta.Transport) + assert.Equal(t, "ghcr.io/example/test:1.0.0", imgMeta.Image) + assert.Equal(t, "Official", imgMeta.Tier) + assert.Equal(t, "Active", imgMeta.Status) + assert.Equal(t, []string{"do_thing"}, imgMeta.Tools) + assert.Equal(t, []string{"test"}, imgMeta.Tags) + require.Len(t, imgMeta.EnvVars, 1) + assert.Equal(t, "API_KEY", imgMeta.EnvVars[0].Name) + + // Remote server should be in RemoteServers + assert.Len(t, reg.RemoteServers, 1) + assert.Contains(t, reg.RemoteServers, "test-remote") + + remoteMeta := reg.RemoteServers["test-remote"] + assert.Empty(t, remoteMeta.Name) + assert.Equal(t, "A remote test server", remoteMeta.Description) + assert.Equal(t, "streamable-http", remoteMeta.Transport) + assert.Equal(t, "https://mcp.example.com/mcp", remoteMeta.URL) + assert.Equal(t, "Official", remoteMeta.Tier) + assert.Equal(t, []string{"remote_tool"}, remoteMeta.Tools) + assert.Equal(t, []string{"remote"}, remoteMeta.Tags) +} + +func TestLegacyBuilder_Build_Defaults(t *testing.T) { + t.Parallel() + + // Minimal server.json with no extensions — should get defaults + minimalImage := `{ + "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json", + "name": "io.github.stacklok/bare", + "description": "Bare server", + "version": "1.0.0", + "packages": [{ + "registryType": "oci", + "identifier": "ghcr.io/example/bare:1.0.0", + "transport": {"type": "stdio"} + }] +}` + + dir := t.TempDir() + require.NoError(t, os.MkdirAll(filepath.Join(dir, "bare"), 0750)) + require.NoError(t, os.WriteFile(filepath.Join(dir, "bare", "server.json"), []byte(minimalImage), 0600)) + + loader := NewLoader(dir) + require.NoError(t, loader.LoadAll()) + + builder := NewLegacyBuilder(loader) + reg, err := builder.Build() + require.NoError(t, err) + + imgMeta := reg.Servers["bare"] + require.NotNil(t, imgMeta) + + // Defaults applied + assert.Equal(t, "Community", imgMeta.Tier) + assert.Equal(t, "Active", imgMeta.Status) + + // Nil slices initialized to empty + assert.NotNil(t, imgMeta.Tools) + assert.NotNil(t, imgMeta.Tags) + assert.NotNil(t, imgMeta.EnvVars) + assert.NotNil(t, imgMeta.Args) +} + +func TestLegacyBuilder_WriteJSON(t *testing.T) { + t.Parallel() + + loader := setupLegacyLoader(t) + builder := NewLegacyBuilder(loader) + + outDir := t.TempDir() + outPath := filepath.Join(outDir, "output", "registry.json") + + err := builder.WriteJSON(outPath) + require.NoError(t, err) + + data, err := os.ReadFile(outPath) + require.NoError(t, err) + + // Verify it's valid JSON with the schema wrapper + var raw map[string]json.RawMessage + require.NoError(t, json.Unmarshal(data, &raw)) + assert.Contains(t, raw, "$schema") + assert.Contains(t, raw, "version") + assert.Contains(t, raw, "servers") + assert.Contains(t, raw, "remote_servers") + + // Verify schema value + var schema string + require.NoError(t, json.Unmarshal(raw["$schema"], &schema)) + assert.Equal(t, "https://raw.githubusercontent.com/stacklok/toolhive/main/pkg/registry/data/toolhive-legacy-registry.schema.json", schema) + + // Verify servers content + var servers map[string]*toolhiveRegistry.ImageMetadata + require.NoError(t, json.Unmarshal(raw["servers"], &servers)) + assert.Contains(t, servers, "test-image") + + var remotes map[string]*toolhiveRegistry.RemoteServerMetadata + require.NoError(t, json.Unmarshal(raw["remote_servers"], &remotes)) + assert.Contains(t, remotes, "test-remote") +} + +func TestLegacyBuilder_ValidateAgainstSchema(t *testing.T) { + t.Parallel() + + loader := setupLegacyLoader(t) + builder := NewLegacyBuilder(loader) + + err := builder.ValidateAgainstSchema() + assert.NoError(t, err) +} diff --git a/internal/registry/loader.go b/internal/registry/loader.go new file mode 100644 index 00000000..3e27970c --- /dev/null +++ b/internal/registry/loader.go @@ -0,0 +1,97 @@ +// Package registry provides functionality for loading server.json files +// and building the aggregate upstream registry. +package registry + +import ( + "encoding/json" + "fmt" + "log" + "os" + "path/filepath" + "sort" + "strings" + + upstream "github.com/modelcontextprotocol/registry/pkg/api/v0" +) + +// Loader walks a servers directory and loads server.json files into memory. +type Loader struct { + serversPath string + entries map[string]upstream.ServerJSON +} + +// NewLoader creates a new Loader that reads from the given servers directory. +func NewLoader(serversPath string) *Loader { + return &Loader{ + serversPath: serversPath, + entries: make(map[string]upstream.ServerJSON), + } +} + +// LoadAll walks top-level subdirectories under serversPath, reads server.json +// from each, and stores the results keyed by directory name. +// Hidden directories (starting with ".") are skipped. +// Returns an error if any server.json contains malformed JSON. +func (l *Loader) LoadAll() error { + err := filepath.Walk(l.serversPath, func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + + // Skip non-directories and the root directory itself + if !info.IsDir() || path == l.serversPath { + return nil + } + + relPath, err := filepath.Rel(l.serversPath, path) + if err != nil { + return err + } + + // Skip hidden directories + if strings.HasPrefix(info.Name(), ".") { + return filepath.SkipDir + } + + // Skip nested directories (only process top-level) + if strings.Contains(relPath, string(os.PathSeparator)) { + return filepath.SkipDir + } + + // Read server.json from this directory + serverJSONPath := filepath.Join(path, "server.json") + data, err := os.ReadFile(serverJSONPath) // #nosec G304 - path is constructed from known directory structure + if err != nil { + if os.IsNotExist(err) { + log.Printf("WARNING: no server.json in %s, skipping", info.Name()) + return nil + } + return fmt.Errorf("failed to read %s: %w", serverJSONPath, err) + } + + var server upstream.ServerJSON + if err := json.Unmarshal(data, &server); err != nil { + return fmt.Errorf("failed to parse %s: %w", serverJSONPath, err) + } + + l.entries[info.Name()] = server + return nil + }) + + return err +} + +// GetEntries returns all loaded server.json entries keyed by directory name. +func (l *Loader) GetEntries() map[string]upstream.ServerJSON { + return l.entries +} + +// GetSortedNames returns directory names in sorted order for deterministic output. +func (l *Loader) GetSortedNames() []string { + names := make([]string, 0, len(l.entries)) + for name := range l.entries { + names = append(names, name) + } + sort.Strings(names) + return names +} diff --git a/internal/registry/loader_test.go b/internal/registry/loader_test.go new file mode 100644 index 00000000..7b9c946a --- /dev/null +++ b/internal/registry/loader_test.go @@ -0,0 +1,124 @@ +package registry + +import ( + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +const minimalServerJSON = `{ + "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json", + "name": "io.github.stacklok/test-server", + "description": "A test server", + "version": "1.0.0" +}` + +const minimalServerJSON2 = `{ + "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json", + "name": "io.github.stacklok/another-server", + "description": "Another test server", + "version": "1.0.0" +}` + +func TestLoader_LoadAll(t *testing.T) { + t.Parallel() + + dir := t.TempDir() + + // Create two server directories with server.json files + require.NoError(t, os.MkdirAll(filepath.Join(dir, "alpha"), 0750)) + require.NoError(t, os.WriteFile(filepath.Join(dir, "alpha", "server.json"), []byte(minimalServerJSON), 0600)) + + require.NoError(t, os.MkdirAll(filepath.Join(dir, "beta"), 0750)) + require.NoError(t, os.WriteFile(filepath.Join(dir, "beta", "server.json"), []byte(minimalServerJSON2), 0600)) + + loader := NewLoader(dir) + require.NoError(t, loader.LoadAll()) + + entries := loader.GetEntries() + assert.Len(t, entries, 2) + assert.Contains(t, entries, "alpha") + assert.Contains(t, entries, "beta") + + // Verify sorted names + names := loader.GetSortedNames() + assert.Equal(t, []string{"alpha", "beta"}, names) + + // Verify content was parsed correctly + assert.Equal(t, "io.github.stacklok/test-server", entries["alpha"].Name) + assert.Equal(t, "A test server", entries["alpha"].Description) +} + +func TestLoader_LoadAll_InvalidJSON(t *testing.T) { + t.Parallel() + + dir := t.TempDir() + + require.NoError(t, os.MkdirAll(filepath.Join(dir, "broken"), 0750)) + require.NoError(t, os.WriteFile(filepath.Join(dir, "broken", "server.json"), []byte(`{invalid json`), 0600)) + + loader := NewLoader(dir) + err := loader.LoadAll() + assert.Error(t, err) + assert.Contains(t, err.Error(), "failed to parse") +} + +func TestLoader_LoadAll_EmptyDir(t *testing.T) { + t.Parallel() + + dir := t.TempDir() + + loader := NewLoader(dir) + require.NoError(t, loader.LoadAll()) + + entries := loader.GetEntries() + assert.Empty(t, entries) + + names := loader.GetSortedNames() + assert.Empty(t, names) +} + +func TestLoader_LoadAll_SkipsHiddenDirs(t *testing.T) { + t.Parallel() + + dir := t.TempDir() + + // Create a hidden directory with server.json + require.NoError(t, os.MkdirAll(filepath.Join(dir, ".hidden"), 0750)) + require.NoError(t, os.WriteFile(filepath.Join(dir, ".hidden", "server.json"), []byte(minimalServerJSON), 0600)) + + // Create a visible directory with server.json + require.NoError(t, os.MkdirAll(filepath.Join(dir, "visible"), 0750)) + require.NoError(t, os.WriteFile(filepath.Join(dir, "visible", "server.json"), []byte(minimalServerJSON), 0600)) + + loader := NewLoader(dir) + require.NoError(t, loader.LoadAll()) + + entries := loader.GetEntries() + assert.Len(t, entries, 1) + assert.Contains(t, entries, "visible") + assert.NotContains(t, entries, ".hidden") +} + +func TestLoader_LoadAll_SkipsMissingServerJSON(t *testing.T) { + t.Parallel() + + dir := t.TempDir() + + // Create a directory without server.json + require.NoError(t, os.MkdirAll(filepath.Join(dir, "empty-dir"), 0750)) + + // Create a directory with server.json + require.NoError(t, os.MkdirAll(filepath.Join(dir, "valid"), 0750)) + require.NoError(t, os.WriteFile(filepath.Join(dir, "valid", "server.json"), []byte(minimalServerJSON), 0600)) + + loader := NewLoader(dir) + require.NoError(t, loader.LoadAll()) + + entries := loader.GetEntries() + assert.Len(t, entries, 1) + assert.Contains(t, entries, "valid") +} diff --git a/internal/serverjson/extensions.go b/internal/serverjson/extensions.go new file mode 100644 index 00000000..6e173da4 --- /dev/null +++ b/internal/serverjson/extensions.go @@ -0,0 +1,164 @@ +// Package serverjson provides utilities for reading, modifying, and writing +// individual server.json files, with support for extracting and updating +// ToolHive-specific extensions in the _meta section. +package serverjson + +import ( + "encoding/json" + "fmt" + "os" + + upstream "github.com/modelcontextprotocol/registry/pkg/api/v0" + "github.com/stacklok/toolhive/pkg/registry/registry" +) + +// ServerFile represents a loaded server.json with its file path and parsed data. +type ServerFile struct { + // Path is the filesystem path to the server.json file. + Path string + // ServerJSON is the parsed upstream ServerJSON structure. + ServerJSON upstream.ServerJSON + // rawBytes preserves the original file bytes for round-trip fidelity. + rawBytes []byte +} + +// LoadServerFile reads and parses a server.json from the given path. +func LoadServerFile(path string) (*ServerFile, error) { + data, err := os.ReadFile(path) // #nosec G304 - path comes from caller (registry directory walk) + if err != nil { + return nil, fmt.Errorf("failed to read %s: %w", path, err) + } + + var server upstream.ServerJSON + if err := json.Unmarshal(data, &server); err != nil { + return nil, fmt.Errorf("failed to parse %s: %w", path, err) + } + + return &ServerFile{ + Path: path, + ServerJSON: server, + rawBytes: data, + }, nil +} + +// ExtensionKey returns the key used inside the publisher namespace. +// For OCI packages this is the image identifier (e.g. "ghcr.io/org/image:tag"). +// For remote servers this is the URL (e.g. "https://api.example.com/mcp"). +func (sf *ServerFile) ExtensionKey() (string, error) { + switch { + case len(sf.ServerJSON.Packages) > 0: + return sf.ServerJSON.Packages[0].Identifier, nil + case len(sf.ServerJSON.Remotes) > 0: + return sf.ServerJSON.Remotes[0].URL, nil + default: + return "", fmt.Errorf("server.json has neither packages nor remotes") + } +} + +// IsPackageServer returns true if this server.json defines OCI package(s). +func (sf *ServerFile) IsPackageServer() bool { + return len(sf.ServerJSON.Packages) > 0 +} + +// IsRemoteServer returns true if this server.json defines remote server(s). +func (sf *ServerFile) IsRemoteServer() bool { + return len(sf.ServerJSON.Remotes) > 0 +} + +// RepositoryURL returns the repository URL if set, or empty string. +func (sf *ServerFile) RepositoryURL() string { + if sf.ServerJSON.Repository != nil { + return sf.ServerJSON.Repository.URL + } + return "" +} + +// GetExtensions extracts the ServerExtensions for this server. +// It navigates: _meta → publisher-provided → io.github.stacklok → +// and deserializes into registry.ServerExtensions. +func (sf *ServerFile) GetExtensions() (*registry.ServerExtensions, error) { + if sf.ServerJSON.Meta == nil || sf.ServerJSON.Meta.PublisherProvided == nil { + return ®istry.ServerExtensions{}, nil + } + + stacklokData, ok := sf.ServerJSON.Meta.PublisherProvided[registry.ToolHivePublisherNamespace].(map[string]any) + if !ok { + return ®istry.ServerExtensions{}, nil + } + + extKey, err := sf.ExtensionKey() + if err != nil { + return nil, err + } + + extData, ok := stacklokData[extKey].(map[string]any) + if !ok { + return ®istry.ServerExtensions{}, nil + } + + jsonData, err := json.Marshal(extData) + if err != nil { + return nil, fmt.Errorf("failed to marshal extensions: %w", err) + } + + var ext registry.ServerExtensions + if err := json.Unmarshal(jsonData, &ext); err != nil { + return nil, fmt.Errorf("failed to unmarshal extensions: %w", err) + } + + return &ext, nil +} + +// UpdateExtensions writes the modified ServerExtensions back into the server.json. +// It parses the raw file bytes into a map[string]interface{} to preserve all +// fields (including unknown ones), replaces only the extension subtree, and +// writes back with json.MarshalIndent. +func (sf *ServerFile) UpdateExtensions(ext *registry.ServerExtensions) error { + extJSON, err := json.Marshal(ext) + if err != nil { + return fmt.Errorf("failed to marshal extensions: %w", err) + } + + var extMap map[string]any + if err := json.Unmarshal(extJSON, &extMap); err != nil { + return fmt.Errorf("failed to unmarshal extensions map: %w", err) + } + + var doc map[string]any + if err := json.Unmarshal(sf.rawBytes, &doc); err != nil { + return fmt.Errorf("failed to parse raw JSON: %w", err) + } + + extKey, err := sf.ExtensionKey() + if err != nil { + return err + } + + meta := ensureMap(doc, "_meta") + publisherProvided := ensureMap(meta, registry.PublisherProvidedKey) + stacklok := ensureMap(publisherProvided, registry.ToolHivePublisherNamespace) + stacklok[extKey] = extMap + + out, err := json.MarshalIndent(doc, "", " ") + if err != nil { + return fmt.Errorf("failed to marshal server.json: %w", err) + } + out = append(out, '\n') + + if err := os.WriteFile(sf.Path, out, 0600); err != nil { + return fmt.Errorf("failed to write %s: %w", sf.Path, err) + } + + sf.rawBytes = out + return nil +} + +// ensureMap gets or creates a nested map[string]any at the given key. +func ensureMap(parent map[string]any, key string) map[string]any { + if val, ok := parent[key].(map[string]any); ok { + return val + } + m := make(map[string]any) + parent[key] = m + return m +} diff --git a/internal/serverjson/extensions_test.go b/internal/serverjson/extensions_test.go new file mode 100644 index 00000000..4422f1d5 --- /dev/null +++ b/internal/serverjson/extensions_test.go @@ -0,0 +1,433 @@ +package serverjson + +import ( + "encoding/json" + "os" + "path/filepath" + "testing" + + "github.com/stacklok/toolhive/pkg/registry/registry" +) + +const statusActive = "Active" + +const testPackageServerJSON = `{ + "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json", + "name": "io.github.stacklok/test-server", + "description": "A test server", + "version": "1.0.0", + "packages": [ + { + "registryType": "oci", + "identifier": "ghcr.io/test/server:v1.0.0", + "transport": { "type": "stdio" } + } + ], + "_meta": { + "io.modelcontextprotocol.registry/publisher-provided": { + "io.github.stacklok": { + "ghcr.io/test/server:v1.0.0": { + "status": "Active", + "tier": "Official", + "tools": ["tool_a", "tool_b"], + "tags": ["test"], + "metadata": { + "stars": 100, + "last_updated": "2026-01-01T00:00:00Z" + } + } + } + } + } +} +` + +const testRemoteServerJSON = `{ + "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json", + "name": "io.github.stacklok/test-remote", + "description": "A test remote server", + "version": "1.0.0", + "remotes": [ + { + "type": "sse", + "url": "https://api.example.com/mcp" + } + ], + "_meta": { + "io.modelcontextprotocol.registry/publisher-provided": { + "io.github.stacklok": { + "https://api.example.com/mcp": { + "status": "Active", + "tier": "Community", + "tools": ["remote_tool"], + "metadata": { + "last_updated": "2026-01-01T00:00:00Z" + } + } + } + } + } +} +` + +const testNoMetaServerJSON = `{ + "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json", + "name": "io.github.stacklok/no-meta", + "description": "Server with no _meta", + "version": "1.0.0", + "packages": [ + { + "registryType": "oci", + "identifier": "ghcr.io/test/no-meta:v1.0.0", + "transport": { "type": "stdio" } + } + ] +} +` + +func writeTestFile(t *testing.T, dir, name, content string) string { + t.Helper() + path := filepath.Join(dir, name) + if err := os.WriteFile(path, []byte(content), 0600); err != nil { + t.Fatal(err) + } + return path +} + +func TestLoadServerFile(t *testing.T) { + t.Parallel() + dir := t.TempDir() + path := writeTestFile(t, dir, "server.json", testPackageServerJSON) + + sf, err := LoadServerFile(path) + if err != nil { + t.Fatalf("LoadServerFile failed: %v", err) + } + + if sf.ServerJSON.Name != "io.github.stacklok/test-server" { + t.Errorf("expected name io.github.stacklok/test-server, got %s", sf.ServerJSON.Name) + } + if sf.Path != path { + t.Errorf("expected path %s, got %s", path, sf.Path) + } +} + +func TestLoadServerFile_InvalidJSON(t *testing.T) { + t.Parallel() + dir := t.TempDir() + path := writeTestFile(t, dir, "server.json", "{invalid json") + + _, err := LoadServerFile(path) + if err == nil { + t.Fatal("expected error for invalid JSON") + } +} + +func TestLoadServerFile_MissingFile(t *testing.T) { + t.Parallel() + _, err := LoadServerFile("/nonexistent/server.json") + if err == nil { + t.Fatal("expected error for missing file") + } +} + +func TestExtensionKey_Package(t *testing.T) { + t.Parallel() + dir := t.TempDir() + path := writeTestFile(t, dir, "server.json", testPackageServerJSON) + + sf, err := LoadServerFile(path) + if err != nil { + t.Fatal(err) + } + + key, err := sf.ExtensionKey() + if err != nil { + t.Fatalf("ExtensionKey failed: %v", err) + } + if key != "ghcr.io/test/server:v1.0.0" { + t.Errorf("expected ghcr.io/test/server:v1.0.0, got %s", key) + } +} + +func TestExtensionKey_Remote(t *testing.T) { + t.Parallel() + dir := t.TempDir() + path := writeTestFile(t, dir, "server.json", testRemoteServerJSON) + + sf, err := LoadServerFile(path) + if err != nil { + t.Fatal(err) + } + + key, err := sf.ExtensionKey() + if err != nil { + t.Fatalf("ExtensionKey failed: %v", err) + } + if key != "https://api.example.com/mcp" { + t.Errorf("expected https://api.example.com/mcp, got %s", key) + } +} + +func TestIsPackageServer(t *testing.T) { + t.Parallel() + dir := t.TempDir() + + pkgPath := writeTestFile(t, dir, "pkg.json", testPackageServerJSON) + remotePath := writeTestFile(t, dir, "remote.json", testRemoteServerJSON) + + pkgSF, _ := LoadServerFile(pkgPath) + remoteSF, _ := LoadServerFile(remotePath) + + if !pkgSF.IsPackageServer() { + t.Error("expected package server to be true") + } + if pkgSF.IsRemoteServer() { + t.Error("expected remote server to be false for package server") + } + if !remoteSF.IsRemoteServer() { + t.Error("expected remote server to be true") + } + if remoteSF.IsPackageServer() { + t.Error("expected package server to be false for remote server") + } +} + +func TestGetExtensions_Package(t *testing.T) { + t.Parallel() + dir := t.TempDir() + path := writeTestFile(t, dir, "server.json", testPackageServerJSON) + + sf, err := LoadServerFile(path) + if err != nil { + t.Fatal(err) + } + + ext, err := sf.GetExtensions() + if err != nil { + t.Fatalf("GetExtensions failed: %v", err) + } + + if ext.Status != statusActive { + t.Errorf("expected status Active, got %s", ext.Status) + } + if ext.Tier != "Official" { + t.Errorf("expected tier Official, got %s", ext.Tier) + } + if len(ext.Tools) != 2 { + t.Errorf("expected 2 tools, got %d", len(ext.Tools)) + } + if ext.Metadata == nil || ext.Metadata.Stars != 100 { + t.Errorf("expected 100 stars, got %v", ext.Metadata) + } +} + +func TestGetExtensions_Remote(t *testing.T) { + t.Parallel() + dir := t.TempDir() + path := writeTestFile(t, dir, "server.json", testRemoteServerJSON) + + sf, err := LoadServerFile(path) + if err != nil { + t.Fatal(err) + } + + ext, err := sf.GetExtensions() + if err != nil { + t.Fatalf("GetExtensions failed: %v", err) + } + + if ext.Status != statusActive { + t.Errorf("expected status Active, got %s", ext.Status) + } + if ext.Tier != "Community" { + t.Errorf("expected tier Community, got %s", ext.Tier) + } + if len(ext.Tools) != 1 || ext.Tools[0] != "remote_tool" { + t.Errorf("expected [remote_tool], got %v", ext.Tools) + } +} + +func TestGetExtensions_NoMeta(t *testing.T) { + t.Parallel() + dir := t.TempDir() + path := writeTestFile(t, dir, "server.json", testNoMetaServerJSON) + + sf, err := LoadServerFile(path) + if err != nil { + t.Fatal(err) + } + + ext, err := sf.GetExtensions() + if err != nil { + t.Fatalf("GetExtensions failed: %v", err) + } + + // Should return empty extensions, not nil + if ext.Status != "" { + t.Errorf("expected empty status, got %s", ext.Status) + } +} + +func TestUpdateExtensions_RoundTrip(t *testing.T) { + t.Parallel() + dir := t.TempDir() + path := writeTestFile(t, dir, "server.json", testPackageServerJSON) + + sf, err := LoadServerFile(path) + if err != nil { + t.Fatal(err) + } + + ext, err := sf.GetExtensions() + if err != nil { + t.Fatal(err) + } + + // Modify extensions + ext.Metadata.Stars = 200 + ext.Metadata.LastUpdated = "2026-02-01T00:00:00Z" + ext.Tools = []string{"tool_a", "tool_b", "tool_c"} + + if err := sf.UpdateExtensions(ext); err != nil { + t.Fatalf("UpdateExtensions failed: %v", err) + } + + // Reload and verify + sf2, err := LoadServerFile(path) + if err != nil { + t.Fatalf("Reload failed: %v", err) + } + + ext2, err := sf2.GetExtensions() + if err != nil { + t.Fatalf("GetExtensions after reload failed: %v", err) + } + + if ext2.Metadata.Stars != 200 { + t.Errorf("expected 200 stars, got %d", ext2.Metadata.Stars) + } + if ext2.Metadata.LastUpdated != "2026-02-01T00:00:00Z" { + t.Errorf("expected 2026-02-01T00:00:00Z, got %s", ext2.Metadata.LastUpdated) + } + if len(ext2.Tools) != 3 { + t.Errorf("expected 3 tools, got %d", len(ext2.Tools)) + } +} + +func TestUpdateExtensions_PreservesFields(t *testing.T) { + t.Parallel() + dir := t.TempDir() + path := writeTestFile(t, dir, "server.json", testPackageServerJSON) + + sf, err := LoadServerFile(path) + if err != nil { + t.Fatal(err) + } + + ext, err := sf.GetExtensions() + if err != nil { + t.Fatal(err) + } + + // Update only stars + ext.Metadata.Stars = 999 + + if err := sf.UpdateExtensions(ext); err != nil { + t.Fatal(err) + } + + // Verify top-level fields are preserved + var doc map[string]any + data, err := os.ReadFile(path) + if err != nil { + t.Fatal(err) + } + if err := json.Unmarshal(data, &doc); err != nil { + t.Fatal(err) + } + + if doc["$schema"] != "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json" { + t.Error("$schema field was lost") + } + if doc["name"] != "io.github.stacklok/test-server" { + t.Error("name field was lost") + } + if doc["description"] != "A test server" { + t.Error("description field was lost") + } + + packages, ok := doc["packages"].([]interface{}) + if !ok || len(packages) != 1 { + t.Error("packages field was lost or modified") + } +} + +func TestRepositoryURL(t *testing.T) { + t.Parallel() + dir := t.TempDir() + + // Server with repo + serverWithRepo := `{ + "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json", + "name": "test", + "description": "test", + "version": "1.0.0", + "repository": {"url": "https://github.com/test/repo"}, + "packages": [{"registryType": "oci", "identifier": "test:v1", "transport": {"type": "stdio"}}] +}` + path := writeTestFile(t, dir, "with-repo.json", serverWithRepo) + sf, _ := LoadServerFile(path) + if sf.RepositoryURL() != "https://github.com/test/repo" { + t.Errorf("expected repo URL, got %s", sf.RepositoryURL()) + } + + // Server without repo + path2 := writeTestFile(t, dir, "no-repo.json", testNoMetaServerJSON) + sf2, _ := LoadServerFile(path2) + if sf2.RepositoryURL() != "" { + t.Errorf("expected empty repo URL, got %s", sf2.RepositoryURL()) + } +} + +func TestUpdateExtensions_NoMeta(t *testing.T) { + t.Parallel() + dir := t.TempDir() + path := writeTestFile(t, dir, "server.json", testNoMetaServerJSON) + + sf, err := LoadServerFile(path) + if err != nil { + t.Fatal(err) + } + + ext := ®istry.ServerExtensions{ + Status: statusActive, + Tier: "Community", + Metadata: ®istry.Metadata{ + Stars: 50, + LastUpdated: "2026-01-01T00:00:00Z", + }, + } + + if err := sf.UpdateExtensions(ext); err != nil { + t.Fatalf("UpdateExtensions on no-meta server failed: %v", err) + } + + // Reload and verify extensions were created + sf2, err := LoadServerFile(path) + if err != nil { + t.Fatal(err) + } + + ext2, err := sf2.GetExtensions() + if err != nil { + t.Fatal(err) + } + + if ext2.Status != statusActive { + t.Errorf("expected Active, got %s", ext2.Status) + } + if ext2.Metadata == nil || ext2.Metadata.Stars != 50 { + t.Errorf("expected 50 stars, got %v", ext2.Metadata) + } +} diff --git a/internal/thvclient/builder.go b/internal/thvclient/builder.go new file mode 100644 index 00000000..d8136482 --- /dev/null +++ b/internal/thvclient/builder.go @@ -0,0 +1,111 @@ +// Package thvclient provides a client for interacting with the ToolHive CLI +// to run MCP servers and discover their available tools. +package thvclient + +import ( + "fmt" + + "github.com/stacklok/toolhive/pkg/registry/registry" + + "github.com/stacklok/toolhive-registry/internal/serverjson" +) + +// CommandBuilder helps build command line arguments for thv. +type CommandBuilder struct { + args []string +} + +// NewCommandBuilder creates a new command builder with the given base command. +func NewCommandBuilder(command string) *CommandBuilder { + return &CommandBuilder{ + args: []string{command}, + } +} + +// AddFlag adds a flag with a value. No-op if value is empty. +func (b *CommandBuilder) AddFlag(flag, value string) *CommandBuilder { + if value != "" { + b.args = append(b.args, flag, value) + } + return b +} + +// AddBoolFlag adds a boolean flag when value is true. +func (b *CommandBuilder) AddBoolFlag(flag string, value bool) *CommandBuilder { + if value { + b.args = append(b.args, flag) + } + return b +} + +// AddEnvVar adds an environment variable flag (-e NAME=value). +func (b *CommandBuilder) AddEnvVar(name, value string) *CommandBuilder { + if value != "" { + b.args = append(b.args, "-e", fmt.Sprintf("%s=%s", name, value)) + } + return b +} + +// AddPositional adds a positional argument. +func (b *CommandBuilder) AddPositional(value string) *CommandBuilder { + b.args = append(b.args, value) + return b +} + +// Build returns the built command arguments. +func (b *CommandBuilder) Build() []string { + return b.args +} + +// BuildRunCommand builds thv run arguments from a ServerFile and extensions. +func BuildRunCommand( + sf *serverjson.ServerFile, + ext *registry.ServerExtensions, + tempName, image string, +) []string { + builder := NewCommandBuilder("run") + builder.AddFlag("--name", tempName) + + // Transport from the package + if len(sf.ServerJSON.Packages) > 0 { + builder.AddFlag( + "--transport", + sf.ServerJSON.Packages[0].Transport.Type, + ) + + // Environment variables from package definition + for _, ev := range sf.ServerJSON.Packages[0].EnvironmentVariables { + if ev.Default != "" { + builder.AddEnvVar(ev.Name, ev.Default) + continue + } + if ev.IsRequired { + builder.AddEnvVar(ev.Name, "placeholder") + continue + } + if ev.IsSecret { + builder.AddEnvVar(ev.Name, "placeholder") + } + } + } + + // Permission profile from extensions + if ext != nil && ext.Permissions != nil && ext.Permissions.Network != nil { + builder.AddFlag("--permission-profile", "network") + } + + // Image as positional argument + builder.AddPositional(image) + + // Args from extensions (after "--" separator) + if ext != nil && len(ext.Args) > 0 { + builder.AddPositional("--") + for _, a := range ext.Args { + if a != "" { + builder.AddPositional(a) + } + } + } + + return builder.Build() +} diff --git a/internal/thvclient/builder_test.go b/internal/thvclient/builder_test.go new file mode 100644 index 00000000..dabf3bcf --- /dev/null +++ b/internal/thvclient/builder_test.go @@ -0,0 +1,201 @@ +package thvclient + +import ( + "fmt" + "os" + "path/filepath" + "slices" + "testing" + + "github.com/stacklok/toolhive/pkg/permissions" + "github.com/stacklok/toolhive/pkg/registry/registry" + + "github.com/stacklok/toolhive-registry/internal/serverjson" +) + +func writeTestServerJSON(t *testing.T, dir, content string) string { + t.Helper() + serverDir := filepath.Join(dir, "test") + if err := os.MkdirAll(serverDir, 0750); err != nil { + t.Fatal(err) + } + path := filepath.Join(serverDir, "server.json") + if err := os.WriteFile(path, []byte(content), 0600); err != nil { + t.Fatal(err) + } + return path +} + +func TestBuildRunCommand_Basic(t *testing.T) { + t.Parallel() + dir := t.TempDir() + serverJSON := `{ + "name": "test", + "description": "test", + "version": "1.0.0", + "packages": [{ + "registryType": "oci", + "identifier": "ghcr.io/test/server:v1", + "transport": {"type": "stdio"} + }] +}` + path := writeTestServerJSON(t, dir, serverJSON) + sf, err := serverjson.LoadServerFile(path) + if err != nil { + t.Fatal(err) + } + + ext := ®istry.ServerExtensions{} + args := BuildRunCommand(sf, ext, "temp-test-123", "ghcr.io/test/server:v1") + + expected := []string{ + "run", "--name", "temp-test-123", + "--transport", "stdio", + "ghcr.io/test/server:v1", + } + + if !slices.Equal(args, expected) { + t.Errorf("expected %v, got %v", expected, args) + } +} + +func TestBuildRunCommand_WithEnvVars(t *testing.T) { + t.Parallel() + dir := t.TempDir() + serverJSON := `{ + "name": "test", + "description": "test", + "version": "1.0.0", + "packages": [{ + "registryType": "oci", + "identifier": "ghcr.io/test/server:v1", + "transport": {"type": "stdio"}, + "environmentVariables": [ + {"name": "TOKEN", "isRequired": true, "isSecret": true}, + {"name": "HOST", "default": "localhost"}, + {"name": "OPTIONAL_SECRET", "isSecret": true} + ] + }] +}` + path := writeTestServerJSON(t, dir, serverJSON) + sf, err := serverjson.LoadServerFile(path) + if err != nil { + t.Fatal(err) + } + + ext := ®istry.ServerExtensions{} + args := BuildRunCommand(sf, ext, "temp-test-123", "ghcr.io/test/server:v1") + + if !containsEnvVar(args, "TOKEN", "placeholder") { + t.Error("expected TOKEN=placeholder") + } + if !containsEnvVar(args, "HOST", "localhost") { + t.Error("expected HOST=localhost") + } + if !containsEnvVar(args, "OPTIONAL_SECRET", "placeholder") { + t.Error("expected OPTIONAL_SECRET=placeholder") + } +} + +func TestBuildRunCommand_WithPermissions(t *testing.T) { + t.Parallel() + dir := t.TempDir() + serverJSON := `{ + "name": "test", + "description": "test", + "version": "1.0.0", + "packages": [{ + "registryType": "oci", + "identifier": "test:v1", + "transport": {"type": "stdio"} + }] +}` + path := writeTestServerJSON(t, dir, serverJSON) + sf, err := serverjson.LoadServerFile(path) + if err != nil { + t.Fatal(err) + } + + ext := ®istry.ServerExtensions{ + Permissions: &permissions.Profile{ + Network: &permissions.NetworkPermissions{ + Outbound: &permissions.OutboundNetworkPermissions{ + AllowHost: []string{"example.com"}, + }, + }, + }, + } + args := BuildRunCommand(sf, ext, "temp-test", "test:v1") + + if !slices.Contains(args, "--permission-profile") { + t.Error("expected --permission-profile flag") + } + idx := slices.Index(args, "--permission-profile") + if idx+1 >= len(args) || args[idx+1] != "network" { + t.Error("expected --permission-profile network") + } +} + +func TestBuildRunCommand_WithArgs(t *testing.T) { + t.Parallel() + dir := t.TempDir() + serverJSON := `{ + "name": "test", + "description": "test", + "version": "1.0.0", + "packages": [{ + "registryType": "oci", + "identifier": "test:v1", + "transport": {"type": "stdio"} + }] +}` + path := writeTestServerJSON(t, dir, serverJSON) + sf, err := serverjson.LoadServerFile(path) + if err != nil { + t.Fatal(err) + } + + ext := ®istry.ServerExtensions{ + Args: []string{"--mode", "read-only"}, + } + args := BuildRunCommand(sf, ext, "temp-test", "test:v1") + + // Should have: ... test:v1 -- --mode read-only + dashIdx := slices.Index(args, "--") + if dashIdx == -1 { + t.Fatal("expected -- separator") + } + if !slices.Equal(args[dashIdx+1:], []string{"--mode", "read-only"}) { + t.Errorf("expected [--mode read-only] after --, got %v", args[dashIdx+1:]) + } +} + +func TestCommandBuilder(t *testing.T) { + t.Parallel() + args := NewCommandBuilder("run"). + AddFlag("--name", "test"). + AddBoolFlag("--verbose", true). + AddBoolFlag("--quiet", false). + AddEnvVar("KEY", "value"). + AddPositional("image:latest"). + Build() + + expected := []string{ + "run", "--name", "test", "--verbose", + "-e", "KEY=value", "image:latest", + } + if !slices.Equal(args, expected) { + t.Errorf("expected %v, got %v", expected, args) + } +} + +// containsEnvVar checks if args contain -e NAME=VALUE pair. +func containsEnvVar(args []string, name, value string) bool { + expected := fmt.Sprintf("%s=%s", name, value) + for i, arg := range args { + if arg == "-e" && i+1 < len(args) && args[i+1] == expected { + return true + } + } + return false +} diff --git a/internal/thvclient/client.go b/internal/thvclient/client.go new file mode 100644 index 00000000..e649085d --- /dev/null +++ b/internal/thvclient/client.go @@ -0,0 +1,128 @@ +package thvclient + +import ( + "fmt" + "os/exec" + "strings" + "time" + + "github.com/stacklok/toolhive-registry/internal/serverjson" +) + +// Client interacts with the ToolHive CLI (thv) to run MCP servers +// and discover their available tools. +type Client struct { + thvPath string + verbose bool +} + +// NewClient creates a new ToolHive client. If thvPath is empty, it searches PATH. +func NewClient(thvPath string, verbose bool) (*Client, error) { + if thvPath == "" { + var err error + thvPath, err = exec.LookPath("thv") + if err != nil { + return nil, fmt.Errorf("thv binary not found in PATH: %w", err) + } + } + + return &Client{ + thvPath: thvPath, + verbose: verbose, + }, nil +} + +// RunServer starts a temporary MCP server from a server.json file. +// Returns the temporary server name for subsequent operations. +func (c *Client) RunServer(sf *serverjson.ServerFile, serverName string) (string, error) { + if sf.IsRemoteServer() { + return "", fmt.Errorf("remote servers cannot be run locally") + } + if !sf.IsPackageServer() { + return "", fmt.Errorf("no packages found in server.json") + } + + image := sf.ServerJSON.Packages[0].Identifier + if image == "" { + return "", fmt.Errorf("empty image identifier in server.json") + } + + ext, err := sf.GetExtensions() + if err != nil { + return "", fmt.Errorf("failed to get extensions: %w", err) + } + + tempName := fmt.Sprintf("temp-%s-%d", serverName, time.Now().Unix()) + runArgs := BuildRunCommand(sf, ext, tempName, image) + + if c.verbose { + fmt.Printf("Running command: thv %s\n", strings.Join(runArgs, " ")) + } + + // #nosec G204 - thvPath is validated in NewClient via exec.LookPath + runCmd := exec.Command(c.thvPath, runArgs...) + runOutput, err := runCmd.CombinedOutput() + if err != nil { + return "", fmt.Errorf( + "failed to start MCP server: %w\nOutput: %s", + err, string(runOutput), + ) + } + + // Give the server time to start + time.Sleep(5 * time.Second) + + return tempName, nil +} + +// ListTools queries a running MCP server for its tools. +func (c *Client) ListTools(serverName string) ([]string, error) { + listArgs := NewCommandBuilder("mcp"). + AddPositional("list"). + AddPositional("tools"). + AddFlag("--server", serverName). + AddFlag("--format", "json"). + Build() + + // #nosec G204 - thvPath is validated in NewClient via exec.LookPath + listCmd := exec.Command(c.thvPath, listArgs...) + output, err := listCmd.CombinedOutput() + if err != nil { + return nil, fmt.Errorf( + "thv mcp list failed: %w\nOutput: %s", + err, string(output), + ) + } + + return ParseToolsJSON(string(output)) +} + +// Logs retrieves logs from a running MCP server. +func (c *Client) Logs(serverName string) (string, error) { + logsArgs := NewCommandBuilder("logs"). + AddFlag("--follow", "false"). + AddPositional(serverName). + Build() + + // #nosec G204 - thvPath is validated in NewClient via exec.LookPath + logsCmd := exec.Command(c.thvPath, logsArgs...) + output, err := logsCmd.CombinedOutput() + if err != nil { + return "", fmt.Errorf( + "thv logs failed: %w\nOutput: %s", + err, string(output), + ) + } + + return string(output), nil +} + +// StopServer stops a running MCP server. +func (c *Client) StopServer(serverName string) error { + // #nosec G204 - thvPath is validated in NewClient via exec.LookPath + stopCmd := exec.Command(c.thvPath, "stop", serverName) + if err := stopCmd.Run(); err != nil { + return fmt.Errorf("failed to stop server %s: %w", serverName, err) + } + return nil +} diff --git a/internal/thvclient/parser.go b/internal/thvclient/parser.go new file mode 100644 index 00000000..22801f9c --- /dev/null +++ b/internal/thvclient/parser.go @@ -0,0 +1,81 @@ +package thvclient + +import ( + "bufio" + "encoding/json" + "fmt" + "sort" + "strings" +) + +// tool represents an MCP tool from thv mcp list output. +type tool struct { + Name string `json:"name"` + Description string `json:"description"` + InputSchema map[string]any `json:"inputSchema,omitempty"` + Annotations map[string]any `json:"annotations,omitempty"` +} + +// mcpListOutput represents the JSON output from thv mcp list. +type mcpListOutput struct { + Tools []tool `json:"tools"` +} + +// ParseToolsJSON parses JSON output from thv mcp list tools --format json. +// Falls back to text parsing if JSON parsing fails. +func ParseToolsJSON(output string) ([]string, error) { + jsonStart := strings.Index(output, "{") + if jsonStart == -1 { + return ParseToolsText(output) + } + jsonOutput := output[jsonStart:] + + var result mcpListOutput + if err := json.Unmarshal([]byte(jsonOutput), &result); err != nil { + return ParseToolsText(output) + } + + var tools []string + for _, t := range result.Tools { + tools = append(tools, t.Name) + } + + sort.Strings(tools) + return tools, nil +} + +// ParseToolsText parses text output from thv mcp list (fallback parser). +func ParseToolsText(output string) ([]string, error) { + var tools []string + foundToolsSection := false + foundHeader := false + + scanner := bufio.NewScanner(strings.NewReader(output)) + for scanner.Scan() { + line := scanner.Text() + + if strings.HasPrefix(line, "TOOLS:") { + foundToolsSection = true + continue + } + + if foundToolsSection && strings.HasPrefix(line, "NAME") { + foundHeader = true + continue + } + + if foundToolsSection && foundHeader && len(line) > 0 { + fields := strings.Fields(line) + if len(fields) > 0 { + tools = append(tools, fields[0]) + } + } + } + + if !foundToolsSection { + return nil, fmt.Errorf("no TOOLS section found in output") + } + + sort.Strings(tools) + return tools, nil +} diff --git a/internal/thvclient/parser_test.go b/internal/thvclient/parser_test.go new file mode 100644 index 00000000..0a3cb604 --- /dev/null +++ b/internal/thvclient/parser_test.go @@ -0,0 +1,85 @@ +package thvclient + +import ( + "slices" + "testing" +) + +func TestParseToolsJSON_Valid(t *testing.T) { + t.Parallel() + output := `{"tools":[{"name":"tool_b","description":"B"},{"name":"tool_a","description":"A"}]}` + tools, err := ParseToolsJSON(output) + if err != nil { + t.Fatalf("ParseToolsJSON failed: %v", err) + } + expected := []string{"tool_a", "tool_b"} + if !slices.Equal(tools, expected) { + t.Errorf("expected %v, got %v", expected, tools) + } +} + +func TestParseToolsJSON_WithWarnings(t *testing.T) { + t.Parallel() + output := `WARNING: something happened +{"tools":[{"name":"my_tool","description":"test"}]}` + tools, err := ParseToolsJSON(output) + if err != nil { + t.Fatalf("ParseToolsJSON failed: %v", err) + } + if len(tools) != 1 || tools[0] != "my_tool" { + t.Errorf("expected [my_tool], got %v", tools) + } +} + +func TestParseToolsJSON_Empty(t *testing.T) { + t.Parallel() + output := `{"tools":[]}` + tools, err := ParseToolsJSON(output) + if err != nil { + t.Fatalf("ParseToolsJSON failed: %v", err) + } + if len(tools) != 0 { + t.Errorf("expected empty tools, got %v", tools) + } +} + +func TestParseToolsJSON_FallsBackToText(t *testing.T) { + t.Parallel() + output := `TOOLS: +NAME DESCRIPTION +my_tool Does things +other_tool Does other things` + tools, err := ParseToolsJSON(output) + if err != nil { + t.Fatalf("ParseToolsJSON fallback failed: %v", err) + } + expected := []string{"my_tool", "other_tool"} + if !slices.Equal(tools, expected) { + t.Errorf("expected %v, got %v", expected, tools) + } +} + +func TestParseToolsText_Valid(t *testing.T) { + t.Parallel() + output := `TOOLS: +NAME DESCRIPTION +tool_b Does B +tool_a Does A` + tools, err := ParseToolsText(output) + if err != nil { + t.Fatalf("ParseToolsText failed: %v", err) + } + expected := []string{"tool_a", "tool_b"} + if !slices.Equal(tools, expected) { + t.Errorf("expected %v, got %v", expected, tools) + } +} + +func TestParseToolsText_NoToolsSection(t *testing.T) { + t.Parallel() + output := `Some random output without tools` + _, err := ParseToolsText(output) + if err == nil { + t.Fatal("expected error for missing TOOLS section") + } +} diff --git a/registries/toolhive/servers/adb-mysql-mcp-server/server.json b/registries/toolhive/servers/adb-mysql-mcp-server/server.json new file mode 100644 index 00000000..6dcffc5f --- /dev/null +++ b/registries/toolhive/servers/adb-mysql-mcp-server/server.json @@ -0,0 +1,89 @@ +{ + "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json", + "name": "io.github.stacklok/adb-mysql-mcp-server", + "description": "Official MCP server for AnalyticDB for MySQL of Alibaba Cloud", + "title": "adb-mysql-mcp-server", + "repository": { + "url": "https://github.com/aliyun/alibabacloud-adb-mysql-mcp-server", + "source": "github" + }, + "version": "1.0.0", + "packages": [ + { + "registryType": "oci", + "identifier": "ghcr.io/stacklok/dockyard/uvx/adb-mysql-mcp-server:1.0.0", + "transport": { + "type": "stdio" + }, + "environmentVariables": [ + { + "description": "AnalyticDB for MySQL host address", + "isRequired": true, + "name": "ADB_MYSQL_HOST" + }, + { + "description": "AnalyticDB for MySQL port number", + "isRequired": true, + "name": "ADB_MYSQL_PORT" + }, + { + "description": "Database user for authentication", + "isRequired": true, + "name": "ADB_MYSQL_USER" + }, + { + "description": "Database password for authentication", + "isRequired": true, + "isSecret": true, + "name": "ADB_MYSQL_PASSWORD" + }, + { + "description": "Database name to connect to", + "isRequired": true, + "name": "ADB_MYSQL_DATABASE" + } + ] + } + ], + "_meta": { + "io.modelcontextprotocol.registry/publisher-provided": { + "io.github.stacklok": { + "ghcr.io/stacklok/dockyard/uvx/adb-mysql-mcp-server:1.0.0": { + "metadata": { + "last_updated": "2026-02-05T04:47:25Z", + "stars": 21 + }, + "permissions": { + "network": { + "outbound": { + "insecure_allow_all": true + } + } + }, + "provenance": { + "cert_issuer": "https://token.actions.githubusercontent.com", + "repository_uri": "https://github.com/stacklok/dockyard", + "runner_environment": "github-hosted", + "signer_identity": "/.github/workflows/build-containers.yml", + "sigstore_url": "tuf-repo-cdn.sigstore.dev" + }, + "status": "Active", + "tags": [ + "database", + "mysql", + "analytics", + "sql", + "alibaba-cloud", + "data-warehouse" + ], + "tier": "Official", + "tools": [ + "execute_sql", + "get_query_plan", + "get_execution_plan" + ] + } + } + } + } +} diff --git a/registries/toolhive/servers/agentql-mcp/server.json b/registries/toolhive/servers/agentql-mcp/server.json new file mode 100644 index 00000000..24015ca5 --- /dev/null +++ b/registries/toolhive/servers/agentql-mcp/server.json @@ -0,0 +1,71 @@ +{ + "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json", + "name": "io.github.stacklok/agentql-mcp", + "description": "Model Context Protocol server that integrates AgentQL data extraction capabilities", + "title": "agentql-mcp", + "repository": { + "url": "https://github.com/tinyfish-io/agentql-mcp", + "source": "github" + }, + "version": "1.0.0", + "packages": [ + { + "registryType": "oci", + "identifier": "ghcr.io/stacklok/dockyard/npx/agentql-mcp:1.0.0", + "transport": { + "type": "stdio" + }, + "environmentVariables": [ + { + "description": "API key for AgentQL service", + "isRequired": true, + "isSecret": true, + "name": "AGENTQL_API_KEY" + } + ] + } + ], + "_meta": { + "io.modelcontextprotocol.registry/publisher-provided": { + "io.github.stacklok": { + "ghcr.io/stacklok/dockyard/npx/agentql-mcp:1.0.0": { + "metadata": { + "last_updated": "2026-02-05T04:47:26Z", + "stars": 142 + }, + "permissions": { + "network": { + "outbound": { + "allow_host": [ + "api.agentql.com" + ], + "allow_port": [ + 443 + ] + } + } + }, + "provenance": { + "cert_issuer": "https://token.actions.githubusercontent.com", + "repository_uri": "https://github.com/stacklok/dockyard", + "runner_environment": "github-hosted", + "signer_identity": "/.github/workflows/build-containers.yml", + "sigstore_url": "tuf-repo-cdn.sigstore.dev" + }, + "status": "Active", + "tags": [ + "web-scraping", + "data-extraction", + "ai", + "automation", + "web" + ], + "tier": "Official", + "tools": [ + "extract-web-data" + ] + } + } + } + } +} diff --git a/registries/toolhive/servers/apollo-mcp-server/server.json b/registries/toolhive/servers/apollo-mcp-server/server.json new file mode 100644 index 00000000..d2cfa17f --- /dev/null +++ b/registries/toolhive/servers/apollo-mcp-server/server.json @@ -0,0 +1,66 @@ +{ + "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json", + "name": "io.github.stacklok/apollo-mcp-server", + "description": "Exposes GraphQL operations as MCP tools for AI-driven API orchestration with Apollo", + "title": "apollo-mcp-server", + "repository": { + "url": "https://github.com/apollographql/apollo-mcp-server", + "source": "github" + }, + "version": "1.0.0", + "packages": [ + { + "registryType": "oci", + "identifier": "ghcr.io/apollographql/apollo-mcp-server:v1.7.0", + "transport": { + "type": "streamable-http", + "url": "http://localhost:5000" + }, + "environmentVariables": [ + { + "description": "Graph ref (graph ID and variant) used to fetch persisted queries or schema (required if no config file)", + "name": "APOLLO_GRAPH_REF" + }, + { + "description": "Apollo Studio API key for the graph (required if no config file)", + "isSecret": true, + "name": "APOLLO_KEY" + } + ] + } + ], + "_meta": { + "io.modelcontextprotocol.registry/publisher-provided": { + "io.github.stacklok": { + "ghcr.io/apollographql/apollo-mcp-server:v1.7.0": { + "metadata": { + "last_updated": "2026-02-05T04:49:17Z", + "stars": 257 + }, + "permissions": { + "network": { + "outbound": { + "allow_port": [ + 443 + ], + "insecure_allow_all": true + } + } + }, + "status": "Active", + "tags": [ + "graphql", + "api", + "orchestration", + "apollo", + "mcp" + ], + "tier": "Official", + "tools": [ + "example_GetAstronautsCurrentlyInSpace" + ] + } + } + } + } +} diff --git a/registries/toolhive/servers/arxiv-mcp-server/server.json b/registries/toolhive/servers/arxiv-mcp-server/server.json new file mode 100644 index 00000000..70a1b370 --- /dev/null +++ b/registries/toolhive/servers/arxiv-mcp-server/server.json @@ -0,0 +1,80 @@ +{ + "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json", + "name": "io.github.stacklok/arxiv-mcp-server", + "description": "AI assistants search and access arXiv papers through MCP with persistent paper storage", + "title": "arxiv-mcp-server", + "repository": { + "url": "https://github.com/blazickjp/arxiv-mcp-server", + "source": "github" + }, + "version": "1.0.0", + "packages": [ + { + "registryType": "oci", + "identifier": "ghcr.io/stacklok/dockyard/uvx/arxiv-mcp-server:0.3.2", + "transport": { + "type": "stdio" + }, + "environmentVariables": [ + { + "description": "Directory path where downloaded papers will be stored", + "default": "/arxiv-papers", + "name": "ARXIV_STORAGE_PATH" + } + ] + } + ], + "_meta": { + "io.modelcontextprotocol.registry/publisher-provided": { + "io.github.stacklok": { + "ghcr.io/stacklok/dockyard/uvx/arxiv-mcp-server:0.3.2": { + "args": [ + "--storage-path", + "/arxiv-papers" + ], + "metadata": { + "last_updated": "2026-02-08T03:02:27Z", + "pulls": 77, + "stars": 2140 + }, + "permissions": { + "network": { + "outbound": { + "allow_host": [ + "arxiv.org", + "export.arxiv.org" + ], + "allow_port": [ + 443, + 80 + ] + } + } + }, + "provenance": { + "cert_issuer": "https://token.actions.githubusercontent.com", + "repository_uri": "https://github.com/stacklok/dockyard", + "runner_environment": "github-hosted", + "signer_identity": "/.github/workflows/build-containers.yml", + "sigstore_url": "tuf-repo-cdn.sigstore.dev" + }, + "status": "Active", + "tags": [ + "research", + "academic", + "papers", + "arxiv", + "search" + ], + "tier": "Community", + "tools": [ + "search_papers", + "download_paper", + "list_papers", + "read_paper" + ] + } + } + } + } +} diff --git a/registries/toolhive/servers/astra-db-mcp/server.json b/registries/toolhive/servers/astra-db-mcp/server.json new file mode 100644 index 00000000..d8d277e8 --- /dev/null +++ b/registries/toolhive/servers/astra-db-mcp/server.json @@ -0,0 +1,92 @@ +{ + "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json", + "name": "io.github.stacklok/astra-db-mcp", + "description": "Model Context Protocol server for interacting with DataStax Astra DB", + "title": "astra-db-mcp", + "repository": { + "url": "https://github.com/datastax/astra-db-mcp", + "source": "github" + }, + "version": "1.0.0", + "packages": [ + { + "registryType": "oci", + "identifier": "ghcr.io/stacklok/dockyard/npx/astra-db-mcp:1.2.2", + "transport": { + "type": "stdio" + }, + "environmentVariables": [ + { + "description": "Astra DB application token for authentication", + "isRequired": true, + "isSecret": true, + "name": "ASTRA_DB_APPLICATION_TOKEN" + }, + { + "description": "Astra DB API endpoint URL", + "isRequired": true, + "name": "ASTRA_DB_API_ENDPOINT" + }, + { + "description": "Astra DB keyspace to use (defaults to default_keyspace)", + "default": "default_keyspace", + "name": "ASTRA_DB_KEYSPACE" + } + ] + } + ], + "_meta": { + "io.modelcontextprotocol.registry/publisher-provided": { + "io.github.stacklok": { + "ghcr.io/stacklok/dockyard/npx/astra-db-mcp:1.2.2": { + "metadata": { + "last_updated": "2026-02-05T04:47:26Z", + "stars": 38 + }, + "permissions": { + "network": { + "outbound": { + "insecure_allow_all": true + } + } + }, + "provenance": { + "cert_issuer": "https://token.actions.githubusercontent.com", + "repository_uri": "https://github.com/stacklok/dockyard", + "runner_environment": "github-hosted", + "signer_identity": "/.github/workflows/build-containers.yml", + "sigstore_url": "tuf-repo-cdn.sigstore.dev" + }, + "status": "Active", + "tags": [ + "database", + "nosql", + "cassandra", + "vector-database", + "datastax", + "astra" + ], + "tier": "Official", + "tools": [ + "GetCollections", + "CreateCollection", + "UpdateCollection", + "DeleteCollection", + "ListRecords", + "GetRecord", + "CreateRecord", + "UpdateRecord", + "DeleteRecord", + "FindRecord", + "BulkCreateRecords", + "BulkUpdateRecords", + "BulkDeleteRecords", + "OpenBrowser", + "HelpAddToClient", + "EstimateDocumentCount" + ] + } + } + } + } +} diff --git a/registries/toolhive/servers/atlassian-remote/server.json b/registries/toolhive/servers/atlassian-remote/server.json new file mode 100644 index 00000000..aa9f64ee --- /dev/null +++ b/registries/toolhive/servers/atlassian-remote/server.json @@ -0,0 +1,68 @@ +{ + "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json", + "name": "io.github.stacklok/atlassian-remote", + "description": "Atlassian's official remote MCP server for Jira, Confluence, and Compass with OAuth 2.1", + "title": "atlassian-remote", + "version": "1.0.0", + "remotes": [ + { + "type": "sse", + "url": "https://mcp.atlassian.com/v1/sse" + } + ], + "_meta": { + "io.modelcontextprotocol.registry/publisher-provided": { + "io.github.stacklok": { + "https://mcp.atlassian.com/v1/sse": { + "custom_metadata": { + "author": "Atlassian", + "homepage": "https://www.atlassian.com/platform/remote-mcp-server" + }, + "metadata": { + "last_updated": "2026-02-05T04:49:18Z" + }, + "status": "Active", + "tags": [ + "remote", + "atlassian", + "jira", + "confluence", + "compass", + "oauth", + "project-management", + "collaboration", + "documentation", + "beta" + ], + "tier": "Official", + "tools": [ + "atlassianUserInfo", + "getAccessibleAtlassianResources", + "getConfluenceSpaces", + "getConfluencePage", + "getPagesInConfluenceSpace", + "getConfluencePageFooterComments", + "getConfluencePageInlineComments", + "getConfluencePageDescendants", + "createConfluencePage", + "updateConfluencePage", + "createConfluenceFooterComment", + "createConfluenceInlineComment", + "searchConfluenceUsingCql", + "getJiraIssue", + "editJiraIssue", + "createJiraIssue", + "getTransitionsForJiraIssue", + "transitionJiraIssue", + "lookupJiraAccountId", + "searchJiraIssuesUsingJql", + "addCommentToJiraIssue", + "getJiraIssueRemoteIssueLinks", + "getVisibleJiraProjects", + "getJiraProjectIssueTypesMetadata" + ] + } + } + } + } +} diff --git a/registries/toolhive/servers/atlassian/server.json b/registries/toolhive/servers/atlassian/server.json new file mode 100644 index 00000000..4ac02cc1 --- /dev/null +++ b/registries/toolhive/servers/atlassian/server.json @@ -0,0 +1,169 @@ +{ + "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json", + "name": "io.github.stacklok/atlassian", + "description": "Connect to Atlassian products like Confluence, Jira Cloud and Server/Data deployments.", + "title": "atlassian", + "repository": { + "url": "https://github.com/sooperset/mcp-atlassian", + "source": "github" + }, + "version": "1.0.0", + "packages": [ + { + "registryType": "oci", + "identifier": "ghcr.io/sooperset/mcp-atlassian:0.13.0", + "transport": { + "type": "stdio" + }, + "environmentVariables": [ + { + "description": "Confluence URL (e.g., https://your-domain.atlassian.net/wiki)", + "name": "CONFLUENCE_URL" + }, + { + "description": "Confluence username/email for Cloud deployments", + "name": "CONFLUENCE_USERNAME" + }, + { + "description": "Confluence API token for Cloud deployments", + "isSecret": true, + "name": "CONFLUENCE_API_TOKEN" + }, + { + "description": "Confluence Personal Access Token for Server/Data Center deployments", + "isSecret": true, + "name": "CONFLUENCE_PERSONAL_TOKEN" + }, + { + "description": "Verify SSL certificates for Confluence Server/Data Center (true/false)", + "name": "CONFLUENCE_SSL_VERIFY" + }, + { + "description": "Comma-separated list of Confluence space keys to filter search results", + "name": "CONFLUENCE_SPACES_FILTER" + }, + { + "description": "Jira URL (e.g., https://your-domain.atlassian.net)", + "name": "JIRA_URL" + }, + { + "description": "Jira username/email for Cloud deployments", + "name": "JIRA_USERNAME" + }, + { + "description": "Jira API token for Cloud deployments", + "isSecret": true, + "name": "JIRA_API_TOKEN" + }, + { + "description": "Jira Personal Access Token for Server/Data Center deployments", + "isSecret": true, + "name": "JIRA_PERSONAL_TOKEN" + }, + { + "description": "Verify SSL certificates for Jira Server/Data Center (true/false)", + "name": "JIRA_SSL_VERIFY" + }, + { + "description": "Comma-separated list of Jira project keys to filter search results", + "name": "JIRA_PROJECTS_FILTER" + }, + { + "description": "Run in read-only mode (disables all write operations)", + "name": "READ_ONLY_MODE" + }, + { + "description": "Increase logging verbosity", + "name": "MCP_VERBOSE" + }, + { + "description": "Comma-separated list of tool names to enable (if not set, all tools are enabled)", + "name": "ENABLED_TOOLS" + } + ] + } + ], + "_meta": { + "io.modelcontextprotocol.registry/publisher-provided": { + "io.github.stacklok": { + "ghcr.io/sooperset/mcp-atlassian:0.13.0": { + "metadata": { + "last_updated": "2026-01-26T02:48:40Z", + "pulls": 12849, + "stars": 4076 + }, + "permissions": { + "network": { + "outbound": { + "allow_host": [ + ".atlassian.net", + ".atlassian.com" + ], + "allow_port": [ + 443 + ] + } + } + }, + "status": "Active", + "tags": [ + "atlassian", + "confluence", + "jira", + "wiki", + "issue-tracking", + "project-management", + "documentation", + "cloud", + "server", + "data-center" + ], + "tier": "Community", + "tools": [ + "confluence_search", + "confluence_get_page", + "confluence_get_page_children", + "confluence_get_comments", + "confluence_get_labels", + "confluence_add_label", + "confluence_create_page", + "confluence_update_page", + "confluence_delete_page", + "confluence_add_comment", + "confluence_search_user", + "jira_get_user_profile", + "jira_get_issue", + "jira_search", + "jira_search_fields", + "jira_get_project_issues", + "jira_get_transitions", + "jira_get_worklog", + "jira_download_attachments", + "jira_get_agile_boards", + "jira_get_board_issues", + "jira_get_sprints_from_board", + "jira_get_sprint_issues", + "jira_get_link_types", + "jira_create_issue", + "jira_batch_create_issues", + "jira_batch_get_changelogs", + "jira_update_issue", + "jira_delete_issue", + "jira_add_comment", + "jira_add_worklog", + "jira_link_to_epic", + "jira_create_issue_link", + "jira_remove_issue_link", + "jira_transition_issue", + "jira_create_sprint", + "jira_update_sprint", + "jira_get_project_versions", + "jira_get_all_projects", + "jira_create_version", + "jira_batch_create_versions" + ] + } + } + } + } +} diff --git a/registries/toolhive/servers/aws-api/server.json b/registries/toolhive/servers/aws-api/server.json new file mode 100644 index 00000000..feda3076 --- /dev/null +++ b/registries/toolhive/servers/aws-api/server.json @@ -0,0 +1,114 @@ +{ + "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json", + "name": "io.github.stacklok/aws-api", + "description": "Enables AI assistants to interact with AWS services and resources through AWS CLI commands", + "title": "aws-api", + "repository": { + "url": "https://github.com/awslabs/mcp", + "source": "github" + }, + "version": "1.0.0", + "packages": [ + { + "registryType": "oci", + "identifier": "public.ecr.aws/awslabs-mcp/awslabs/aws-api-mcp-server:1.3.11", + "transport": { + "type": "stdio" + }, + "environmentVariables": [ + { + "description": "Default AWS region for CLI commands unless a specific region is provided in the request", + "default": "us-east-1", + "name": "AWS_REGION" + }, + { + "description": "AWS access key ID for authentication", + "isSecret": true, + "name": "AWS_ACCESS_KEY_ID" + }, + { + "description": "AWS secret access key for authentication", + "isSecret": true, + "name": "AWS_SECRET_ACCESS_KEY" + }, + { + "description": "AWS session token for temporary credentials", + "isSecret": true, + "name": "AWS_SESSION_TOKEN" + }, + { + "description": "When set to \"true\", restricts execution to read-only operations", + "default": "false", + "name": "READ_OPERATIONS_ONLY" + }, + { + "description": "When set to \"true\", asks explicit consent before executing non-read-only operations", + "default": "false", + "name": "REQUIRE_MUTATION_CONSENT" + }, + { + "description": "Working directory path for MCP server operations (must be absolute path)", + "name": "AWS_API_MCP_WORKING_DIR" + }, + { + "description": "When set to \"true\", enables system-wide file access (may cause unintended overwrites)", + "default": "false", + "name": "AWS_API_MCP_ALLOW_UNRESTRICTED_LOCAL_FILE_ACCESS" + }, + { + "description": "Allow sending additional telemetry data to AWS related to server configuration", + "default": "true", + "name": "AWS_API_MCP_TELEMETRY" + }, + { + "description": "When set to \"true\", enables experimental agent scripts functionality", + "default": "false", + "name": "EXPERIMENTAL_AGENT_SCRIPTS" + }, + { + "description": "Directory path containing custom user scripts for agent scripts functionality", + "name": "AWS_API_MCP_AGENT_SCRIPTS_DIR" + } + ] + } + ], + "_meta": { + "io.modelcontextprotocol.registry/publisher-provided": { + "io.github.stacklok": { + "public.ecr.aws/awslabs-mcp/awslabs/aws-api-mcp-server:1.3.11": { + "metadata": { + "last_updated": "2026-01-28T02:42:35Z", + "stars": 7978 + }, + "permissions": { + "network": { + "outbound": { + "allow_host": [ + "aws.amazon.com", + ".amazonaws.com" + ], + "allow_port": [ + 443 + ] + } + } + }, + "status": "Active", + "tags": [ + "aws", + "cli", + "cloud", + "infrastructure", + "api", + "devops" + ], + "tier": "Official", + "tools": [ + "call_aws", + "suggest_aws_commands" + ] + } + } + } + } +} diff --git a/registries/toolhive/servers/aws-diagram/server.json b/registries/toolhive/servers/aws-diagram/server.json new file mode 100644 index 00000000..e985ec76 --- /dev/null +++ b/registries/toolhive/servers/aws-diagram/server.json @@ -0,0 +1,70 @@ +{ + "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json", + "name": "io.github.stacklok/aws-diagram", + "description": "Generate AWS diagrams, sequence diagrams, flow diagrams, and class diagrams using Python code.", + "title": "aws-diagram", + "repository": { + "url": "https://github.com/awslabs/mcp", + "source": "github" + }, + "version": "1.0.0", + "packages": [ + { + "registryType": "oci", + "identifier": "ghcr.io/stacklok/dockyard/uvx/aws-diagram:1.0.18", + "transport": { + "type": "stdio" + }, + "environmentVariables": [ + { + "description": "Directory where diagrams will be saved", + "default": "/tmp/diagrams", + "name": "OUTPUT_DIR" + }, + { + "description": "Logging level (DEBUG, INFO, WARNING, ERROR, CRITICAL)", + "default": "ERROR", + "name": "FASTMCP_LOG_LEVEL" + } + ] + } + ], + "_meta": { + "io.modelcontextprotocol.registry/publisher-provided": { + "io.github.stacklok": { + "ghcr.io/stacklok/dockyard/uvx/aws-diagram:1.0.18": { + "metadata": { + "last_updated": "2026-02-05T04:47:28Z", + "stars": 8053 + }, + "permissions": { + "network": { + "outbound": {} + } + }, + "provenance": { + "cert_issuer": "https://token.actions.githubusercontent.com", + "repository_uri": "https://github.com/stacklok/dockyard", + "runner_environment": "github-hosted", + "signer_identity": "/.github/workflows/build-containers.yml", + "sigstore_url": "tuf-repo-cdn.sigstore.dev" + }, + "status": "Active", + "tags": [ + "aws", + "cloud", + "diagrams", + "architecture", + "visualization" + ], + "tier": "Official", + "tools": [ + "generate_diagram", + "get_diagram_examples", + "list_icons" + ] + } + } + } + } +} diff --git a/registries/toolhive/servers/aws-documentation/server.json b/registries/toolhive/servers/aws-documentation/server.json new file mode 100644 index 00000000..8d3d26a0 --- /dev/null +++ b/registries/toolhive/servers/aws-documentation/server.json @@ -0,0 +1,77 @@ +{ + "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json", + "name": "io.github.stacklok/aws-documentation", + "description": "Access AWS documentation, search for content, and get recommendations.", + "title": "aws-documentation", + "repository": { + "url": "https://github.com/awslabs/mcp", + "source": "github" + }, + "version": "1.0.0", + "packages": [ + { + "registryType": "oci", + "identifier": "ghcr.io/stacklok/dockyard/uvx/aws-documentation:1.1.18", + "transport": { + "type": "stdio" + }, + "environmentVariables": [ + { + "description": "AWS documentation partition (aws, aws-cn)", + "default": "aws", + "name": "AWS_DOCUMENTATION_PARTITION" + }, + { + "description": "Logging level (DEBUG, INFO, WARNING, ERROR, CRITICAL)", + "default": "ERROR", + "name": "FASTMCP_LOG_LEVEL" + } + ] + } + ], + "_meta": { + "io.modelcontextprotocol.registry/publisher-provided": { + "io.github.stacklok": { + "ghcr.io/stacklok/dockyard/uvx/aws-documentation:1.1.18": { + "metadata": { + "last_updated": "2026-02-05T04:47:28Z", + "stars": 8053 + }, + "permissions": { + "network": { + "outbound": { + "allow_host": [ + ".docs.aws.amazon.com", + ".docs.amazonaws.cn" + ], + "allow_port": [ + 443 + ] + } + } + }, + "provenance": { + "cert_issuer": "https://token.actions.githubusercontent.com", + "repository_uri": "https://github.com/stacklok/dockyard", + "runner_environment": "github-hosted", + "signer_identity": "/.github/workflows/build-containers.yml", + "sigstore_url": "tuf-repo-cdn.sigstore.dev" + }, + "status": "Active", + "tags": [ + "aws", + "documentation", + "cloud", + "reference" + ], + "tier": "Official", + "tools": [ + "read_documentation", + "search_documentation", + "recommend" + ] + } + } + } + } +} diff --git a/registries/toolhive/servers/aws-knowledge/server.json b/registries/toolhive/servers/aws-knowledge/server.json new file mode 100644 index 00000000..7a1cdf96 --- /dev/null +++ b/registries/toolhive/servers/aws-knowledge/server.json @@ -0,0 +1,53 @@ +{ + "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json", + "name": "io.github.stacklok/aws-knowledge", + "description": "Documentation, code samples, regional availability knowledge, and other official AWS content", + "title": "aws-knowledge", + "repository": { + "url": "https://github.com/awslabs/mcp", + "source": "github" + }, + "version": "1.0.0", + "remotes": [ + { + "type": "streamable-http", + "url": "https://knowledge-mcp.global.api.aws" + } + ], + "_meta": { + "io.modelcontextprotocol.registry/publisher-provided": { + "io.github.stacklok": { + "https://knowledge-mcp.global.api.aws": { + "custom_metadata": { + "author": "AWS Labs", + "homepage": "https://awslabs.github.io/mcp/servers/aws-knowledge-mcp-server/" + }, + "metadata": { + "last_updated": "2026-01-28T02:42:37Z", + "stars": 7978 + }, + "status": "Active", + "tags": [ + "remote", + "aws", + "documentation", + "cloudformation", + "reference", + "blogs", + "well-architected", + "code-samples", + "regional-availability" + ], + "tier": "Official", + "tools": [ + "search_documentation", + "read_documentation", + "recommend", + "list_regions", + "get_regional_availability" + ] + } + } + } + } +} diff --git a/registries/toolhive/servers/aws-pricing/server.json b/registries/toolhive/servers/aws-pricing/server.json new file mode 100644 index 00000000..fd188f50 --- /dev/null +++ b/registries/toolhive/servers/aws-pricing/server.json @@ -0,0 +1,98 @@ +{ + "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json", + "name": "io.github.stacklok/aws-pricing", + "description": "Generate upfront AWS service cost estimates and cost insights.", + "title": "aws-pricing", + "repository": { + "url": "https://github.com/awslabs/mcp", + "source": "github" + }, + "version": "1.0.0", + "packages": [ + { + "registryType": "oci", + "identifier": "public.ecr.aws/f3y8w4n0/awslabs/aws-pricing-mcp-server:1.0.24", + "transport": { + "type": "stdio" + }, + "environmentVariables": [ + { + "description": "AWS access key ID with access to the AWS Pricing API", + "isSecret": true, + "name": "AWS_ACCESS_KEY_ID" + }, + { + "description": "AWS secret access key", + "isSecret": true, + "name": "AWS_SECRET_ACCESS_KEY" + }, + { + "description": "AWS session token for temporary credentials", + "isSecret": true, + "name": "AWS_SESSION_TOKEN" + }, + { + "description": "AWS region for the Pricing API endpoint (us-east-1, eu-central-1, ap-southeast-1)", + "default": "us-east-1", + "name": "AWS_REGION" + }, + { + "description": "Logging level (DEBUG, INFO, WARNING, ERROR, CRITICAL)", + "default": "ERROR", + "name": "FASTMCP_LOG_LEVEL" + } + ] + } + ], + "_meta": { + "io.modelcontextprotocol.registry/publisher-provided": { + "io.github.stacklok": { + "public.ecr.aws/f3y8w4n0/awslabs/aws-pricing-mcp-server:1.0.24": { + "metadata": { + "last_updated": "2026-01-26T02:48:41Z", + "pulls": 8179, + "stars": 7957 + }, + "permissions": { + "network": { + "outbound": { + "allow_host": [ + "aws.amazon.com", + "pricing.us-east-1.amazonaws.com", + "api.pricing.us-east-1.amazonaws.com", + "api.pricing.eu-central-1.amazonaws.com", + "api.pricing.ap-southeast-1.amazonaws.com" + ], + "allow_port": [ + 443 + ] + } + } + }, + "status": "Active", + "tags": [ + "aws", + "cost-analysis", + "pricing", + "estimates", + "cost-insights", + "aws-costs", + "aws-pricing" + ], + "tier": "Official", + "tools": [ + "analyze_cdk_project", + "analyze_terraform_project", + "get_pricing", + "get_bedrock_patterns", + "generate_cost_report", + "get_pricing_service_codes", + "get_pricing_service_attributes", + "get_pricing_attribute_values", + "get_price_list_urls" + ] + } + } + } + } +} diff --git a/registries/toolhive/servers/azure/server.json b/registries/toolhive/servers/azure/server.json new file mode 100644 index 00000000..3630455e --- /dev/null +++ b/registries/toolhive/servers/azure/server.json @@ -0,0 +1,136 @@ +{ + "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json", + "name": "io.github.stacklok/azure", + "description": "The Azure MCP Server, bringing the power of Azure to your agents.", + "title": "azure", + "repository": { + "url": "https://github.com/Azure/azure-mcp", + "source": "github" + }, + "version": "1.0.0", + "packages": [ + { + "registryType": "oci", + "identifier": "mcr.microsoft.com/azure-sdk/azure-mcp:1.0.1", + "transport": { + "type": "stdio" + }, + "environmentVariables": [ + { + "description": "Your Azure tenant ID", + "isRequired": true, + "isSecret": true, + "name": "AZURE_TENANT_ID" + }, + { + "description": "Your Azure client ID for authentication", + "isRequired": true, + "isSecret": true, + "name": "AZURE_CLIENT_ID" + }, + { + "description": "Your Azure client secret for authentication", + "isRequired": true, + "isSecret": true, + "name": "AZURE_CLIENT_SECRET" + }, + { + "description": "HTTP proxy URL for outbound requests (optional)", + "name": "HTTP_PROXY" + }, + { + "description": "HTTPS proxy URL for outbound requests (optional)", + "name": "HTTPS_PROXY" + }, + { + "description": "Comma-separated list of hosts to exclude from proxying (optional)", + "name": "NO_PROXY" + } + ] + } + ], + "_meta": { + "io.modelcontextprotocol.registry/publisher-provided": { + "io.github.stacklok": { + "mcr.microsoft.com/azure-sdk/azure-mcp:1.0.1": { + "metadata": { + "last_updated": "2026-02-16T03:01:21Z", + "pulls": 1809, + "stars": 1201 + }, + "permissions": { + "network": { + "outbound": { + "allow_host": [ + "login.microsoftonline.com", + "login.windows.net", + "management.azure.com", + "graph.microsoft.com", + ".blob.core.windows.net", + ".table.core.windows.net", + ".vault.azure.net", + ".documents.azure.com", + ".servicebus.windows.net" + ], + "allow_port": [ + 443 + ] + } + } + }, + "status": "Active", + "tags": [ + "azure", + "microsoft", + "cloud", + "iaas", + "paas", + "infrastructure", + "database", + "storage" + ], + "tier": "Official", + "tools": [ + "acr", + "aks", + "appconfig", + "azuremanagedlustre", + "azureterraformbestpractices", + "bicepschema", + "cloudarchitect", + "cosmos", + "datadog", + "deploy", + "documentation", + "extension_az", + "extension_azd", + "extension_azqr", + "foundry", + "functionapp", + "get_bestpractices", + "grafana", + "group", + "keyvault", + "kusto", + "loadtesting", + "marketplace", + "monitor", + "mysql", + "postgres", + "quota", + "redis", + "resourcehealth", + "role", + "search", + "servicebus", + "sql", + "storage", + "subscription", + "virtualdesktop", + "workbooks" + ] + } + } + } + } +} diff --git a/registries/toolhive/servers/brightdata-mcp/server.json b/registries/toolhive/servers/brightdata-mcp/server.json new file mode 100644 index 00000000..47909949 --- /dev/null +++ b/registries/toolhive/servers/brightdata-mcp/server.json @@ -0,0 +1,96 @@ +{ + "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json", + "name": "io.github.stacklok/brightdata-mcp", + "description": "An MCP interface into the Bright Data toolset for web scraping and data extraction", + "title": "brightdata-mcp", + "repository": { + "url": "https://github.com/brightdata/brightdata-mcp", + "source": "github" + }, + "version": "1.0.0", + "packages": [ + { + "registryType": "oci", + "identifier": "ghcr.io/stacklok/dockyard/npx/brightdata-mcp:2.8.4", + "transport": { + "type": "stdio" + }, + "environmentVariables": [ + { + "description": "Bright Data API token for authentication", + "isRequired": true, + "isSecret": true, + "name": "API_TOKEN" + }, + { + "description": "Rate limiting configuration (format: limit/time+unit, e.g., 100/1h, 50/30m, 10/5s)", + "name": "RATE_LIMIT" + }, + { + "description": "Custom Web Unlocker zone name", + "default": "mcp_unlocker", + "name": "WEB_UNLOCKER_ZONE" + }, + { + "description": "Custom Browser API zone name", + "default": "mcp_browser", + "name": "BROWSER_ZONE" + }, + { + "description": "Enable pro mode to access all tools including browser automation and web data extraction", + "default": "false", + "name": "PRO_MODE" + } + ] + } + ], + "_meta": { + "io.modelcontextprotocol.registry/publisher-provided": { + "io.github.stacklok": { + "ghcr.io/stacklok/dockyard/npx/brightdata-mcp:2.8.4": { + "metadata": { + "last_updated": "2026-02-09T03:01:53Z", + "pulls": 65, + "stars": 2007 + }, + "permissions": { + "network": { + "outbound": { + "allow_host": [ + "api.brightdata.com", + "brightdata.com" + ], + "allow_port": [ + 443, + 80 + ] + } + } + }, + "provenance": { + "cert_issuer": "https://token.actions.githubusercontent.com", + "repository_uri": "https://github.com/stacklok/dockyard", + "runner_environment": "github-hosted", + "signer_identity": "/.github/workflows/build-containers.yml", + "sigstore_url": "tuf-repo-cdn.sigstore.dev" + }, + "status": "Active", + "tags": [ + "web-scraping", + "data-extraction", + "api", + "automation", + "browser" + ], + "tier": "Community", + "tools": [ + "scrape_as_markdown", + "scrape_batch", + "search_engine", + "search_engine_batch" + ] + } + } + } + } +} diff --git a/registries/toolhive/servers/browserbase/server.json b/registries/toolhive/servers/browserbase/server.json new file mode 100644 index 00000000..21a85de4 --- /dev/null +++ b/registries/toolhive/servers/browserbase/server.json @@ -0,0 +1,86 @@ +{ + "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json", + "name": "io.github.stacklok/browserbase", + "description": "MCP server for cloud browser automation with Browserbase and Stagehand", + "title": "browserbase", + "repository": { + "url": "https://github.com/browserbase/mcp-server-browserbase", + "source": "github" + }, + "version": "1.0.0", + "packages": [ + { + "registryType": "oci", + "identifier": "ghcr.io/stacklok/dockyard/npx/browserbase-mcp-server:2.4.3", + "transport": { + "type": "stdio" + }, + "environmentVariables": [ + { + "description": "Browserbase API key", + "isRequired": true, + "isSecret": true, + "name": "BROWSERBASE_API_KEY" + }, + { + "description": "Browserbase project ID", + "isRequired": true, + "name": "BROWSERBASE_PROJECT_ID" + }, + { + "description": "Google Gemini API key for Stagehand", + "isRequired": true, + "isSecret": true, + "name": "GEMINI_API_KEY" + } + ] + } + ], + "_meta": { + "io.modelcontextprotocol.registry/publisher-provided": { + "io.github.stacklok": { + "ghcr.io/stacklok/dockyard/npx/browserbase-mcp-server:2.4.3": { + "metadata": { + "last_updated": "2026-02-14T02:56:20Z", + "pulls": 133, + "stars": 3128 + }, + "permissions": { + "network": { + "outbound": { + "insecure_allow_all": true + } + } + }, + "provenance": { + "cert_issuer": "https://token.actions.githubusercontent.com", + "repository_uri": "https://github.com/stacklok/dockyard", + "runner_environment": "github-hosted", + "signer_identity": "/.github/workflows/build-containers.yml", + "sigstore_url": "tuf-repo-cdn.sigstore.dev" + }, + "status": "Active", + "tags": [ + "browser", + "automation", + "web-scraping", + "testing", + "stagehand" + ], + "tier": "Official", + "tools": [ + "browserbase_screenshot", + "browserbase_session_close", + "browserbase_session_create", + "browserbase_stagehand_act", + "browserbase_stagehand_agent", + "browserbase_stagehand_extract", + "browserbase_stagehand_get_url", + "browserbase_stagehand_navigate", + "browserbase_stagehand_observe" + ] + } + } + } + } +} diff --git a/registries/toolhive/servers/buildkite/server.json b/registries/toolhive/servers/buildkite/server.json new file mode 100644 index 00000000..92588263 --- /dev/null +++ b/registries/toolhive/servers/buildkite/server.json @@ -0,0 +1,101 @@ +{ + "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json", + "name": "io.github.stacklok/buildkite", + "description": "Connect your Buildkite data (pipelines, builds, jobs, tests) to AI tooling and editors.", + "title": "buildkite", + "repository": { + "url": "https://github.com/buildkite/buildkite-mcp-server", + "source": "github" + }, + "version": "1.0.0", + "packages": [ + { + "registryType": "oci", + "identifier": "ghcr.io/buildkite/buildkite-mcp-server:0.10.0", + "transport": { + "type": "stdio" + }, + "environmentVariables": [ + { + "description": "Your Buildkite API access token", + "isRequired": true, + "isSecret": true, + "name": "BUILDKITE_API_TOKEN" + }, + { + "description": "Token threshold for job logs. If exceeded, logs will be written to disk and returned by path (for local use only).", + "name": "JOB_LOG_TOKEN_THRESHOLD" + } + ] + } + ], + "_meta": { + "io.modelcontextprotocol.registry/publisher-provided": { + "io.github.stacklok": { + "ghcr.io/buildkite/buildkite-mcp-server:0.10.0": { + "args": [ + "stdio" + ], + "metadata": { + "last_updated": "2026-01-30T02:55:47Z", + "stars": 45 + }, + "permissions": { + "network": { + "outbound": { + "allow_host": [ + ".buildkite.com" + ], + "allow_port": [ + 443 + ] + } + } + }, + "status": "Active", + "tags": [ + "buildkite", + "continuous-integration", + "continuous-delivery", + "pipelines", + "builds", + "jobs", + "devops", + "testing" + ], + "tier": "Official", + "tools": [ + "access_token", + "create_build", + "create_pipeline", + "current_user", + "get_artifact", + "get_build", + "get_build_test_engine_runs", + "get_cluster", + "get_cluster_queue", + "get_failed_executions", + "get_pipeline", + "get_test", + "get_test_run", + "list_annotations", + "list_artifacts_for_build", + "list_artifacts_for_job", + "list_builds", + "list_cluster_queues", + "list_clusters", + "list_pipelines", + "list_test_runs", + "read_logs", + "search_logs", + "tail_logs", + "unblock_job", + "update_pipeline", + "user_token_organization", + "wait_for_build" + ] + } + } + } + } +} diff --git a/registries/toolhive/servers/canva/server.json b/registries/toolhive/servers/canva/server.json new file mode 100644 index 00000000..1f286f21 --- /dev/null +++ b/registries/toolhive/servers/canva/server.json @@ -0,0 +1,62 @@ +{ + "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json", + "name": "io.github.stacklok/canva", + "description": "Canva's official remote MCP server for design creation and template management", + "title": "canva", + "version": "1.0.0", + "remotes": [ + { + "type": "streamable-http", + "url": "https://mcp.canva.com/mcp" + } + ], + "_meta": { + "io.modelcontextprotocol.registry/publisher-provided": { + "io.github.stacklok": { + "https://mcp.canva.com/mcp": { + "custom_metadata": { + "author": "Canva", + "homepage": "https://www.canva.dev/docs/connect/canva-mcp-server-setup/" + }, + "metadata": { + "last_updated": "2026-02-05T04:49:18Z" + }, + "status": "Active", + "tags": [ + "remote", + "canva", + "design", + "graphics", + "templates", + "oauth", + "creative", + "visual-content", + "export", + "collaboration" + ], + "tier": "Official", + "tools": [ + "upload-asset-from-url", + "get-asset-upload-from-url-status", + "search-designs", + "get-design", + "get-design-pages", + "get-design-content", + "import-design-from-url", + "get-design-import-from-url-status", + "export-design", + "get-export-formats", + "get-design-export-status", + "create-folder", + "move-item-to-folder", + "list-folder-items", + "comment-on-design", + "list-comments", + "list-replies", + "reply-to-comment" + ] + } + } + } + } +} diff --git a/registries/toolhive/servers/chroma-mcp/server.json b/registries/toolhive/servers/chroma-mcp/server.json new file mode 100644 index 00000000..55ba84b6 --- /dev/null +++ b/registries/toolhive/servers/chroma-mcp/server.json @@ -0,0 +1,81 @@ +{ + "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json", + "name": "io.github.stacklok/chroma-mcp", + "description": "MCP server for ChromaDB vector database operations", + "title": "chroma-mcp", + "repository": { + "url": "https://github.com/chroma-core/chroma-mcp", + "source": "github" + }, + "version": "1.0.0", + "packages": [ + { + "registryType": "oci", + "identifier": "ghcr.io/stacklok/dockyard/uvx/chroma-mcp:0.2.6", + "transport": { + "type": "stdio" + }, + "environmentVariables": [ + { + "description": "ChromaDB server URL", + "default": "http://localhost:8000", + "name": "CHROMA_SERVER_URL" + }, + { + "description": "API key for ChromaDB authentication", + "isSecret": true, + "name": "CHROMA_API_KEY" + } + ] + } + ], + "_meta": { + "io.modelcontextprotocol.registry/publisher-provided": { + "io.github.stacklok": { + "ghcr.io/stacklok/dockyard/uvx/chroma-mcp:0.2.6": { + "metadata": { + "last_updated": "2026-02-05T04:47:28Z", + "stars": 483 + }, + "permissions": { + "network": { + "outbound": { + "insecure_allow_all": true + } + } + }, + "provenance": { + "cert_issuer": "https://token.actions.githubusercontent.com", + "repository_uri": "https://github.com/stacklok/dockyard", + "runner_environment": "github-hosted", + "signer_identity": "/.github/workflows/build-containers.yml", + "sigstore_url": "tuf-repo-cdn.sigstore.dev" + }, + "status": "Active", + "tags": [ + "database", + "vector-database", + "embeddings", + "ai", + "chromadb" + ], + "tier": "Official", + "tools": [ + "chroma_list_collections", + "chroma_create_collection", + "chroma_peek_collection", + "chroma_get_collection_info", + "chroma_get_collection_count", + "chroma_modify_collection", + "chroma_delete_collection", + "chroma_add_documents", + "chroma_query_documents", + "chroma_get_documents", + "chroma_update_documents", + "chroma_delete_documents" + ] + } + } + } + } +} diff --git a/registries/toolhive/servers/chrome-devtools-mcp/server.json b/registries/toolhive/servers/chrome-devtools-mcp/server.json new file mode 100644 index 00000000..9fbe8fb9 --- /dev/null +++ b/registries/toolhive/servers/chrome-devtools-mcp/server.json @@ -0,0 +1,90 @@ +{ + "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json", + "name": "io.github.stacklok/chrome-devtools-mcp", + "description": "Control and inspect live Chrome browser from AI coding agents with full DevTools access", + "title": "chrome-devtools-mcp", + "repository": { + "url": "https://github.com/ChromeDevTools/chrome-devtools-mcp", + "source": "github" + }, + "version": "1.0.0", + "packages": [ + { + "registryType": "oci", + "identifier": "ghcr.io/stacklok/dockyard/npx/chrome-devtools-mcp:0.17.0", + "transport": { + "type": "stdio" + } + } + ], + "_meta": { + "io.modelcontextprotocol.registry/publisher-provided": { + "io.github.stacklok": { + "ghcr.io/stacklok/dockyard/npx/chrome-devtools-mcp:0.17.0": { + "metadata": { + "last_updated": "2026-02-05T02:58:04Z", + "stars": 23258 + }, + "permissions": { + "network": { + "outbound": { + "allow_port": [ + 443, + 80 + ], + "insecure_allow_all": true + } + } + }, + "provenance": { + "cert_issuer": "https://token.actions.githubusercontent.com", + "repository_uri": "https://github.com/stacklok/dockyard", + "runner_environment": "github-hosted", + "signer_identity": "/.github/workflows/build-containers.yml", + "sigstore_url": "tuf-repo-cdn.sigstore.dev" + }, + "status": "Active", + "tags": [ + "chrome", + "devtools", + "browser", + "automation", + "debugging", + "performance", + "puppeteer", + "testing" + ], + "tier": "Official", + "tools": [ + "click", + "close_page", + "drag", + "emulate", + "evaluate_script", + "fill", + "fill_form", + "get_console_message", + "get_network_request", + "handle_dialog", + "hover", + "list_console_messages", + "list_network_requests", + "list_pages", + "navigate_page", + "new_page", + "performance_analyze_insight", + "performance_start_trace", + "performance_stop_trace", + "press_key", + "resize_page", + "select_page", + "take_screenshot", + "take_snapshot", + "upload_file", + "wait_for" + ] + } + } + } + } +} diff --git a/registries/toolhive/servers/cloud-run/server.json b/registries/toolhive/servers/cloud-run/server.json new file mode 100644 index 00000000..e075d4c1 --- /dev/null +++ b/registries/toolhive/servers/cloud-run/server.json @@ -0,0 +1,94 @@ +{ + "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json", + "name": "io.github.stacklok/cloud-run", + "description": "Deploy apps to Google Cloud Run with integrated logging and service management", + "title": "cloud-run", + "repository": { + "url": "https://github.com/GoogleCloudPlatform/cloud-run-mcp", + "source": "github" + }, + "version": "1.0.0", + "packages": [ + { + "registryType": "oci", + "identifier": "docker.io/mcp/cloud-run-mcp:latest", + "transport": { + "type": "stdio" + }, + "environmentVariables": [ + { + "description": "Path to Google Cloud credentials JSON file", + "isSecret": true, + "name": "GOOGLE_APPLICATION_CREDENTIALS" + }, + { + "description": "Google Cloud project ID for deployments", + "name": "GOOGLE_CLOUD_PROJECT" + }, + { + "description": "Default Google Cloud region for deployments", + "name": "GOOGLE_CLOUD_REGION" + }, + { + "description": "Default Cloud Run service name", + "name": "DEFAULT_SERVICE_NAME" + }, + { + "description": "Skip IAM permission checks (true/false)", + "name": "SKIP_IAM_CHECK" + } + ] + } + ], + "_meta": { + "io.modelcontextprotocol.registry/publisher-provided": { + "io.github.stacklok": { + "docker.io/mcp/cloud-run-mcp:latest": { + "metadata": { + "last_updated": "2026-02-05T04:49:20Z", + "pulls": 273, + "stars": 532 + }, + "permissions": { + "network": { + "outbound": { + "allow_host": [ + "run.googleapis.com", + "cloudbuild.googleapis.com", + "storage.googleapis.com", + "logging.googleapis.com", + "cloudresourcemanager.googleapis.com" + ], + "allow_port": [ + 443 + ] + } + } + }, + "status": "Active", + "tags": [ + "google-cloud", + "cloud-run", + "deployment", + "gcp", + "serverless", + "containers", + "devops" + ], + "tier": "Official", + "tools": [ + "deploy_file_contents", + "deploy_local_files", + "deploy_local_folder", + "deploy_container_image", + "list_services", + "get_service", + "get_service_log", + "list_projects", + "create_project" + ] + } + } + } + } +} diff --git a/registries/toolhive/servers/context7-remote/server.json b/registries/toolhive/servers/context7-remote/server.json new file mode 100644 index 00000000..df8a95f1 --- /dev/null +++ b/registries/toolhive/servers/context7-remote/server.json @@ -0,0 +1,44 @@ +{ + "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json", + "name": "io.github.stacklok/context7-remote", + "description": "Remote Context7 MCP server pulls version-specific docs and code examples directly into your prompt", + "title": "context7-remote", + "repository": { + "url": "https://github.com/upstash/context7", + "source": "github" + }, + "version": "1.0.0", + "remotes": [ + { + "type": "streamable-http", + "url": "https://mcp.context7.com/mcp" + } + ], + "_meta": { + "io.modelcontextprotocol.registry/publisher-provided": { + "io.github.stacklok": { + "https://mcp.context7.com/mcp": { + "custom_metadata": { + "author": "Upstash", + "homepage": "https://context7.com/" + }, + "metadata": { + "last_updated": "2026-02-05T04:47:23Z", + "pulls": 313, + "stars": 44756 + }, + "status": "Active", + "tags": [ + "documentation", + "code-examples" + ], + "tier": "Official", + "tools": [ + "resolve-library-id", + "get-library-docs" + ] + } + } + } + } +} diff --git a/registries/toolhive/servers/context7/server.json b/registries/toolhive/servers/context7/server.json new file mode 100644 index 00000000..d73aef12 --- /dev/null +++ b/registries/toolhive/servers/context7/server.json @@ -0,0 +1,69 @@ +{ + "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json", + "name": "io.github.stacklok/context7", + "description": "Context7 MCP pulls version-specific docs and code examples directly into your prompt", + "title": "context7", + "repository": { + "url": "https://github.com/upstash/context7", + "source": "github" + }, + "version": "1.0.0", + "packages": [ + { + "registryType": "oci", + "identifier": "ghcr.io/stacklok/dockyard/npx/context7:2.1.1", + "transport": { + "type": "stdio" + }, + "environmentVariables": [ + { + "description": "API key for higher rate limits", + "isSecret": true, + "name": "CONTEXT7_API_KEY" + } + ] + } + ], + "_meta": { + "io.modelcontextprotocol.registry/publisher-provided": { + "io.github.stacklok": { + "ghcr.io/stacklok/dockyard/npx/context7:2.1.1": { + "metadata": { + "last_updated": "2026-02-05T04:49:19Z", + "pulls": 313, + "stars": 44756 + }, + "permissions": { + "network": { + "outbound": { + "allow_host": [ + "context7.com" + ], + "allow_port": [ + 443 + ] + } + } + }, + "provenance": { + "cert_issuer": "https://token.actions.githubusercontent.com", + "repository_uri": "https://github.com/stacklok/dockyard", + "runner_environment": "github-hosted", + "signer_identity": "/.github/workflows/build-containers.yml", + "sigstore_url": "tuf-repo-cdn.sigstore.dev" + }, + "status": "Active", + "tags": [ + "documentation", + "code-examples" + ], + "tier": "Official", + "tools": [ + "query-docs", + "resolve-library-id" + ] + } + } + } + } +} diff --git a/registries/toolhive/servers/crowdstrike-falcon/server.json b/registries/toolhive/servers/crowdstrike-falcon/server.json new file mode 100644 index 00000000..ba328dcb --- /dev/null +++ b/registries/toolhive/servers/crowdstrike-falcon/server.json @@ -0,0 +1,123 @@ +{ + "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json", + "name": "io.github.stacklok/crowdstrike-falcon", + "description": "CrowdStrike Falcon integration for security analysis, detections, incidents, and threat intel", + "title": "crowdstrike-falcon", + "repository": { + "url": "https://github.com/crowdstrike/falcon-mcp", + "source": "github" + }, + "version": "1.0.0", + "packages": [ + { + "registryType": "oci", + "identifier": "quay.io/crowdstrike/falcon-mcp:latest", + "transport": { + "type": "streamable-http", + "url": "http://localhost:8000" + }, + "environmentVariables": [ + { + "description": "CrowdStrike API client ID", + "isRequired": true, + "isSecret": true, + "name": "FALCON_CLIENT_ID" + }, + { + "description": "CrowdStrike API client secret", + "isRequired": true, + "isSecret": true, + "name": "FALCON_CLIENT_SECRET" + }, + { + "description": "CrowdStrike API base URL (e.g., https://api.crowdstrike.com, https://api.us-2.crowdstrike.com, https://api.eu-1.crowdstrike.com)", + "isRequired": true, + "name": "FALCON_BASE_URL" + }, + { + "description": "Comma-separated list of modules to enable (detections,incidents,intel,hosts,spotlight,cloud,idp). If not set, all modules are enabled.", + "name": "FALCON_MCP_MODULES" + }, + { + "description": "Enable debug logging - true or false (default: false)", + "name": "FALCON_MCP_DEBUG" + } + ] + } + ], + "_meta": { + "io.modelcontextprotocol.registry/publisher-provided": { + "io.github.stacklok": { + "quay.io/crowdstrike/falcon-mcp:latest": { + "args": [ + "--transport", + "streamable-http", + "--host", + "0.0.0.0", + "--port", + "8000" + ], + "metadata": { + "last_updated": "2026-02-05T04:47:22Z", + "pulls": 3771, + "stars": 98 + }, + "permissions": { + "network": { + "outbound": { + "allow_host": [ + "api.crowdstrike.com", + "api.us-2.crowdstrike.com", + "api.eu-1.crowdstrike.com", + "api.laggar.gcw.crowdstrike.com" + ], + "allow_port": [ + 443 + ] + } + } + }, + "status": "Active", + "tags": [ + "crowdstrike", + "falcon", + "security", + "cybersecurity", + "threat-intelligence", + "detections", + "incidents", + "vulnerabilities", + "endpoint-security", + "threat-hunting", + "incident-response", + "malware-analysis", + "identity-protection", + "cloud-security" + ], + "tier": "Official", + "tools": [ + "falcon_check_connectivity", + "falcon_get_available_modules", + "falcon_search_detections", + "falcon_get_detection_details", + "falcon_show_crowd_score", + "falcon_search_incidents", + "falcon_get_incident_details", + "falcon_search_behaviors", + "falcon_get_behavior_details", + "falcon_search_actors", + "falcon_search_indicators", + "falcon_search_reports", + "falcon_search_hosts", + "falcon_get_host_details", + "falcon_search_vulnerabilities", + "falcon_search_kubernetes_containers", + "falcon_count_kubernetes_containers", + "falcon_search_images_vulnerabilities", + "idp_investigate_entity" + ] + } + } + } + } +} diff --git a/registries/toolhive/servers/database-toolbox/server.json b/registries/toolhive/servers/database-toolbox/server.json new file mode 100644 index 00000000..4283c594 --- /dev/null +++ b/registries/toolhive/servers/database-toolbox/server.json @@ -0,0 +1,65 @@ +{ + "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json", + "name": "io.github.stacklok/database-toolbox", + "description": "Database operations MCP server with connection pooling, authentication, and observability", + "title": "database-toolbox", + "repository": { + "url": "https://github.com/googleapis/genai-toolbox", + "source": "github" + }, + "version": "1.0.0", + "packages": [ + { + "registryType": "oci", + "identifier": "us-central1-docker.pkg.dev/database-toolbox/toolbox/toolbox:0.27.0", + "transport": { + "type": "streamable-http", + "url": "http://localhost:5000" + } + } + ], + "_meta": { + "io.modelcontextprotocol.registry/publisher-provided": { + "io.github.stacklok": { + "us-central1-docker.pkg.dev/database-toolbox/toolbox/toolbox:0.27.0": { + "args": [ + "--address", + "0.0.0.0" + ], + "metadata": { + "last_updated": "2026-02-16T03:01:21Z", + "pulls": 2408, + "stars": 12997 + }, + "permissions": { + "network": { + "outbound": { + "insecure_allow_all": true + } + } + }, + "status": "Active", + "tags": [ + "database", + "sql", + "postgresql", + "mysql", + "sqlite", + "mongodb", + "redis", + "connection-pooling", + "authentication", + "observability", + "toolbox", + "genai", + "mcp-server" + ], + "tier": "Official", + "tools": [ + "set_during_runtime" + ] + } + } + } + } +} diff --git a/registries/toolhive/servers/dolt/server.json b/registries/toolhive/servers/dolt/server.json new file mode 100644 index 00000000..fd3d0615 --- /dev/null +++ b/registries/toolhive/servers/dolt/server.json @@ -0,0 +1,149 @@ +{ + "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json", + "name": "io.github.stacklok/dolt", + "description": "Git-like version control for SQL databases with branching, merging, and data versioning", + "title": "dolt", + "repository": { + "url": "https://github.com/dolthub/dolt-mcp", + "source": "github" + }, + "version": "1.0.0", + "packages": [ + { + "registryType": "oci", + "identifier": "docker.io/dolthub/dolt-mcp:0.3.4", + "transport": { + "type": "streamable-http", + "url": "http://localhost:8080" + }, + "environmentVariables": [ + { + "description": "Hostname of the Dolt SQL server", + "isRequired": true, + "name": "DOLT_HOST" + }, + { + "description": "Dolt server port", + "default": "3306", + "name": "DOLT_PORT" + }, + { + "description": "Username for Dolt server authentication", + "isRequired": true, + "name": "DOLT_USER" + }, + { + "description": "Password for Dolt server authentication", + "isSecret": true, + "name": "DOLT_PASSWORD" + }, + { + "description": "Name of the database to connect to", + "isRequired": true, + "name": "DOLT_DATABASE" + }, + { + "description": "Server mode (stdio or http)", + "default": "http", + "name": "MCP_MODE" + }, + { + "description": "HTTP server port (HTTP mode only)", + "default": "8080", + "name": "MCP_PORT" + } + ] + } + ], + "_meta": { + "io.modelcontextprotocol.registry/publisher-provided": { + "io.github.stacklok": { + "docker.io/dolthub/dolt-mcp:0.3.4": { + "metadata": { + "last_updated": "2026-02-09T09:15:26Z", + "pulls": 993, + "stars": 6 + }, + "permissions": { + "network": { + "outbound": { + "allow_host": [ + "localhost", + "dolthub.com", + ".dolthub.com" + ], + "allow_port": [ + 3306, + 443, + 80, + 8080 + ] + } + } + }, + "status": "Active", + "tags": [ + "database", + "version-control", + "sql", + "mysql", + "git", + "collaboration", + "data-science", + "branching", + "merging", + "reproducibility" + ], + "tier": "Official", + "tools": [ + "add_dolt_remote", + "add_dolt_test", + "alter_table", + "clone_database", + "create_database", + "create_dolt_branch", + "create_dolt_branch_from_head", + "create_dolt_commit", + "create_table", + "delete_dolt_branch", + "describe_table", + "dolt_fetch_all_branches", + "dolt_fetch_branch", + "dolt_pull_branch", + "dolt_push_branch", + "dolt_reset_hard", + "dolt_reset_soft", + "drop_database", + "drop_table", + "exec", + "get_dolt_merge_status", + "kill_process", + "list_databases", + "list_dolt_branches", + "list_dolt_commits", + "list_dolt_diff_changes_by_table_name", + "list_dolt_diff_changes_in_date_range", + "list_dolt_diff_changes_in_working_set", + "list_dolt_remotes", + "merge_dolt_branch", + "merge_dolt_branch_no_fast_forward", + "move_dolt_branch", + "query", + "remove_dolt_remote", + "remove_dolt_test", + "run_dolt_tests", + "select_active_branch", + "select_version", + "show_create_table", + "show_processlist", + "show_tables", + "stage_all_tables_for_dolt_commit", + "stage_table_for_dolt_commit", + "unstage_all_tables", + "unstage_table" + ] + } + } + } + } +} diff --git a/registries/toolhive/servers/elasticsearch/server.json b/registries/toolhive/servers/elasticsearch/server.json new file mode 100644 index 00000000..70ff43bc --- /dev/null +++ b/registries/toolhive/servers/elasticsearch/server.json @@ -0,0 +1,104 @@ +{ + "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json", + "name": "io.github.stacklok/elasticsearch", + "description": "Connect to your Elasticsearch data.", + "title": "elasticsearch", + "repository": { + "url": "https://github.com/elastic/mcp-server-elasticsearch", + "source": "github" + }, + "version": "1.0.0", + "packages": [ + { + "registryType": "oci", + "identifier": "docker.elastic.co/mcp/elasticsearch:0.4.6", + "transport": { + "type": "streamable-http", + "url": "http://localhost:8080" + }, + "environmentVariables": [ + { + "description": "Your Elasticsearch instance URL", + "isRequired": true, + "name": "ES_URL" + }, + { + "description": "Elasticsearch API key for authentication", + "isSecret": true, + "name": "ES_API_KEY" + }, + { + "description": "Elasticsearch username for basic authentication", + "name": "ES_USERNAME" + }, + { + "description": "Elasticsearch password for basic authentication", + "isSecret": true, + "name": "ES_PASSWORD" + }, + { + "description": "Path to custom CA certificate for Elasticsearch SSL/TLS", + "name": "ES_CA_CERT" + }, + { + "description": "Set to '1' or 'true' to skip SSL certificate verification", + "name": "ES_SSL_SKIP_VERIFY" + }, + { + "description": "Path prefix for Elasticsearch instance exposed at a non-root path", + "name": "ES_PATH_PREFIX" + }, + { + "description": "Server assumes Elasticsearch 9.x. Set to 8 target Elasticsearch 8.x", + "name": "ES_VERSION" + } + ] + } + ], + "_meta": { + "io.modelcontextprotocol.registry/publisher-provided": { + "io.github.stacklok": { + "docker.elastic.co/mcp/elasticsearch:0.4.6": { + "args": [ + "http" + ], + "metadata": { + "last_updated": "2026-02-05T02:58:03Z", + "pulls": 10995, + "stars": 603 + }, + "permissions": { + "network": { + "outbound": { + "allow_port": [ + 443, + 9200 + ], + "insecure_allow_all": true + } + } + }, + "status": "Active", + "tags": [ + "elasticsearch", + "search", + "analytics", + "data", + "alerting", + "observability", + "metrics", + "logs" + ], + "tier": "Official", + "tools": [ + "esql", + "get_mappings", + "get_shards", + "list_indices", + "search" + ] + } + } + } + } +} diff --git a/registries/toolhive/servers/everything/server.json b/registries/toolhive/servers/everything/server.json new file mode 100644 index 00000000..b3cd7e42 --- /dev/null +++ b/registries/toolhive/servers/everything/server.json @@ -0,0 +1,62 @@ +{ + "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json", + "name": "io.github.stacklok/everything", + "description": "This MCP server attempts to exercise all the features of the MCP protocol", + "title": "everything", + "repository": { + "url": "https://github.com/modelcontextprotocol/servers", + "source": "github" + }, + "version": "1.0.0", + "packages": [ + { + "registryType": "oci", + "identifier": "docker.io/mcp/everything:latest", + "transport": { + "type": "stdio" + } + } + ], + "_meta": { + "io.modelcontextprotocol.registry/publisher-provided": { + "io.github.stacklok": { + "docker.io/mcp/everything:latest": { + "metadata": { + "last_updated": "2026-02-09T09:15:24Z", + "pulls": 14373, + "stars": 78304 + }, + "permissions": { + "network": { + "outbound": {} + } + }, + "status": "Active", + "tags": [ + "adds", + "all", + "attempts", + "demonstrates", + "everything", + "exercise", + "features", + "returns", + "simple", + "tools" + ], + "tier": "Community", + "tools": [ + "add", + "annotatedMessage", + "echo", + "getResourceReference", + "getTinyImage", + "longRunningOperation", + "printEnv", + "sampleLLM" + ] + } + } + } + } +} diff --git a/registries/toolhive/servers/fetch/server.json b/registries/toolhive/servers/fetch/server.json new file mode 100644 index 00000000..a60b5931 --- /dev/null +++ b/registries/toolhive/servers/fetch/server.json @@ -0,0 +1,68 @@ +{ + "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json", + "name": "io.github.stacklok/fetch", + "description": "Allows you to fetch content from the web", + "title": "fetch", + "repository": { + "url": "https://github.com/stackloklabs/gofetch", + "source": "github" + }, + "version": "1.0.0", + "packages": [ + { + "registryType": "oci", + "identifier": "ghcr.io/stackloklabs/gofetch/server:1.0.3", + "transport": { + "type": "streamable-http", + "url": "http://localhost:8080" + } + } + ], + "_meta": { + "io.modelcontextprotocol.registry/publisher-provided": { + "io.github.stacklok": { + "ghcr.io/stackloklabs/gofetch/server:1.0.3": { + "metadata": { + "last_updated": "2026-02-05T04:47:22Z", + "pulls": 12390, + "stars": 20 + }, + "permissions": { + "network": { + "outbound": { + "allow_port": [ + 443 + ], + "insecure_allow_all": true + } + } + }, + "provenance": { + "cert_issuer": "https://token.actions.githubusercontent.com", + "repository_uri": "https://github.com/StacklokLabs/gofetch", + "runner_environment": "github-hosted", + "signer_identity": "/.github/workflows/release.yml", + "sigstore_url": "tuf-repo-cdn.sigstore.dev" + }, + "status": "Active", + "tags": [ + "content", + "html", + "markdown", + "fetch", + "fetching", + "get", + "wget", + "json", + "curl", + "modelcontextprotocol" + ], + "tier": "Community", + "tools": [ + "fetch" + ] + } + } + } + } +} diff --git a/registries/toolhive/servers/filesystem/server.json b/registries/toolhive/servers/filesystem/server.json new file mode 100644 index 00000000..b1f78f9c --- /dev/null +++ b/registries/toolhive/servers/filesystem/server.json @@ -0,0 +1,71 @@ +{ + "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json", + "name": "io.github.stacklok/filesystem", + "description": "Allows you to do filesystem operations. Mount paths under /projects using --volume.", + "title": "filesystem", + "repository": { + "url": "https://github.com/modelcontextprotocol/servers", + "source": "github" + }, + "version": "1.0.0", + "packages": [ + { + "registryType": "oci", + "identifier": "docker.io/mcp/filesystem:1.0.2", + "transport": { + "type": "stdio" + } + } + ], + "_meta": { + "io.modelcontextprotocol.registry/publisher-provided": { + "io.github.stacklok": { + "docker.io/mcp/filesystem:1.0.2": { + "args": [ + "/projects" + ], + "metadata": { + "last_updated": "2026-01-26T02:48:41Z", + "pulls": 20619, + "stars": 77164 + }, + "permissions": { + "network": { + "outbound": {} + } + }, + "status": "Active", + "tags": [ + "create_directory", + "edit_file", + "filesystem", + "get_file_info", + "implementing", + "list_allowed_directories", + "list_directory", + "move_file", + "node", + "operations" + ], + "tier": "Community", + "tools": [ + "create_directory", + "directory_tree", + "edit_file", + "get_file_info", + "list_allowed_directories", + "list_directory", + "list_directory_with_sizes", + "move_file", + "read_file", + "read_media_file", + "read_multiple_files", + "read_text_file", + "search_files", + "write_file" + ] + } + } + } + } +} diff --git a/registries/toolhive/servers/firecrawl/server.json b/registries/toolhive/servers/firecrawl/server.json new file mode 100644 index 00000000..c8751c4e --- /dev/null +++ b/registries/toolhive/servers/firecrawl/server.json @@ -0,0 +1,103 @@ +{ + "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json", + "name": "io.github.stacklok/firecrawl", + "description": "Web scraping and content extraction MCP server with advanced crawling and LLM integration", + "title": "firecrawl", + "repository": { + "url": "https://github.com/firecrawl/firecrawl-mcp-server", + "source": "github" + }, + "version": "1.0.0", + "packages": [ + { + "registryType": "oci", + "identifier": "docker.io/mcp/firecrawl:latest", + "transport": { + "type": "stdio" + }, + "environmentVariables": [ + { + "description": "API key for FireCrawl service authentication", + "isRequired": true, + "isSecret": true, + "name": "FIRECRAWL_API_KEY" + }, + { + "description": "FireCrawl API URL (default: https://api.firecrawl.dev/v1)", + "name": "FIRECRAWL_API_URL" + }, + { + "description": "Maximum number of retry attempts for API calls", + "name": "FIRECRAWL_RETRY_MAX_ATTEMPTS" + }, + { + "description": "Initial delay in milliseconds for retry backoff", + "name": "FIRECRAWL_RETRY_INITIAL_DELAY" + }, + { + "description": "Maximum delay in milliseconds for retry backoff", + "name": "FIRECRAWL_RETRY_MAX_DELAY" + }, + { + "description": "Backoff factor for retry delay calculation", + "name": "FIRECRAWL_RETRY_BACKOFF_FACTOR" + }, + { + "description": "Credit threshold for warning notifications", + "name": "FIRECRAWL_CREDIT_WARNING_THRESHOLD" + }, + { + "description": "Credit threshold for critical notifications", + "name": "FIRECRAWL_CREDIT_CRITICAL_THRESHOLD" + } + ] + } + ], + "_meta": { + "io.modelcontextprotocol.registry/publisher-provided": { + "io.github.stacklok": { + "docker.io/mcp/firecrawl:latest": { + "metadata": { + "last_updated": "2026-01-26T02:48:41Z", + "pulls": 12644, + "stars": 5334 + }, + "permissions": { + "network": { + "outbound": { + "allow_host": [ + "api.firecrawl.dev" + ], + "allow_port": [ + 443 + ] + } + } + }, + "status": "Active", + "tags": [ + "web-crawler", + "web-scraping", + "data-collection", + "batch-processing", + "content-extraction", + "search-api", + "llm-tools", + "javascript-rendering", + "research", + "automation" + ], + "tier": "Official", + "tools": [ + "firecrawl_check_crawl_status", + "firecrawl_crawl", + "firecrawl_extract", + "firecrawl_map", + "firecrawl_scrape", + "firecrawl_search" + ] + } + } + } + } +} diff --git a/registries/toolhive/servers/genai-toolbox/server.json b/registries/toolhive/servers/genai-toolbox/server.json new file mode 100644 index 00000000..5ade4a20 --- /dev/null +++ b/registries/toolhive/servers/genai-toolbox/server.json @@ -0,0 +1,61 @@ +{ + "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json", + "name": "io.github.stacklok/genai-toolbox", + "description": "Will be removed soon. Please use database-toolbox instead.", + "title": "genai-toolbox", + "repository": { + "url": "https://github.com/googleapis/genai-toolbox", + "source": "github" + }, + "version": "1.0.0", + "packages": [ + { + "registryType": "oci", + "identifier": "us-central1-docker.pkg.dev/database-toolbox/toolbox/toolbox:0.27.0", + "transport": { + "type": "sse", + "url": "http://localhost:5000" + } + } + ], + "_meta": { + "io.modelcontextprotocol.registry/publisher-provided": { + "io.github.stacklok": { + "us-central1-docker.pkg.dev/database-toolbox/toolbox/toolbox:0.27.0": { + "metadata": { + "last_updated": "2026-02-16T03:01:21Z", + "pulls": 2408, + "stars": 12997 + }, + "permissions": { + "network": { + "outbound": { + "insecure_allow_all": true + } + } + }, + "status": "Deprecated", + "tags": [ + "database", + "sql", + "postgresql", + "mysql", + "sqlite", + "mongodb", + "redis", + "connection-pooling", + "authentication", + "observability", + "toolbox", + "genai", + "mcp-server" + ], + "tier": "Official", + "tools": [ + "set_during_runtime" + ] + } + } + } + } +} diff --git a/registries/toolhive/servers/git/server.json b/registries/toolhive/servers/git/server.json new file mode 100644 index 00000000..9db9686c --- /dev/null +++ b/registries/toolhive/servers/git/server.json @@ -0,0 +1,65 @@ +{ + "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json", + "name": "io.github.stacklok/git", + "description": "Provides support for interacting with Git repositories", + "title": "git", + "repository": { + "url": "https://github.com/modelcontextprotocol/servers", + "source": "github" + }, + "version": "1.0.0", + "packages": [ + { + "registryType": "oci", + "identifier": "docker.io/mcp/git:latest", + "transport": { + "type": "stdio" + } + } + ], + "_meta": { + "io.modelcontextprotocol.registry/publisher-provided": { + "io.github.stacklok": { + "docker.io/mcp/git:latest": { + "metadata": { + "last_updated": "2026-01-27T02:44:59Z", + "pulls": 10404, + "stars": 77255 + }, + "permissions": { + "network": { + "outbound": {} + } + }, + "status": "Active", + "tags": [ + "adds", + "automation", + "git", + "interaction", + "records", + "repository", + "shows", + "tools", + "unstages" + ], + "tier": "Community", + "tools": [ + "git_status", + "git_diff_unstaged", + "git_diff_staged", + "git_diff", + "git_commit", + "git_add", + "git_reset", + "git_log", + "git_create_branch", + "git_checkout", + "git_show", + "git_init" + ] + } + } + } + } +} diff --git a/registries/toolhive/servers/github-remote/server.json b/registries/toolhive/servers/github-remote/server.json new file mode 100644 index 00000000..0f8515fd --- /dev/null +++ b/registries/toolhive/servers/github-remote/server.json @@ -0,0 +1,149 @@ +{ + "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json", + "name": "io.github.stacklok/github-remote", + "description": "GitHub's official MCP server for repositories, issues, PRs, actions, and security with OAuth", + "title": "github-remote", + "repository": { + "url": "https://github.com/github/github-mcp-server", + "source": "github" + }, + "version": "1.0.0", + "remotes": [ + { + "type": "streamable-http", + "url": "https://api.githubcopilot.com/mcp" + } + ], + "_meta": { + "io.modelcontextprotocol.registry/publisher-provided": { + "io.github.stacklok": { + "https://api.githubcopilot.com/mcp": { + "custom_metadata": { + "author": "GitHub", + "homepage": "https://github.com", + "license": "MIT" + }, + "metadata": { + "last_updated": "2026-02-05T04:49:20Z", + "stars": 26646 + }, + "oauth_config": { + "authorize_url": "https://github.com/login/oauth/authorize", + "scopes": [ + "repo", + "user:email" + ], + "token_url": "https://github.com/login/oauth/access_token", + "use_pkce": true + }, + "status": "Active", + "tags": [ + "remote", + "github", + "git", + "version-control", + "repositories", + "issues", + "pull-requests", + "actions", + "copilot" + ], + "tier": "Official", + "tools": [ + "add_comment_to_pending_review", + "add_issue_comment", + "add_sub_issue", + "assign_copilot_to_issue", + "cancel_workflow_run", + "create_and_submit_pull_request_review", + "create_branch", + "create_gist", + "create_issue", + "create_or_update_file", + "create_pending_pull_request_review", + "create_pull_request", + "create_pull_request_with_copilot", + "create_repository", + "delete_file", + "delete_pending_pull_request_review", + "delete_workflow_run_logs", + "dismiss_notification", + "download_workflow_run_artifact", + "fork_repository", + "get_code_scanning_alert", + "get_commit", + "get_dependabot_alert", + "get_discussion", + "get_discussion_comments", + "get_file_contents", + "get_global_security_advisory", + "get_issue", + "get_issue_comments", + "get_job_logs", + "get_latest_release", + "get_me", + "get_notification_details", + "get_pull_request", + "get_pull_request_comments", + "get_pull_request_diff", + "get_pull_request_files", + "get_pull_request_reviews", + "get_pull_request_status", + "get_release_by_tag", + "get_secret_scanning_alert", + "get_tag", + "get_team_members", + "get_teams", + "get_workflow_run", + "get_workflow_run_logs", + "get_workflow_run_usage", + "list_branches", + "list_code_scanning_alerts", + "list_commits", + "list_dependabot_alerts", + "list_discussion_categories", + "list_discussions", + "list_gists", + "list_global_security_advisories", + "list_issue_types", + "list_issues", + "list_notifications", + "list_org_repository_security_advisories", + "list_pull_requests", + "list_releases", + "list_repository_security_advisories", + "list_secret_scanning_alerts", + "list_sub_issues", + "list_tags", + "list_workflow_jobs", + "list_workflow_run_artifacts", + "list_workflow_runs", + "list_workflows", + "manage_notification_subscription", + "manage_repository_notification_subscription", + "mark_all_notifications_read", + "merge_pull_request", + "push_files", + "remove_sub_issue", + "reprioritize_sub_issue", + "request_copilot_review", + "rerun_failed_jobs", + "rerun_workflow_run", + "run_workflow", + "search_code", + "search_issues", + "search_orgs", + "search_pull_requests", + "search_repositories", + "search_users", + "submit_pending_pull_request_review", + "update_gist", + "update_issue", + "update_pull_request", + "update_pull_request_branch" + ] + } + } + } + } +} diff --git a/registries/toolhive/servers/github/server.json b/registries/toolhive/servers/github/server.json new file mode 100644 index 00000000..d04611ab --- /dev/null +++ b/registries/toolhive/servers/github/server.json @@ -0,0 +1,134 @@ +{ + "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json", + "name": "io.github.stacklok/github", + "description": "Provides integration with GitHub's APIs", + "title": "github", + "repository": { + "url": "https://github.com/github/github-mcp-server", + "source": "github" + }, + "version": "1.0.0", + "packages": [ + { + "registryType": "oci", + "identifier": "ghcr.io/github/github-mcp-server:v0.30.3", + "transport": { + "type": "stdio" + }, + "environmentVariables": [ + { + "description": "GitHub personal access token with appropriate permissions", + "isRequired": true, + "isSecret": true, + "name": "GITHUB_PERSONAL_ACCESS_TOKEN" + }, + { + "description": "GitHub Enterprise Server hostname (optional)", + "name": "GITHUB_HOST" + }, + { + "description": "Comma-separated list of toolsets to enable (e.g., 'repos,issues,pull_requests'). If not set, all toolsets are enabled. See the README for available toolsets.", + "name": "GITHUB_TOOLSETS" + }, + { + "description": "Set to '1' to enable dynamic toolset discovery", + "name": "GITHUB_DYNAMIC_TOOLSETS" + }, + { + "description": "Set to '1' to enable read-only mode, preventing any modifications to GitHub resources", + "name": "GITHUB_READ_ONLY" + } + ] + } + ], + "_meta": { + "io.modelcontextprotocol.registry/publisher-provided": { + "io.github.stacklok": { + "ghcr.io/github/github-mcp-server:v0.30.3": { + "metadata": { + "last_updated": "2026-01-28T02:42:36Z", + "pulls": 5000, + "stars": 26386 + }, + "permissions": { + "network": { + "outbound": { + "allow_host": [ + ".github.com", + ".githubusercontent.com" + ], + "allow_port": [ + 443 + ] + } + } + }, + "provenance": { + "cert_issuer": "https://token.actions.githubusercontent.com", + "repository_uri": "https://github.com/github/github-mcp-server", + "runner_environment": "github-hosted", + "signer_identity": "/.github/workflows/docker-publish.yml", + "sigstore_url": "tuf-repo-cdn.sigstore.dev" + }, + "status": "Active", + "tags": [ + "api", + "create", + "fork", + "github", + "list", + "pull-request", + "push", + "repository", + "search", + "update", + "issues" + ], + "tier": "Official", + "tools": [ + "add_comment_to_pending_review", + "add_issue_comment", + "assign_copilot_to_issue", + "create_branch", + "create_or_update_file", + "create_pull_request", + "create_repository", + "delete_file", + "fork_repository", + "get_commit", + "get_file_contents", + "get_label", + "get_latest_release", + "get_me", + "get_release_by_tag", + "get_tag", + "get_team_members", + "get_teams", + "issue_read", + "issue_write", + "list_branches", + "list_commits", + "list_issue_types", + "list_issues", + "list_pull_requests", + "list_releases", + "list_tags", + "merge_pull_request", + "pull_request_read", + "pull_request_review_write", + "push_files", + "request_copilot_review", + "search_code", + "search_issues", + "search_pull_requests", + "search_repositories", + "search_users", + "sub_issue_write", + "update_pull_request", + "update_pull_request_branch" + ] + } + } + } + } +} diff --git a/registries/toolhive/servers/gitlab/server.json b/registries/toolhive/servers/gitlab/server.json new file mode 100644 index 00000000..bf69e495 --- /dev/null +++ b/registries/toolhive/servers/gitlab/server.json @@ -0,0 +1,196 @@ +{ + "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json", + "name": "io.github.stacklok/gitlab", + "description": "Provides integration with a GitLab instance to manage projects, issues, merge requests, and more.", + "title": "gitlab", + "repository": { + "url": "https://github.com/zereight/gitlab-mcp", + "source": "github" + }, + "version": "1.0.0", + "packages": [ + { + "registryType": "oci", + "identifier": "iwakitakuma/gitlab-mcp:2.0.19", + "transport": { + "type": "streamable-http", + "url": "http://localhost:3002" + }, + "environmentVariables": [ + { + "description": "Your GitLab personal access token.", + "isRequired": true, + "isSecret": true, + "name": "GITLAB_PERSONAL_ACCESS_TOKEN" + }, + { + "description": "Your GitLab API URL.", + "default": "https://gitlab.com/api/v4", + "name": "GITLAB_API_URL" + }, + { + "description": "Default project ID. If set, overwrite this value when making an API request.", + "name": "GITLAB_PROJECT_ID" + }, + { + "description": "Optional comma-separated list of allowed project IDs. When set with a single value, acts as a default project.", + "name": "GITLAB_ALLOWED_PROJECT_IDS" + }, + { + "description": "When set to 'true', restricts the server to only expose read-only operations.", + "name": "GITLAB_READ_ONLY_MODE" + }, + { + "description": "When set to 'true', enables the wiki-related tools. By default, wiki features are disabled.", + "name": "USE_GITLAB_WIKI" + }, + { + "description": "When set to 'true', enables the milestone-related tools. By default, milestone features are disabled.", + "name": "USE_MILESTONE" + }, + { + "description": "When set to 'true', enables the pipeline-related tools. By default, pipeline features are disabled.", + "name": "USE_PIPELINE" + }, + { + "description": "Path to an authentication cookie file for GitLab instances that require cookie-based authentication.", + "name": "GITLAB_AUTH_COOKIE_PATH" + }, + { + "description": "When set to 'true', enables the Server-Sent Events transport.", + "name": "SSE" + }, + { + "description": "When set to 'true', enables the Streamable HTTP transport. If both SSE and STREAMABLE_HTTP are set to 'true', Streamable HTTP is used.", + "default": "true", + "name": "STREAMABLE_HTTP" + } + ] + } + ], + "_meta": { + "io.modelcontextprotocol.registry/publisher-provided": { + "io.github.stacklok": { + "iwakitakuma/gitlab-mcp:2.0.19": { + "metadata": { + "last_updated": "2026-02-05T04:49:18Z", + "pulls": 57519, + "stars": 986 + }, + "permissions": { + "network": { + "outbound": { + "allow_host": [ + ".gitlab.com", + ".gitlab-static.net", + ".gitlab.io", + ".gitlab.net" + ], + "allow_port": [ + 443 + ] + } + } + }, + "status": "Active", + "tags": [ + "gitlab", + "version-control", + "repository", + "issues", + "merge-requests", + "wiki", + "milestones", + "pipelines" + ], + "tier": "Community", + "tools": [ + "merge_merge_request", + "create_or_update_file", + "search_repositories", + "create_repository", + "get_file_contents", + "push_files", + "create_issue", + "create_merge_request", + "fork_repository", + "create_branch", + "get_merge_request", + "get_merge_request_diffs", + "list_merge_request_diffs", + "get_branch_diffs", + "update_merge_request", + "create_note", + "create_merge_request_thread", + "mr_discussions", + "update_merge_request_note", + "create_merge_request_note", + "get_draft_note", + "list_draft_notes", + "create_draft_note", + "update_draft_note", + "delete_draft_note", + "publish_draft_note", + "bulk_publish_draft_notes", + "update_issue_note", + "create_issue_note", + "list_issues", + "my_issues", + "get_issue", + "update_issue", + "delete_issue", + "list_issue_links", + "list_issue_discussions", + "get_issue_link", + "create_issue_link", + "delete_issue_link", + "list_namespaces", + "get_namespace", + "verify_namespace", + "get_project", + "list_projects", + "list_project_members", + "list_labels", + "get_label", + "create_label", + "update_label", + "delete_label", + "list_group_projects", + "list_wiki_pages", + "get_wiki_page", + "create_wiki_page", + "update_wiki_page", + "delete_wiki_page", + "get_repository_tree", + "list_pipelines", + "get_pipeline", + "list_pipeline_jobs", + "list_pipeline_trigger_jobs", + "get_pipeline_job", + "get_pipeline_job_output", + "create_pipeline", + "retry_pipeline", + "cancel_pipeline", + "list_merge_requests", + "list_milestones", + "get_milestone", + "create_milestone", + "edit_milestone", + "delete_milestone", + "get_milestone_issue", + "get_milestone_merge_requests", + "promote_milestone", + "get_milestone_burndown_events", + "get_users", + "list_commits", + "get_commit", + "get_commit_diff", + "list_group_iterations", + "upload_markdown", + "download_attachment" + ] + } + } + } + } +} diff --git a/registries/toolhive/servers/grafana/server.json b/registries/toolhive/servers/grafana/server.json new file mode 100644 index 00000000..22271a08 --- /dev/null +++ b/registries/toolhive/servers/grafana/server.json @@ -0,0 +1,149 @@ +{ + "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json", + "name": "io.github.stacklok/grafana", + "description": "Grafana integration for dashboard search, datasource queries, alerting, and incident response", + "title": "grafana", + "repository": { + "url": "https://github.com/grafana/mcp-grafana", + "source": "github" + }, + "version": "1.0.0", + "packages": [ + { + "registryType": "oci", + "identifier": "docker.io/grafana/mcp-grafana:0.10.0", + "transport": { + "type": "sse", + "url": "http://localhost:8000" + }, + "environmentVariables": [ + { + "description": "URL of the Grafana instance to connect to", + "isRequired": true, + "name": "GRAFANA_URL" + }, + { + "description": "Service account token with appropriate permissions", + "isRequired": true, + "isSecret": true, + "name": "GRAFANA_SERVICE_ACCOUNT_TOKEN" + }, + { + "description": "Organization ID for multi-organization Grafana instances", + "name": "GRAFANA_ORG_ID" + } + ] + } + ], + "_meta": { + "io.modelcontextprotocol.registry/publisher-provided": { + "io.github.stacklok": { + "docker.io/grafana/mcp-grafana:0.10.0": { + "metadata": { + "last_updated": "2026-02-05T04:47:24Z", + "pulls": 8120, + "stars": 2232 + }, + "permissions": { + "network": { + "outbound": { + "allow_port": [ + 443 + ], + "insecure_allow_all": true + } + } + }, + "status": "Active", + "tags": [ + "grafana", + "dashboards", + "visualization", + "monitoring", + "alerting", + "prometheus", + "loki", + "tempo", + "pyroscope", + "incidents", + "observability", + "metrics", + "logs", + "traces", + "sift", + "investigations", + "oncall" + ], + "tier": "Official", + "tools": [ + "add_activity_to_incident", + "create_alert_rule", + "create_annotation", + "create_folder", + "create_graphite_annotation", + "create_incident", + "delete_alert_rule", + "fetch_pyroscope_profile", + "find_error_pattern_logs", + "find_slow_requests", + "generate_deeplink", + "get_alert_group", + "get_alert_rule_by_uid", + "get_annotation_tags", + "get_annotations", + "get_assertions", + "get_current_oncall_users", + "get_dashboard_by_uid", + "get_dashboard_panel_queries", + "get_dashboard_property", + "get_dashboard_summary", + "get_datasource_by_name", + "get_datasource_by_uid", + "get_incident", + "get_oncall_shift", + "get_panel_image", + "get_resource_description", + "get_resource_permissions", + "get_role_assignments", + "get_role_details", + "get_sift_analysis", + "get_sift_investigation", + "list_alert_groups", + "list_alert_rules", + "list_all_roles", + "list_contact_points", + "list_datasources", + "list_incidents", + "list_loki_label_names", + "list_loki_label_values", + "list_oncall_schedules", + "list_oncall_teams", + "list_oncall_users", + "list_prometheus_label_names", + "list_prometheus_label_values", + "list_prometheus_metric_metadata", + "list_prometheus_metric_names", + "list_pyroscope_label_names", + "list_pyroscope_label_values", + "list_pyroscope_profile_types", + "list_sift_investigations", + "list_team_roles", + "list_teams", + "list_user_roles", + "list_users_by_org", + "patch_annotation", + "query_loki_logs", + "query_loki_patterns", + "query_loki_stats", + "query_prometheus", + "search_dashboards", + "search_folders", + "update_alert_rule", + "update_annotation", + "update_dashboard" + ] + } + } + } + } +} diff --git a/registries/toolhive/servers/graphlit/server.json b/registries/toolhive/servers/graphlit/server.json new file mode 100644 index 00000000..9b114864 --- /dev/null +++ b/registries/toolhive/servers/graphlit/server.json @@ -0,0 +1,203 @@ +{ + "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json", + "name": "io.github.stacklok/graphlit", + "description": "MCP server for Graphlit platform - ingest, search, and retrieve knowledge from multiple sources", + "title": "graphlit", + "repository": { + "url": "https://github.com/graphlit/graphlit-mcp-server", + "source": "github" + }, + "version": "1.0.0", + "packages": [ + { + "registryType": "oci", + "identifier": "ghcr.io/stacklok/dockyard/npx/graphlit-mcp-server:1.0.20260112001", + "transport": { + "type": "stdio" + }, + "environmentVariables": [ + { + "description": "Your Graphlit environment ID", + "isRequired": true, + "name": "GRAPHLIT_ENVIRONMENT_ID" + }, + { + "description": "Your Graphlit organization ID", + "isRequired": true, + "name": "GRAPHLIT_ORGANIZATION_ID" + }, + { + "description": "Your JWT secret for signing the JWT token", + "isRequired": true, + "isSecret": true, + "name": "GRAPHLIT_JWT_SECRET" + }, + { + "description": "Slack bot token for Slack integration", + "isSecret": true, + "name": "SLACK_BOT_TOKEN" + }, + { + "description": "Discord bot token for Discord integration", + "isSecret": true, + "name": "DISCORD_BOT_TOKEN" + }, + { + "description": "Twitter/X API token", + "isSecret": true, + "name": "TWITTER_TOKEN" + }, + { + "description": "Google refresh token for Gmail integration", + "isSecret": true, + "name": "GOOGLE_EMAIL_REFRESH_TOKEN" + }, + { + "description": "Google client ID for Gmail integration", + "name": "GOOGLE_EMAIL_CLIENT_ID" + }, + { + "description": "Google client secret for Gmail integration", + "isSecret": true, + "name": "GOOGLE_EMAIL_CLIENT_SECRET" + }, + { + "description": "Linear API key for Linear integration", + "isSecret": true, + "name": "LINEAR_API_KEY" + }, + { + "description": "GitHub personal access token", + "isSecret": true, + "name": "GITHUB_PERSONAL_ACCESS_TOKEN" + }, + { + "description": "Jira email for authentication", + "name": "JIRA_EMAIL" + }, + { + "description": "Jira API token", + "isSecret": true, + "name": "JIRA_TOKEN" + }, + { + "description": "Notion API key for Notion integration", + "isSecret": true, + "name": "NOTION_API_KEY" + } + ] + } + ], + "_meta": { + "io.modelcontextprotocol.registry/publisher-provided": { + "io.github.stacklok": { + "ghcr.io/stacklok/dockyard/npx/graphlit-mcp-server:1.0.20260112001": { + "metadata": { + "last_updated": "2026-02-14T02:56:20Z", + "pulls": 109, + "stars": 373 + }, + "permissions": { + "network": { + "outbound": { + "allow_port": [ + 443 + ], + "insecure_allow_all": true + } + } + }, + "provenance": { + "cert_issuer": "https://token.actions.githubusercontent.com", + "repository_uri": "https://github.com/stacklok/dockyard", + "runner_environment": "github-hosted", + "signer_identity": "/.github/workflows/build-containers.yml", + "sigstore_url": "tuf-repo-cdn.sigstore.dev" + }, + "status": "Active", + "tags": [ + "knowledge-base", + "rag", + "search", + "ingestion", + "data-connectors" + ], + "tier": "Official", + "tools": [ + "addContentsToCollection", + "askGraphlit", + "configureProject", + "createCollection", + "deleteCollection", + "deleteCollections", + "deleteContent", + "deleteContents", + "deleteConversation", + "deleteConversations", + "deleteFeed", + "deleteFeeds", + "describeImageContent", + "describeImageUrl", + "extractText", + "ingestBoxFiles", + "ingestDiscordMessages", + "ingestDropboxFiles", + "ingestFile", + "ingestGitHubFiles", + "ingestGitHubIssues", + "ingestGoogleDriveFiles", + "ingestGoogleEmail", + "ingestJiraIssues", + "ingestLinearIssues", + "ingestMemory", + "ingestMicrosoftEmail", + "ingestMicrosoftTeamsMessages", + "ingestNotionPages", + "ingestOneDriveFiles", + "ingestRSS", + "ingestRedditPosts", + "ingestSharePointFiles", + "ingestSlackMessages", + "ingestText", + "ingestTwitterPosts", + "ingestTwitterSearch", + "ingestUrl", + "isContentDone", + "isFeedDone", + "listBoxFolders", + "listDiscordChannels", + "listDiscordGuilds", + "listDropboxFolders", + "listGoogleCalendars", + "listLinearProjects", + "listMicrosoftCalendars", + "listNotionDatabases", + "listNotionPages", + "listSharePointFolders", + "listSharePointLibraries", + "listSlackChannels", + "promptConversation", + "publishAudio", + "publishImage", + "queryCollections", + "queryContents", + "queryConversations", + "queryFeeds", + "queryProjectUsage", + "removeContentsFromCollection", + "retrieveImages", + "retrieveSources", + "screenshotPage", + "sendEmailNotification", + "sendSlackNotification", + "sendTwitterNotification", + "sendWebHookNotification", + "webCrawl", + "webMap", + "webSearch" + ] + } + } + } + } +} diff --git a/registries/toolhive/servers/hass-mcp/server.json b/registries/toolhive/servers/hass-mcp/server.json new file mode 100644 index 00000000..cea5ad2a --- /dev/null +++ b/registries/toolhive/servers/hass-mcp/server.json @@ -0,0 +1,80 @@ +{ + "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json", + "name": "io.github.stacklok/hass-mcp", + "description": "Home Assistant integration enabling direct interaction with smart home devices and automations", + "title": "hass-mcp", + "repository": { + "url": "https://github.com/voska/hass-mcp", + "source": "github" + }, + "version": "1.0.0", + "packages": [ + { + "registryType": "oci", + "identifier": "docker.io/voska/hass-mcp:0.1.1", + "transport": { + "type": "stdio" + }, + "environmentVariables": [ + { + "description": "Home Assistant instance URL (e.g. http://homeassistant.local:8123)", + "isRequired": true, + "name": "HA_URL" + }, + { + "description": "Home Assistant Long-Lived Access Token", + "isRequired": true, + "isSecret": true, + "name": "HA_TOKEN" + } + ] + } + ], + "_meta": { + "io.modelcontextprotocol.registry/publisher-provided": { + "io.github.stacklok": { + "docker.io/voska/hass-mcp:0.1.1": { + "metadata": { + "last_updated": "2026-02-05T04:47:22Z", + "pulls": 17082, + "stars": 271 + }, + "permissions": { + "network": { + "outbound": { + "insecure_allow_all": true + } + } + }, + "status": "Active", + "tags": [ + "home-assistant", + "smart-home", + "automation", + "iot", + "sensors", + "devices", + "control", + "monitoring", + "home-automation", + "domotics" + ], + "tier": "Community", + "tools": [ + "get_version", + "get_entity", + "entity_action", + "list_entities", + "search_entities_tool", + "domain_summary_tool", + "list_automations", + "call_service_tool", + "restart_ha", + "get_history", + "get_error_log" + ] + } + } + } + } +} diff --git a/registries/toolhive/servers/heroku-mcp-server/server.json b/registries/toolhive/servers/heroku-mcp-server/server.json new file mode 100644 index 00000000..d0feaf12 --- /dev/null +++ b/registries/toolhive/servers/heroku-mcp-server/server.json @@ -0,0 +1,108 @@ +{ + "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json", + "name": "io.github.stacklok/heroku-mcp-server", + "description": "MCP server for seamless interaction between LLMs and the Heroku Platform", + "title": "heroku-mcp-server", + "repository": { + "url": "https://github.com/heroku/heroku-mcp-server", + "source": "github" + }, + "version": "1.0.0", + "packages": [ + { + "registryType": "oci", + "identifier": "ghcr.io/stacklok/dockyard/npx/heroku-mcp-server:1.0.7", + "transport": { + "type": "stdio" + }, + "environmentVariables": [ + { + "description": "Your Heroku authorization token", + "isRequired": true, + "isSecret": true, + "name": "HEROKU_API_KEY" + }, + { + "description": "Timeout in milliseconds for command execution", + "default": "15000", + "name": "MCP_SERVER_REQUEST_TIMEOUT" + } + ] + } + ], + "_meta": { + "io.modelcontextprotocol.registry/publisher-provided": { + "io.github.stacklok": { + "ghcr.io/stacklok/dockyard/npx/heroku-mcp-server:1.0.7": { + "metadata": { + "last_updated": "2026-02-05T04:49:21Z", + "stars": 73 + }, + "permissions": { + "network": { + "outbound": { + "allow_host": [ + ".heroku.com", + ".herokuapp.com" + ], + "allow_port": [ + 443 + ] + } + } + }, + "provenance": { + "cert_issuer": "https://token.actions.githubusercontent.com", + "repository_uri": "https://github.com/stacklok/dockyard", + "runner_environment": "github-hosted", + "signer_identity": "/.github/workflows/build-containers.yml", + "sigstore_url": "tuf-repo-cdn.sigstore.dev" + }, + "status": "Active", + "tags": [ + "heroku", + "paas", + "deployment", + "cloud", + "devops" + ], + "tier": "Official", + "tools": [ + "list_apps", + "get_app_info", + "create_app", + "rename_app", + "transfer_app", + "deploy_to_heroku", + "deploy_one_off_dyno", + "ps_list", + "ps_scale", + "ps_restart", + "list_addons", + "get_addon_info", + "create_addon", + "maintenance_on", + "maintenance_off", + "get_app_logs", + "pipelines_create", + "pipelines_promote", + "pipelines_list", + "pipelines_info", + "list_teams", + "list_private_spaces", + "pg_psql", + "pg_info", + "pg_ps", + "pg_locks", + "pg_outliers", + "pg_credentials", + "pg_kill", + "pg_maintenance", + "pg_backups", + "pg_upgrade" + ] + } + } + } + } +} diff --git a/registries/toolhive/servers/heroku/server.json b/registries/toolhive/servers/heroku/server.json new file mode 100644 index 00000000..eee7ec72 --- /dev/null +++ b/registries/toolhive/servers/heroku/server.json @@ -0,0 +1,109 @@ +{ + "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json", + "name": "io.github.stacklok/heroku", + "description": "MCP server for seamless interaction between LLMs and the Heroku Platform", + "title": "heroku", + "repository": { + "url": "https://github.com/heroku/heroku-mcp-server", + "source": "github" + }, + "version": "1.0.0", + "packages": [ + { + "registryType": "oci", + "identifier": "ghcr.io/stacklok/dockyard/npx/heroku-mcp-server:1.0.7", + "transport": { + "type": "stdio" + }, + "environmentVariables": [ + { + "description": "Your Heroku authorization token", + "isRequired": true, + "isSecret": true, + "name": "HEROKU_API_KEY" + }, + { + "description": "Timeout in milliseconds for command execution", + "default": "15000", + "name": "MCP_SERVER_REQUEST_TIMEOUT" + } + ] + } + ], + "_meta": { + "io.modelcontextprotocol.registry/publisher-provided": { + "io.github.stacklok": { + "ghcr.io/stacklok/dockyard/npx/heroku-mcp-server:1.0.7": { + "metadata": { + "last_updated": "2026-01-25T13:39:52Z", + "pulls": 104, + "stars": 73 + }, + "permissions": { + "network": { + "outbound": { + "allow_host": [ + ".heroku.com", + ".herokuapp.com" + ], + "allow_port": [ + 443 + ] + } + } + }, + "provenance": { + "cert_issuer": "https://token.actions.githubusercontent.com", + "repository_uri": "https://github.com/stacklok/dockyard", + "runner_environment": "github-hosted", + "signer_identity": "/.github/workflows/build-containers.yml", + "sigstore_url": "tuf-repo-cdn.sigstore.dev" + }, + "status": "Active", + "tags": [ + "heroku", + "paas", + "deployment", + "cloud", + "devops" + ], + "tier": "Official", + "tools": [ + "list_apps", + "get_app_info", + "create_app", + "rename_app", + "transfer_app", + "deploy_to_heroku", + "deploy_one_off_dyno", + "ps_list", + "ps_scale", + "ps_restart", + "list_addons", + "get_addon_info", + "create_addon", + "maintenance_on", + "maintenance_off", + "get_app_logs", + "pipelines_create", + "pipelines_promote", + "pipelines_list", + "pipelines_info", + "list_teams", + "list_private_spaces", + "pg_psql", + "pg_info", + "pg_ps", + "pg_locks", + "pg_outliers", + "pg_credentials", + "pg_kill", + "pg_maintenance", + "pg_backups", + "pg_upgrade" + ] + } + } + } + } +} diff --git a/registries/toolhive/servers/huggingface/server.json b/registries/toolhive/servers/huggingface/server.json new file mode 100644 index 00000000..5082e7c2 --- /dev/null +++ b/registries/toolhive/servers/huggingface/server.json @@ -0,0 +1,53 @@ +{ + "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json", + "name": "io.github.stacklok/huggingface", + "description": "Official Hugging Face MCP server for models, datasets, and research papers", + "title": "huggingface", + "version": "1.0.0", + "remotes": [ + { + "type": "streamable-http", + "url": "https://huggingface.co/mcp" + } + ], + "_meta": { + "io.modelcontextprotocol.registry/publisher-provided": { + "io.github.stacklok": { + "https://huggingface.co/mcp": { + "custom_metadata": { + "author": "Hugging Face", + "homepage": "https://huggingface.co/mcp" + }, + "metadata": { + "last_updated": "2026-02-05T04:49:20Z" + }, + "status": "Active", + "tags": [ + "remote", + "ai", + "huggingface", + "models", + "datasets", + "research-papers", + "documentation", + "image-generation", + "authentication" + ], + "tier": "Official", + "tools": [ + "hf_whoami", + "space_search", + "model_search", + "model_details", + "paper_search", + "dataset_search", + "dataset_details", + "hf_doc_search", + "hf_doc_fetch", + "gr1_flux1_schnell_infer" + ] + } + } + } + } +} diff --git a/registries/toolhive/servers/ida-pro-mcp/server.json b/registries/toolhive/servers/ida-pro-mcp/server.json new file mode 100644 index 00000000..bb5087fc --- /dev/null +++ b/registries/toolhive/servers/ida-pro-mcp/server.json @@ -0,0 +1,95 @@ +{ + "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json", + "name": "io.github.stacklok/ida-pro-mcp", + "description": "MCP server for IDA Pro reverse engineering and analysis", + "title": "ida-pro-mcp", + "repository": { + "url": "https://github.com/mrexodia/ida-pro-mcp", + "source": "github" + }, + "version": "1.0.0", + "packages": [ + { + "registryType": "oci", + "identifier": "ghcr.io/stacklok/dockyard/uvx/ida-pro-mcp:1.4.0", + "transport": { + "type": "stdio" + } + } + ], + "_meta": { + "io.modelcontextprotocol.registry/publisher-provided": { + "io.github.stacklok": { + "ghcr.io/stacklok/dockyard/uvx/ida-pro-mcp:1.4.0": { + "metadata": { + "last_updated": "2026-02-09T03:01:53Z", + "pulls": 99, + "stars": 5444 + }, + "permissions": { + "network": { + "outbound": { + "insecure_allow_all": true + } + } + }, + "provenance": { + "cert_issuer": "https://token.actions.githubusercontent.com", + "repository_uri": "https://github.com/stacklok/dockyard", + "runner_environment": "github-hosted", + "signer_identity": "/.github/workflows/build-containers.yml", + "sigstore_url": "tuf-repo-cdn.sigstore.dev" + }, + "status": "Active", + "tags": [ + "reverse-engineering", + "ida-pro", + "analysis", + "security", + "disassembly", + "decompilation" + ], + "tier": "Community", + "tools": [ + "check_connection", + "get_metadata", + "get_function_by_name", + "get_function_by_address", + "get_current_address", + "get_current_function", + "convert_number", + "list_functions", + "list_globals_filter", + "list_globals", + "list_strings_filter", + "list_strings", + "list_local_types", + "decompile_function", + "disassemble_function", + "get_xrefs_to", + "get_xrefs_to_field", + "get_entry_points", + "set_comment", + "rename_local_variable", + "rename_global_variable", + "set_global_variable_type", + "rename_function", + "set_function_prototype", + "declare_c_type", + "set_local_variable_type", + "dbg_get_registers", + "dbg_get_call_stack", + "dbg_list_breakpoints", + "dbg_start_process", + "dbg_exit_process", + "dbg_continue_process", + "dbg_run_to", + "dbg_set_breakpoint", + "dbg_delete_breakpoint", + "dbg_enable_breakpoint" + ] + } + } + } + } +} diff --git a/registries/toolhive/servers/jam/server.json b/registries/toolhive/servers/jam/server.json new file mode 100644 index 00000000..07a3b750 --- /dev/null +++ b/registries/toolhive/servers/jam/server.json @@ -0,0 +1,50 @@ +{ + "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json", + "name": "io.github.stacklok/jam", + "description": "Jam's official remote MCP server for debugging with video recordings and logs", + "title": "jam", + "version": "1.0.0", + "remotes": [ + { + "type": "streamable-http", + "url": "https://mcp.jam.dev/mcp" + } + ], + "_meta": { + "io.modelcontextprotocol.registry/publisher-provided": { + "io.github.stacklok": { + "https://mcp.jam.dev/mcp": { + "custom_metadata": { + "author": "Jam", + "homepage": "https://jam.dev/docs/debug-a-jam/mcp" + }, + "metadata": { + "last_updated": "2026-02-05T04:49:20Z" + }, + "status": "Active", + "tags": [ + "remote", + "jam", + "debugging", + "screen-recording", + "video-analysis", + "oauth", + "console-logs", + "network-requests", + "screenshots", + "user-events" + ], + "tier": "Official", + "tools": [ + "getDetails", + "getConsoleLogs", + "getNetworkRequests", + "getScreenshot", + "getUserEvents", + "analyzeVideo" + ] + } + } + } + } +} diff --git a/registries/toolhive/servers/k8s/server.json b/registries/toolhive/servers/k8s/server.json new file mode 100644 index 00000000..5c2147c9 --- /dev/null +++ b/registries/toolhive/servers/k8s/server.json @@ -0,0 +1,75 @@ +{ + "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json", + "name": "io.github.stacklok/k8s", + "description": "Allows LLM-powered applications to interact with Kubernetes clusters.", + "title": "k8s", + "repository": { + "url": "https://github.com/StacklokLabs/mkp", + "source": "github" + }, + "version": "1.0.0", + "packages": [ + { + "registryType": "oci", + "identifier": "ghcr.io/stackloklabs/mkp/server:0.2.4", + "transport": { + "type": "streamable-http", + "url": "http://localhost:8080" + }, + "environmentVariables": [ + { + "description": "Path to the kubeconfig file for Kubernetes API authentication (mounted into the container with --volume)", + "name": "KUBECONFIG" + } + ] + } + ], + "_meta": { + "io.modelcontextprotocol.registry/publisher-provided": { + "io.github.stacklok": { + "ghcr.io/stackloklabs/mkp/server:0.2.4": { + "metadata": { + "last_updated": "2026-02-05T04:47:24Z", + "pulls": 13952, + "stars": 56 + }, + "permissions": { + "network": { + "outbound": { + "allow_port": [ + 443 + ], + "insecure_allow_all": true + } + } + }, + "provenance": { + "cert_issuer": "https://token.actions.githubusercontent.com", + "repository_uri": "https://github.com/StacklokLabs/mkp", + "runner_environment": "github-hosted", + "signer_identity": "/.github/workflows/release.yml", + "sigstore_url": "tuf-repo-cdn.sigstore.dev" + }, + "status": "Active", + "tags": [ + "kubernetes", + "k8s", + "api", + "resources", + "cluster", + "namespaced", + "apply", + "get", + "list" + ], + "tier": "Community", + "tools": [ + "list_resources", + "get_resource", + "apply_resource" + ] + } + } + } + } +} diff --git a/registries/toolhive/servers/kion/server.json b/registries/toolhive/servers/kion/server.json new file mode 100644 index 00000000..30e0f78c --- /dev/null +++ b/registries/toolhive/servers/kion/server.json @@ -0,0 +1,105 @@ +{ + "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json", + "name": "io.github.stacklok/kion", + "description": "Integrate with a Kion.io instance for cloud management, FinOps, and governance", + "title": "kion", + "repository": { + "url": "https://github.com/kionsoftware/kion-mcp", + "source": "github" + }, + "version": "1.0.0", + "packages": [ + { + "registryType": "oci", + "identifier": "kionsoftware/kion-mcp:v0.3.0", + "transport": { + "type": "stdio" + }, + "environmentVariables": [ + { + "description": "App API key for authentication to your Kion instance", + "isRequired": true, + "isSecret": true, + "name": "KION_BEARER_TOKEN" + }, + { + "description": "URL of your Kion instance", + "isRequired": true, + "name": "KION_SERVER_URL" + } + ] + } + ], + "_meta": { + "io.modelcontextprotocol.registry/publisher-provided": { + "io.github.stacklok": { + "kionsoftware/kion-mcp:v0.3.0": { + "metadata": { + "last_updated": "2026-01-28T02:42:37Z", + "pulls": 651, + "stars": 7 + }, + "permissions": { + "network": { + "outbound": { + "insecure_allow_all": true + } + } + }, + "status": "Active", + "tags": [ + "kion", + "cloud-management", + "finops", + "cloudops", + "compliance", + "governance", + "cloud-costs" + ], + "tier": "Official", + "tools": [ + "add_project_spend_plan_entries", + "allocate_funds", + "create_budget", + "create_funding_source", + "create_ou", + "create_project_with_budget", + "create_project_with_spend_plan", + "get_accounts", + "get_cloud_access_role_details", + "get_cloud_access_roles_on_entity", + "get_cloud_provider_services", + "get_cloud_providers", + "get_compliance_check", + "get_compliance_checks_paginated", + "get_compliance_findings", + "get_compliance_ous", + "get_compliance_program", + "get_compliance_standard", + "get_compliance_standard_project", + "get_compliance_standards_paginated", + "get_entity_by_id", + "get_label_key_id", + "get_labels", + "get_ou_budget", + "get_ou_funding_sources", + "get_ous", + "get_permission_scheme", + "get_project_budget", + "get_project_spend_plan_with_totals", + "get_projects", + "get_spend_report", + "get_suppressed_compliance_findings", + "get_tag_keys", + "get_tag_values", + "get_user_cloud_access_roles", + "get_user_groups", + "get_user_info", + "get_users", + "update_budget" + ] + } + } + } + } +} diff --git a/registries/toolhive/servers/kiwi/server.json b/registries/toolhive/servers/kiwi/server.json new file mode 100644 index 00000000..48a12a9a --- /dev/null +++ b/registries/toolhive/servers/kiwi/server.json @@ -0,0 +1,46 @@ +{ + "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json", + "name": "io.github.stacklok/kiwi", + "description": "Kiwi.com Flight Search MCP server to search and book flights", + "title": "kiwi", + "repository": { + "url": "https://github.com/alpic-ai/kiwi-mcp-server-public", + "source": "github" + }, + "version": "1.0.0", + "remotes": [ + { + "type": "streamable-http", + "url": "https://mcp.kiwi.com" + } + ], + "_meta": { + "io.modelcontextprotocol.registry/publisher-provided": { + "io.github.stacklok": { + "https://mcp.kiwi.com": { + "custom_metadata": { + "author": "Kiwi.com", + "homepage": "https://kiwi.com" + }, + "metadata": { + "last_updated": "2026-02-07T02:54:31Z", + "stars": 8 + }, + "status": "Active", + "tags": [ + "remote", + "travel", + "flights", + "booking", + "search", + "kiwi" + ], + "tier": "Official", + "tools": [ + "search-flight" + ] + } + } + } + } +} diff --git a/registries/toolhive/servers/kyverno/server.json b/registries/toolhive/servers/kyverno/server.json new file mode 100644 index 00000000..d6316cc1 --- /dev/null +++ b/registries/toolhive/servers/kyverno/server.json @@ -0,0 +1,77 @@ +{ + "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json", + "name": "io.github.stacklok/kyverno", + "description": "Kyverno policy management for Kubernetes security assessment and compliance monitoring", + "title": "kyverno", + "repository": { + "url": "https://github.com/nirmata/kyverno-mcp", + "source": "github" + }, + "version": "1.0.0", + "packages": [ + { + "registryType": "oci", + "identifier": "ghcr.io/nirmata/kyverno-mcp:v0.2.2", + "transport": { + "type": "stdio" + }, + "environmentVariables": [ + { + "description": "Path to the kubeconfig file for Kubernetes API authentication (mounted into the container with --volume)", + "name": "KUBECONFIG" + } + ] + } + ], + "_meta": { + "io.modelcontextprotocol.registry/publisher-provided": { + "io.github.stacklok": { + "ghcr.io/nirmata/kyverno-mcp:v0.2.2": { + "metadata": { + "last_updated": "2026-01-27T02:45:00Z", + "pulls": 4946, + "stars": 15 + }, + "permissions": { + "network": { + "outbound": { + "allow_port": [ + 443 + ], + "insecure_allow_all": true + } + } + }, + "status": "Active", + "tags": [ + "kyverno", + "kubernetes", + "policy-management", + "security", + "compliance", + "governance", + "policy-as-code", + "nirmata", + "admission-control", + "validation", + "mutation", + "generation", + "policy-violations", + "security-assessment", + "rbac", + "pod-security", + "best-practices" + ], + "tier": "Official", + "tools": [ + "list_contexts", + "switch_context", + "apply_policies", + "show_violations", + "help" + ] + } + } + } + } +} diff --git a/registries/toolhive/servers/launchdarkly/server.json b/registries/toolhive/servers/launchdarkly/server.json new file mode 100644 index 00000000..f57f9541 --- /dev/null +++ b/registries/toolhive/servers/launchdarkly/server.json @@ -0,0 +1,91 @@ +{ + "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json", + "name": "io.github.stacklok/launchdarkly", + "description": "Official MCP server for LaunchDarkly feature flag management and AI Config orchestration", + "title": "launchdarkly", + "repository": { + "url": "https://github.com/launchdarkly/mcp-server", + "source": "github" + }, + "version": "1.0.0", + "packages": [ + { + "registryType": "oci", + "identifier": "ghcr.io/stacklok/dockyard/npx/launchdarkly-mcp-server:0.4.2", + "transport": { + "type": "stdio" + }, + "environmentVariables": [ + { + "description": "LaunchDarkly API access token with appropriate permissions (Writer or Developer role recommended)", + "isRequired": true, + "isSecret": true, + "name": "LAUNCHDARKLY_API_KEY" + } + ] + } + ], + "_meta": { + "io.modelcontextprotocol.registry/publisher-provided": { + "io.github.stacklok": { + "ghcr.io/stacklok/dockyard/npx/launchdarkly-mcp-server:0.4.2": { + "metadata": { + "last_updated": "2026-02-07T02:54:31Z", + "stars": 18 + }, + "permissions": { + "network": { + "outbound": { + "allow_host": [ + "app.launchdarkly.com", + "app.launchdarkly.us", + "app.eu.launchdarkly.com" + ], + "allow_port": [ + 443 + ] + } + } + }, + "provenance": { + "cert_issuer": "https://token.actions.githubusercontent.com", + "repository_uri": "https://github.com/stacklok/dockyard", + "runner_environment": "github-hosted", + "signer_identity": "/.github/workflows/build-containers.yml", + "sigstore_url": "tuf-repo-cdn.sigstore.dev" + }, + "status": "Active", + "tags": [ + "feature-flags", + "ai-config", + "configuration", + "devops", + "launchdarkly" + ], + "tier": "Official", + "tools": [ + "create-ai-config", + "create-ai-config-variation", + "delete-ai-config", + "delete-ai-config-variation", + "get-ai-config", + "get-ai-config-targeting", + "get-ai-config-variation", + "list-ai-configs", + "update-ai-config", + "update-ai-config-targeting", + "update-ai-config-variation", + "get-code-references", + "get-environments", + "create-feature-flag", + "delete-feature-flag", + "get-feature-flag", + "get-flag-status-across-environments", + "list-feature-flags", + "update-feature-flag" + ] + } + } + } + } +} diff --git a/registries/toolhive/servers/linear/server.json b/registries/toolhive/servers/linear/server.json new file mode 100644 index 00000000..103e3318 --- /dev/null +++ b/registries/toolhive/servers/linear/server.json @@ -0,0 +1,66 @@ +{ + "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json", + "name": "io.github.stacklok/linear", + "description": "Linear's official remote MCP server to manage issues, projects, and comments", + "title": "linear", + "version": "1.0.0", + "remotes": [ + { + "type": "streamable-http", + "url": "https://mcp.linear.app/mcp" + } + ], + "_meta": { + "io.modelcontextprotocol.registry/publisher-provided": { + "io.github.stacklok": { + "https://mcp.linear.app/mcp": { + "custom_metadata": { + "author": "Linear", + "homepage": "https://linear.app/docs/mcp" + }, + "metadata": { + "last_updated": "2026-02-05T04:49:20Z" + }, + "status": "Active", + "tags": [ + "issue-tracking", + "project-management", + "linear", + "remote", + "task-management", + "productivity", + "software-development", + "bug-tracking", + "team-collaboration" + ], + "tier": "Official", + "tools": [ + "list_comments", + "create_comment", + "list_cycles", + "get_document", + "list_documents", + "get_issue", + "list_issues", + "create_issue", + "update_issue", + "list_issue_statuses", + "get_issue_status", + "list_issue_labels", + "create_issue_label", + "list_projects", + "get_project", + "create_project", + "update_project", + "list_project_labels", + "list_teams", + "get_team", + "list_users", + "get_user", + "search_documentation" + ] + } + } + } + } +} diff --git a/registries/toolhive/servers/magic-mcp/server.json b/registries/toolhive/servers/magic-mcp/server.json new file mode 100644 index 00000000..e1944172 --- /dev/null +++ b/registries/toolhive/servers/magic-mcp/server.json @@ -0,0 +1,78 @@ +{ + "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json", + "name": "io.github.stacklok/magic-mcp", + "description": "AI-powered UI component generator MCP server by 21st.dev", + "title": "magic-mcp", + "repository": { + "url": "https://github.com/21st-dev/magic-mcp", + "source": "github" + }, + "version": "1.0.0", + "packages": [ + { + "registryType": "oci", + "identifier": "ghcr.io/stacklok/dockyard/npx/magic-mcp:0.1.0", + "transport": { + "type": "stdio" + }, + "environmentVariables": [ + { + "description": "21st.dev Magic API key for component generation", + "isRequired": true, + "isSecret": true, + "name": "API_KEY" + } + ] + } + ], + "_meta": { + "io.modelcontextprotocol.registry/publisher-provided": { + "io.github.stacklok": { + "ghcr.io/stacklok/dockyard/npx/magic-mcp:0.1.0": { + "metadata": { + "last_updated": "2026-02-13T03:02:08Z", + "pulls": 128, + "stars": 4262 + }, + "permissions": { + "network": { + "outbound": { + "allow_host": [ + "21st.dev", + "api.21st.dev" + ], + "allow_port": [ + 443, + 80 + ] + } + } + }, + "provenance": { + "cert_issuer": "https://token.actions.githubusercontent.com", + "repository_uri": "https://github.com/stacklok/dockyard", + "runner_environment": "github-hosted", + "signer_identity": "/.github/workflows/build-containers.yml", + "sigstore_url": "tuf-repo-cdn.sigstore.dev" + }, + "status": "Active", + "tags": [ + "ui", + "frontend", + "components", + "ai", + "generator", + "react" + ], + "tier": "Community", + "tools": [ + "create_ui", + "fetch_ui", + "logo_search", + "refine_ui" + ] + } + } + } + } +} diff --git a/registries/toolhive/servers/mcp-clickhouse/server.json b/registries/toolhive/servers/mcp-clickhouse/server.json new file mode 100644 index 00000000..50a2fde9 --- /dev/null +++ b/registries/toolhive/servers/mcp-clickhouse/server.json @@ -0,0 +1,108 @@ +{ + "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json", + "name": "io.github.stacklok/mcp-clickhouse", + "description": "MCP server for ClickHouse with SQL queries, database/table listing, and optional chDB OLAP engine", + "title": "mcp-clickhouse", + "repository": { + "url": "https://github.com/ClickHouse/mcp-clickhouse", + "source": "github" + }, + "version": "1.0.0", + "packages": [ + { + "registryType": "oci", + "identifier": "ghcr.io/stacklok/dockyard/uvx/mcp-clickhouse:0.2.0", + "transport": { + "type": "stdio" + }, + "environmentVariables": [ + { + "description": "The hostname of your ClickHouse server", + "isRequired": true, + "name": "CLICKHOUSE_HOST" + }, + { + "description": "The username for authentication", + "isRequired": true, + "name": "CLICKHOUSE_USER" + }, + { + "description": "The password for authentication", + "isRequired": true, + "isSecret": true, + "name": "CLICKHOUSE_PASSWORD" + }, + { + "description": "The port number of your ClickHouse server", + "default": "8443", + "name": "CLICKHOUSE_PORT" + }, + { + "description": "Enable/disable HTTPS connection", + "default": "true", + "name": "CLICKHOUSE_SECURE" + }, + { + "description": "Enable/disable SSL certificate verification", + "default": "true", + "name": "CLICKHOUSE_VERIFY" + }, + { + "description": "Default database to use", + "name": "CLICKHOUSE_DATABASE" + }, + { + "description": "Enable/disable chDB functionality", + "default": "false", + "name": "CHDB_ENABLED" + }, + { + "description": "The path to the chDB data directory", + "default": ":memory:", + "name": "CHDB_DATA_PATH" + } + ] + } + ], + "_meta": { + "io.modelcontextprotocol.registry/publisher-provided": { + "io.github.stacklok": { + "ghcr.io/stacklok/dockyard/uvx/mcp-clickhouse:0.2.0": { + "metadata": { + "last_updated": "2026-02-13T03:02:09Z", + "pulls": 81, + "stars": 688 + }, + "permissions": { + "network": { + "outbound": { + "insecure_allow_all": true + } + } + }, + "provenance": { + "cert_issuer": "https://token.actions.githubusercontent.com", + "repository_uri": "https://github.com/stacklok/dockyard", + "runner_environment": "github-hosted", + "signer_identity": "/.github/workflows/build-containers.yml", + "sigstore_url": "tuf-repo-cdn.sigstore.dev" + }, + "status": "Active", + "tags": [ + "database", + "clickhouse", + "sql", + "analytics", + "olap" + ], + "tier": "Official", + "tools": [ + "list_databases", + "list_tables", + "run_select_query" + ] + } + } + } + } +} diff --git a/registries/toolhive/servers/mcp-jetbrains/server.json b/registries/toolhive/servers/mcp-jetbrains/server.json new file mode 100644 index 00000000..4c6e323b --- /dev/null +++ b/registries/toolhive/servers/mcp-jetbrains/server.json @@ -0,0 +1,75 @@ +{ + "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json", + "name": "io.github.stacklok/mcp-jetbrains", + "description": "A MCP proxy to redirect requests to JetBrains IDEs", + "title": "mcp-jetbrains", + "repository": { + "url": "https://github.com/JetBrains/mcp-jetbrains", + "source": "github" + }, + "version": "1.0.0", + "packages": [ + { + "registryType": "oci", + "identifier": "ghcr.io/stacklok/dockyard/npx/mcp-jetbrains:1.8.0", + "transport": { + "type": "stdio" + }, + "environmentVariables": [ + { + "description": "Port of IDE's built-in webserver (if running multiple IDEs)", + "name": "IDE_PORT" + }, + { + "description": "Host/address of IDE's built-in webserver", + "default": "127.0.0.1", + "name": "HOST" + }, + { + "description": "Enable logging for debugging", + "default": "false", + "name": "LOG_ENABLED" + } + ] + } + ], + "_meta": { + "io.modelcontextprotocol.registry/publisher-provided": { + "io.github.stacklok": { + "ghcr.io/stacklok/dockyard/npx/mcp-jetbrains:1.8.0": { + "metadata": { + "last_updated": "2026-02-13T03:02:09Z", + "pulls": 77, + "stars": 944 + }, + "permissions": { + "network": { + "outbound": { + "insecure_allow_all": true + } + } + }, + "provenance": { + "cert_issuer": "https://token.actions.githubusercontent.com", + "repository_uri": "https://github.com/stacklok/dockyard", + "runner_environment": "github-hosted", + "signer_identity": "/.github/workflows/build-containers.yml", + "sigstore_url": "tuf-repo-cdn.sigstore.dev" + }, + "status": "Active", + "tags": [ + "ide", + "jetbrains", + "proxy", + "development", + "intellij" + ], + "tier": "Official", + "tools": [ + "dynamic_tools_from_ide" + ] + } + } + } + } +} diff --git a/registries/toolhive/servers/mcp-neo4j-aura-manager/server.json b/registries/toolhive/servers/mcp-neo4j-aura-manager/server.json new file mode 100644 index 00000000..20f0342e --- /dev/null +++ b/registries/toolhive/servers/mcp-neo4j-aura-manager/server.json @@ -0,0 +1,92 @@ +{ + "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json", + "name": "io.github.stacklok/mcp-neo4j-aura-manager", + "description": "MCP server for managing Neo4j Aura cloud instances and services", + "title": "mcp-neo4j-aura-manager", + "repository": { + "url": "https://github.com/neo4j-contrib/mcp-neo4j", + "source": "github" + }, + "version": "1.0.0", + "packages": [ + { + "registryType": "oci", + "identifier": "ghcr.io/stacklok/dockyard/uvx/mcp-neo4j-aura-manager:0.4.7", + "transport": { + "type": "stdio" + }, + "environmentVariables": [ + { + "description": "Neo4j Aura API client ID", + "isRequired": true, + "isSecret": true, + "name": "AURA_CLIENT_ID" + }, + { + "description": "Neo4j Aura API client secret", + "isRequired": true, + "isSecret": true, + "name": "AURA_CLIENT_SECRET" + }, + { + "description": "Neo4j Aura tenant ID", + "isSecret": true, + "name": "AURA_TENANT_ID" + } + ] + } + ], + "_meta": { + "io.modelcontextprotocol.registry/publisher-provided": { + "io.github.stacklok": { + "ghcr.io/stacklok/dockyard/uvx/mcp-neo4j-aura-manager:0.4.7": { + "metadata": { + "last_updated": "2026-02-13T03:02:10Z", + "pulls": 141, + "stars": 898 + }, + "permissions": { + "network": { + "outbound": { + "allow_host": [ + "api.neo4j.io", + "console.neo4j.io", + ".neo4j.io" + ], + "allow_port": [ + 443, + 80 + ] + } + } + }, + "provenance": { + "cert_issuer": "https://token.actions.githubusercontent.com", + "repository_uri": "https://github.com/stacklok/dockyard", + "runner_environment": "github-hosted", + "signer_identity": "/.github/workflows/build-containers.yml", + "sigstore_url": "tuf-repo-cdn.sigstore.dev" + }, + "status": "Active", + "tags": [ + "database", + "neo4j", + "aura", + "cloud", + "management" + ], + "tier": "Community", + "tools": [ + "list_instances", + "create_instance", + "delete_instance", + "get_instance", + "update_instance", + "scale_instance", + "enable_features" + ] + } + } + } + } +} diff --git a/registries/toolhive/servers/mcp-neo4j-cypher/server.json b/registries/toolhive/servers/mcp-neo4j-cypher/server.json new file mode 100644 index 00000000..e86ae6a1 --- /dev/null +++ b/registries/toolhive/servers/mcp-neo4j-cypher/server.json @@ -0,0 +1,94 @@ +{ + "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json", + "name": "io.github.stacklok/mcp-neo4j-cypher", + "description": "MCP server for executing Cypher queries against Neo4j databases with natural language interface", + "title": "mcp-neo4j-cypher", + "repository": { + "url": "https://github.com/neo4j-contrib/mcp-neo4j", + "source": "github" + }, + "version": "1.0.0", + "packages": [ + { + "registryType": "oci", + "identifier": "ghcr.io/stacklok/dockyard/uvx/mcp-neo4j-cypher:0.5.2", + "transport": { + "type": "stdio" + }, + "environmentVariables": [ + { + "description": "Neo4j database connection URI", + "isRequired": true, + "default": "bolt://localhost:7687", + "name": "NEO4J_URI" + }, + { + "description": "Neo4j database username", + "default": "neo4j", + "name": "NEO4J_USERNAME" + }, + { + "description": "Neo4j database password", + "isSecret": true, + "name": "NEO4J_PASSWORD" + }, + { + "description": "Neo4j database name", + "default": "neo4j", + "name": "NEO4J_DATABASE" + } + ] + } + ], + "_meta": { + "io.modelcontextprotocol.registry/publisher-provided": { + "io.github.stacklok": { + "ghcr.io/stacklok/dockyard/uvx/mcp-neo4j-cypher:0.5.2": { + "metadata": { + "last_updated": "2026-02-14T02:56:21Z", + "pulls": 91, + "stars": 898 + }, + "permissions": { + "network": { + "outbound": { + "allow_host": [ + "localhost", + ".neo4j.io", + ".databases.neo4j.io" + ], + "allow_port": [ + 7687, + 7473, + 7474, + 443 + ] + } + } + }, + "provenance": { + "cert_issuer": "https://token.actions.githubusercontent.com", + "repository_uri": "https://github.com/stacklok/dockyard", + "runner_environment": "github-hosted", + "signer_identity": "/.github/workflows/build-containers.yml", + "sigstore_url": "tuf-repo-cdn.sigstore.dev" + }, + "status": "Active", + "tags": [ + "database", + "neo4j", + "cypher", + "query", + "graph-database" + ], + "tier": "Community", + "tools": [ + "get_neo4j_schema", + "read_neo4j_cypher", + "write_neo4j_cypher" + ] + } + } + } + } +} diff --git a/registries/toolhive/servers/mcp-neo4j-memory/server.json b/registries/toolhive/servers/mcp-neo4j-memory/server.json new file mode 100644 index 00000000..eeb29c44 --- /dev/null +++ b/registries/toolhive/servers/mcp-neo4j-memory/server.json @@ -0,0 +1,91 @@ +{ + "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json", + "name": "io.github.stacklok/mcp-neo4j-memory", + "description": "MCP server for Neo4j memory management and knowledge graph storage operations", + "title": "mcp-neo4j-memory", + "repository": { + "url": "https://github.com/neo4j-contrib/mcp-neo4j", + "source": "github" + }, + "version": "1.0.0", + "packages": [ + { + "registryType": "oci", + "identifier": "ghcr.io/stacklok/dockyard/uvx/mcp-neo4j-memory:0.4.4", + "transport": { + "type": "stdio" + }, + "environmentVariables": [ + { + "description": "Neo4j database connection URI", + "isRequired": true, + "default": "bolt://localhost:7687", + "name": "NEO4J_URI" + }, + { + "description": "Neo4j database username", + "default": "neo4j", + "name": "NEO4J_USERNAME" + }, + { + "description": "Neo4j database password", + "isSecret": true, + "name": "NEO4J_PASSWORD" + } + ] + } + ], + "_meta": { + "io.modelcontextprotocol.registry/publisher-provided": { + "io.github.stacklok": { + "ghcr.io/stacklok/dockyard/uvx/mcp-neo4j-memory:0.4.4": { + "metadata": { + "last_updated": "2026-02-16T03:01:20Z", + "pulls": 105, + "stars": 899 + }, + "permissions": { + "network": { + "outbound": { + "allow_host": [ + "localhost", + ".neo4j.io", + ".databases.neo4j.io" + ], + "allow_port": [ + 7687, + 7473, + 7474, + 443 + ] + } + } + }, + "provenance": { + "cert_issuer": "https://token.actions.githubusercontent.com", + "repository_uri": "https://github.com/stacklok/dockyard", + "runner_environment": "github-hosted", + "signer_identity": "/.github/workflows/build-containers.yml", + "sigstore_url": "tuf-repo-cdn.sigstore.dev" + }, + "status": "Active", + "tags": [ + "database", + "neo4j", + "memory", + "knowledge-graph", + "graph-database" + ], + "tier": "Community", + "tools": [ + "store_memory", + "retrieve_memory", + "search_memories", + "delete_memory", + "list_memories" + ] + } + } + } + } +} diff --git a/registries/toolhive/servers/mcp-optimizer/server.json b/registries/toolhive/servers/mcp-optimizer/server.json new file mode 100644 index 00000000..67bc12c6 --- /dev/null +++ b/registries/toolhive/servers/mcp-optimizer/server.json @@ -0,0 +1,88 @@ +{ + "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json", + "name": "io.github.stacklok/mcp-optimizer", + "description": "MCP server that acts as an intelligent intermediary between AI clients and multiple MCP servers", + "title": "mcp-optimizer", + "repository": { + "url": "https://github.com/StacklokLabs/mcp-optimizer", + "source": "github" + }, + "version": "1.0.0", + "packages": [ + { + "registryType": "oci", + "identifier": "ghcr.io/stackloklabs/mcp-optimizer:0.2.5", + "transport": { + "type": "streamable-http", + "url": "http://localhost:9900" + }, + "environmentVariables": [ + { + "description": "Comma-separated list of ToolHive group names to filter tool lookups (default: no filtering)", + "name": "ALLOWED_GROUPS" + }, + { + "description": "Number of tools to return from find_tool (default: 8)", + "name": "MAX_TOOLS_TO_RETURN" + }, + { + "description": "Distance threshold for tool similarity (default: 1.0)", + "name": "TOOL_DISTANCE_THRESHOLD" + }, + { + "description": "Maximum number of tokens to return from call_tool (default: no limit)", + "name": "MAX_TOOL_RESPONSE_TOKENS" + }, + { + "description": "Polling interval for running MCP servers (default: 60 seconds)", + "name": "WORKLOAD_POLLING_INTERVAL" + }, + { + "description": "Polling interval for ToolHive registry (default: 24 hours)", + "name": "REGISTRY_POLLING_INTERVAL" + }, + { + "description": "Runtime mode for MCP servers (docker or k8s, default: docker)", + "name": "RUNTIME_MODE" + } + ] + } + ], + "_meta": { + "io.modelcontextprotocol.registry/publisher-provided": { + "io.github.stacklok": { + "ghcr.io/stackloklabs/mcp-optimizer:0.2.5": { + "metadata": { + "last_updated": "2026-01-25T13:39:50Z", + "stars": 9 + }, + "permissions": { + "network": { + "outbound": {} + } + }, + "provenance": { + "cert_issuer": "https://token.actions.githubusercontent.com", + "repository_uri": "https://github.com/StacklokLabs/mcp-optimizer", + "runner_environment": "github-hosted", + "signer_identity": "/.github/workflows/release.yml", + "sigstore_url": "tuf-repo-cdn.sigstore.dev" + }, + "status": "Active", + "tags": [ + "mcp", + "proxy", + "gateway", + "intelligent" + ], + "tier": "Official", + "tools": [ + "call_tool", + "find_tool", + "list_tools" + ] + } + } + } + } +} diff --git a/registries/toolhive/servers/mcp-redfish/server.json b/registries/toolhive/servers/mcp-redfish/server.json new file mode 100644 index 00000000..4a1e236f --- /dev/null +++ b/registries/toolhive/servers/mcp-redfish/server.json @@ -0,0 +1,87 @@ +{ + "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json", + "name": "io.github.stacklok/mcp-redfish", + "description": "Natural language interface for managing infrastructure via Redfish API", + "title": "mcp-redfish", + "repository": { + "url": "https://github.com/nokia/mcp-redfish", + "source": "github" + }, + "version": "1.0.0", + "packages": [ + { + "registryType": "oci", + "identifier": "ghcr.io/nokia/mcp-redfish:0.3.4", + "transport": { + "type": "stdio" + }, + "environmentVariables": [ + { + "description": "JSON array of Redfish endpoint configurations with address, port, username, password, auth_method, and tls_server_ca_cert", + "isRequired": true, + "default": "[{\"address\":\"127.0.0.1\"}]", + "name": "REDFISH_HOSTS" + }, + { + "description": "Default port for Redfish API endpoints", + "default": "443", + "name": "REDFISH_PORT" + }, + { + "description": "Authentication method (basic or session)", + "default": "session", + "name": "REDFISH_AUTH_METHOD" + }, + { + "description": "Default username for Redfish authentication", + "isSecret": true, + "name": "REDFISH_USERNAME" + }, + { + "description": "Default password for Redfish authentication", + "isSecret": true, + "name": "REDFISH_PASSWORD" + }, + { + "description": "Path to CA certificate for TLS verification", + "name": "REDFISH_SERVER_CA_CERT" + }, + { + "description": "MCP transport method (stdio, sse, or streamable-http)", + "default": "stdio", + "name": "MCP_TRANSPORT" + }, + { + "description": "Logging verbosity level", + "default": "INFO", + "name": "MCP_REDFISH_LOG_LEVEL" + } + ] + } + ], + "_meta": { + "io.modelcontextprotocol.registry/publisher-provided": { + "io.github.stacklok": { + "ghcr.io/nokia/mcp-redfish:0.3.4": { + "metadata": { + "last_updated": "2026-01-30T02:55:47Z", + "stars": 4 + }, + "status": "Active", + "tags": [ + "infrastructure", + "redfish", + "api", + "hardware", + "server-management" + ], + "tier": "Community", + "tools": [ + "get_resource_data", + "list_servers" + ] + } + } + } + } +} diff --git a/registries/toolhive/servers/mcp-server-box/server.json b/registries/toolhive/servers/mcp-server-box/server.json new file mode 100644 index 00000000..22377812 --- /dev/null +++ b/registries/toolhive/servers/mcp-server-box/server.json @@ -0,0 +1,106 @@ +{ + "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json", + "name": "io.github.stacklok/mcp-server-box", + "description": "Box API integration for file operations, AI querying, metadata management, and document generation", + "title": "mcp-server-box", + "repository": { + "url": "https://github.com/box-community/mcp-server-box", + "source": "github" + }, + "version": "1.0.0", + "packages": [ + { + "registryType": "oci", + "identifier": "ghcr.io/stacklok/dockyard/uvx/mcp-server-box:0.1.2", + "transport": { + "type": "stdio" + }, + "environmentVariables": [ + { + "description": "Box API Client ID", + "isRequired": true, + "name": "BOX_CLIENT_ID" + }, + { + "description": "Box API Client Secret", + "isRequired": true, + "isSecret": true, + "name": "BOX_CLIENT_SECRET" + } + ] + } + ], + "_meta": { + "io.modelcontextprotocol.registry/publisher-provided": { + "io.github.stacklok": { + "ghcr.io/stacklok/dockyard/uvx/mcp-server-box:0.1.2": { + "metadata": { + "last_updated": "2026-01-25T13:39:50Z", + "pulls": 52, + "stars": 93 + }, + "permissions": { + "network": { + "outbound": { + "insecure_allow_all": true + } + } + }, + "provenance": { + "cert_issuer": "https://token.actions.githubusercontent.com", + "repository_uri": "https://github.com/stacklok/dockyard", + "runner_environment": "github-hosted", + "signer_identity": "/.github/workflows/build-containers.yml", + "sigstore_url": "tuf-repo-cdn.sigstore.dev" + }, + "status": "Active", + "tags": [ + "storage", + "box", + "files", + "ai", + "document-generation" + ], + "tier": "Official", + "tools": [ + "box_who_am_i", + "box_authorize_app_tool", + "box_search_tool", + "box_search_folder_by_name_tool", + "box_ai_ask_file_single_tool", + "box_ai_ask_file_multi_tool", + "box_ai_ask_hub_tool", + "box_ai_extract_freeform_tool", + "box_ai_extract_structured_using_fields_tool", + "box_ai_extract_structured_using_template_tool", + "box_ai_extract_structured_enhanced_using_fields_tool", + "box_ai_extract_structured_enhanced_using_template_tool", + "box_docgen_create_batch_tool", + "box_docgen_get_job_by_id_tool", + "box_docgen_list_jobs_tool", + "box_docgen_list_jobs_by_batch_tool", + "box_docgen_template_create_tool", + "box_docgen_template_list_tool", + "box_docgen_template_get_by_id_tool", + "box_docgen_template_list_tags_tool", + "box_docgen_template_list_jobs_tool", + "box_docgen_template_get_by_name_tool", + "box_docgen_create_single_file_from_user_input_tool", + "box_read_tool", + "box_upload_file_from_path_tool", + "box_upload_file_from_content_tool", + "box_download_file_tool", + "box_list_folder_content_by_folder_id", + "box_manage_folder_tool", + "box_metadata_template_get_by_name_tool", + "box_metadata_set_instance_on_file_tool", + "box_metadata_get_instance_on_file_tool", + "box_metadata_delete_instance_on_file_tool", + "box_metadata_update_instance_on_file_tool", + "box_metadata_template_create_tool" + ] + } + } + } + } +} diff --git a/registries/toolhive/servers/mcp-server-circleci/server.json b/registries/toolhive/servers/mcp-server-circleci/server.json new file mode 100644 index 00000000..7e8ef73c --- /dev/null +++ b/registries/toolhive/servers/mcp-server-circleci/server.json @@ -0,0 +1,100 @@ +{ + "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json", + "name": "io.github.stacklok/mcp-server-circleci", + "description": "CircleCI MCP server for CI/CD operations, pipeline management, and build analysis", + "title": "mcp-server-circleci", + "repository": { + "url": "https://github.com/CircleCI-Public/mcp-server-circleci", + "source": "github" + }, + "version": "1.0.0", + "packages": [ + { + "registryType": "oci", + "identifier": "ghcr.io/stacklok/dockyard/npx/mcp-server-circleci:0.14.1", + "transport": { + "type": "stdio" + }, + "environmentVariables": [ + { + "description": "CircleCI Personal API Token for authentication", + "isRequired": true, + "isSecret": true, + "name": "CIRCLECI_TOKEN" + }, + { + "description": "CircleCI base URL (optional, defaults to https://circleci.com)", + "default": "https://circleci.com", + "name": "CIRCLECI_BASE_URL" + }, + { + "description": "Directory for file outputs (optional, used by some tools)", + "name": "FILE_OUTPUT_DIRECTORY" + } + ] + } + ], + "_meta": { + "io.modelcontextprotocol.registry/publisher-provided": { + "io.github.stacklok": { + "ghcr.io/stacklok/dockyard/npx/mcp-server-circleci:0.14.1": { + "metadata": { + "last_updated": "2026-02-05T04:49:16Z", + "stars": 76 + }, + "permissions": { + "network": { + "outbound": { + "allow_host": [ + "circleci.com", + "app.circleci.com" + ], + "allow_port": [ + 443, + 80 + ] + } + } + }, + "provenance": { + "cert_issuer": "https://token.actions.githubusercontent.com", + "repository_uri": "https://github.com/stacklok/dockyard", + "runner_environment": "github-hosted", + "signer_identity": "/.github/workflows/build-containers.yml", + "sigstore_url": "tuf-repo-cdn.sigstore.dev" + }, + "status": "Active", + "tags": [ + "circleci", + "ci-cd", + "devops", + "testing", + "automation", + "pipeline", + "continuous-integration", + "continuous-deployment", + "build-automation" + ], + "tier": "Official", + "tools": [ + "get_build_failure_logs", + "find_flaky_tests", + "get_latest_pipeline_status", + "get_job_test_results", + "config_helper", + "create_prompt_template", + "recommend_prompt_template_tests", + "list_followed_projects", + "run_pipeline", + "run_rollback_pipeline", + "rerun_workflow", + "analyze_diff", + "list_component_versions", + "download_usage_api_data", + "find_underused_resource_classes" + ] + } + } + } + } +} diff --git a/registries/toolhive/servers/mcp-server-neon/server.json b/registries/toolhive/servers/mcp-server-neon/server.json new file mode 100644 index 00000000..46df9b2f --- /dev/null +++ b/registries/toolhive/servers/mcp-server-neon/server.json @@ -0,0 +1,97 @@ +{ + "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json", + "name": "io.github.stacklok/mcp-server-neon", + "description": "MCP server for interacting with Neon Management API and databases", + "title": "mcp-server-neon", + "repository": { + "url": "https://github.com/neondatabase-labs/mcp-server-neon", + "source": "github" + }, + "version": "1.0.0", + "packages": [ + { + "registryType": "oci", + "identifier": "ghcr.io/stacklok/dockyard/npx/mcp-server-neon:0.6.5", + "transport": { + "type": "stdio" + }, + "environmentVariables": [ + { + "description": "API key for Neon database service", + "isRequired": true, + "isSecret": true, + "name": "NEON_API_KEY" + } + ] + } + ], + "_meta": { + "io.modelcontextprotocol.registry/publisher-provided": { + "io.github.stacklok": { + "ghcr.io/stacklok/dockyard/npx/mcp-server-neon:0.6.5": { + "metadata": { + "last_updated": "2026-01-25T13:39:51Z", + "pulls": 55, + "stars": 540 + }, + "permissions": { + "network": { + "outbound": { + "allow_host": [ + "console.neon.tech", + "api.neon.tech", + "neon.tech" + ], + "allow_port": [ + 443, + 5432 + ] + } + } + }, + "provenance": { + "cert_issuer": "https://token.actions.githubusercontent.com", + "repository_uri": "https://github.com/stacklok/dockyard", + "runner_environment": "github-hosted", + "signer_identity": "/.github/workflows/build-containers.yml", + "sigstore_url": "tuf-repo-cdn.sigstore.dev" + }, + "status": "Active", + "tags": [ + "database", + "postgresql", + "api", + "management", + "sql", + "migration", + "branching" + ], + "tier": "Official", + "tools": [ + "list_projects", + "describe_project", + "create_project", + "delete_project", + "create_branch", + "delete_branch", + "describe_branch", + "list_branch_computes", + "list_organizations", + "get_connection_string", + "run_sql", + "run_sql_transaction", + "get_database_tables", + "describe_table_schema", + "list_slow_queries", + "prepare_database_migration", + "complete_database_migration", + "explain_sql_statement", + "prepare_query_tuning", + "complete_query_tuning", + "provision_neon_auth" + ] + } + } + } + } +} diff --git a/registries/toolhive/servers/mcp-spec/server.json b/registries/toolhive/servers/mcp-spec/server.json new file mode 100644 index 00000000..33240025 --- /dev/null +++ b/registries/toolhive/servers/mcp-spec/server.json @@ -0,0 +1,41 @@ +{ + "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json", + "name": "io.github.stacklok/mcp-spec", + "description": "Official MCP server for the Model Context Protocol specification", + "title": "mcp-spec", + "version": "1.0.0", + "remotes": [ + { + "type": "streamable-http", + "url": "https://modelcontextprotocol.io/mcp" + } + ], + "_meta": { + "io.modelcontextprotocol.registry/publisher-provided": { + "io.github.stacklok": { + "https://modelcontextprotocol.io/mcp": { + "custom_metadata": { + "author": "Model Context Protocol", + "homepage": "https://modelcontextprotocol.io" + }, + "metadata": { + "last_updated": "2026-02-05T04:49:20Z" + }, + "status": "Active", + "tags": [ + "remote", + "mcp", + "specification", + "protocol", + "documentation", + "official" + ], + "tier": "Official", + "tools": [ + "SearchModelContextProtocol" + ] + } + } + } + } +} diff --git a/registries/toolhive/servers/memory/server.json b/registries/toolhive/servers/memory/server.json new file mode 100644 index 00000000..05cfd409 --- /dev/null +++ b/registries/toolhive/servers/memory/server.json @@ -0,0 +1,70 @@ +{ + "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json", + "name": "io.github.stacklok/memory", + "description": "Persistent memory for LLM applications using local knowledge graph to store user information", + "title": "memory", + "repository": { + "url": "https://github.com/modelcontextprotocol/servers", + "source": "github" + }, + "version": "1.0.0", + "packages": [ + { + "registryType": "oci", + "identifier": "docker.io/mcp/memory:latest", + "transport": { + "type": "stdio" + }, + "environmentVariables": [ + { + "description": "Path to the memory storage JSON file (default: memory.json in the server directory)", + "name": "MEMORY_FILE_PATH" + } + ] + } + ], + "_meta": { + "io.modelcontextprotocol.registry/publisher-provided": { + "io.github.stacklok": { + "docker.io/mcp/memory:latest": { + "metadata": { + "last_updated": "2026-01-27T02:44:59Z", + "pulls": 15854, + "stars": 77255 + }, + "permissions": { + "network": { + "outbound": { + "allow_port": [ + 443 + ] + } + } + }, + "status": "Active", + "tags": [ + "entities", + "graph", + "knowledge", + "memory", + "observations", + "persistent", + "relations" + ], + "tier": "Community", + "tools": [ + "create_entities", + "create_relations", + "add_observations", + "delete_entities", + "delete_observations", + "delete_relations", + "read_graph", + "search_nodes", + "open_nodes" + ] + } + } + } + } +} diff --git a/registries/toolhive/servers/mermaid/server.json b/registries/toolhive/servers/mermaid/server.json new file mode 100644 index 00000000..ac670f21 --- /dev/null +++ b/registries/toolhive/servers/mermaid/server.json @@ -0,0 +1,39 @@ +{ + "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json", + "name": "io.github.stacklok/mermaid", + "description": "Enables AI assistants to create, validate, and render Mermaid diagrams seamlessly", + "title": "mermaid", + "version": "1.0.0", + "remotes": [ + { + "type": "streamable-http", + "url": "https://mcp.mermaidchart.com/mcp" + } + ], + "_meta": { + "io.modelcontextprotocol.registry/publisher-provided": { + "io.github.stacklok": { + "https://mcp.mermaidchart.com/mcp": { + "custom_metadata": { + "author": "Mermaid", + "homepage": "https://docs.mermaidchart.com/ai/mcp-server" + }, + "metadata": { + "last_updated": "2026-02-14T02:56:21Z" + }, + "status": "Active", + "tags": [ + "remote", + "diagrams", + "visualization", + "mermaid" + ], + "tier": "Official", + "tools": [ + "validate_and_render_mermaid_diagram" + ] + } + } + } + } +} diff --git a/registries/toolhive/servers/monday/server.json b/registries/toolhive/servers/monday/server.json new file mode 100644 index 00000000..111b7a05 --- /dev/null +++ b/registries/toolhive/servers/monday/server.json @@ -0,0 +1,67 @@ +{ + "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json", + "name": "io.github.stacklok/monday", + "description": "Monday.com's official remote MCP server for project and workflow management", + "title": "monday", + "repository": { + "url": "https://github.com/mondaycom/mcp", + "source": "github" + }, + "version": "1.0.0", + "remotes": [ + { + "type": "sse", + "url": "https://mcp.monday.com/sse" + } + ], + "_meta": { + "io.modelcontextprotocol.registry/publisher-provided": { + "io.github.stacklok": { + "https://mcp.monday.com/sse": { + "custom_metadata": { + "author": "Monday.com", + "license": "MIT" + }, + "metadata": { + "last_updated": "2026-02-05T04:49:20Z", + "stars": 366 + }, + "status": "Active", + "tags": [ + "remote", + "monday", + "project-management", + "workflow", + "collaboration", + "boards", + "items", + "api-token", + "work-os", + "automation" + ], + "tier": "Official", + "tools": [ + "get_board_items_by_name", + "create_item", + "create_update", + "get_board_activity", + "get_board_info", + "create_board", + "create_column", + "list_users_and_teams", + "all_monday_api", + "get_graphql_schema", + "get_column_type_info", + "get_type_details", + "read_docs", + "workspace_info", + "list_workspaces", + "create_doc", + "create_dashboard", + "all_widgets_schema" + ] + } + } + } + } +} diff --git a/registries/toolhive/servers/mongodb/server.json b/registries/toolhive/servers/mongodb/server.json new file mode 100644 index 00000000..9e54108a --- /dev/null +++ b/registries/toolhive/servers/mongodb/server.json @@ -0,0 +1,125 @@ +{ + "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json", + "name": "io.github.stacklok/mongodb", + "description": "Provides support for interacting with MongoDB Databases and MongoDB Atlas.", + "title": "mongodb", + "repository": { + "url": "https://github.com/mongodb-js/mongodb-mcp-server", + "source": "github" + }, + "version": "1.0.0", + "packages": [ + { + "registryType": "oci", + "identifier": "docker.io/mongodb/mongodb-mcp-server:1.6.0", + "transport": { + "type": "stdio" + }, + "environmentVariables": [ + { + "description": "MongoDB connection string for direct database connections (optional, if not set, you'll need to call the connect tool before interacting with MongoDB data)", + "isSecret": true, + "name": "MDB_MCP_CONNECTION_STRING" + }, + { + "description": "Atlas API client ID for authentication (required for running Atlas tools)", + "isSecret": true, + "name": "MDB_MCP_API_CLIENT_ID" + }, + { + "description": "Atlas API client secret for authentication (required for running Atlas tools)", + "isSecret": true, + "name": "MDB_MCP_API_CLIENT_SECRET" + }, + { + "description": "Atlas API base URL (default is https://cloud.mongodb.com/)", + "name": "MDB_MCP_API_BASE_URL" + }, + { + "description": "MongoDB server address for direct connections (optional, used for connect tool)", + "name": "MDB_MCP_SERVER_ADDRESS" + }, + { + "description": "MongoDB server port for direct connections (optional, used for connect tool)", + "name": "MDB_MCP_SERVER_PORT" + }, + { + "description": "Folder to store logs (inside the container)", + "name": "MDB_MCP_LOG_PATH" + }, + { + "description": "Comma-separated list of tool names, operation types, and/or categories of tools to disable", + "name": "MDB_MCP_DISABLED_TOOLS" + }, + { + "description": "When set to true, only allows read and metadata operation types", + "name": "MDB_MCP_READ_ONLY" + }, + { + "description": "When set to disabled, disables telemetry collection", + "name": "MDB_MCP_TELEMETRY" + } + ] + } + ], + "_meta": { + "io.modelcontextprotocol.registry/publisher-provided": { + "io.github.stacklok": { + "docker.io/mongodb/mongodb-mcp-server:1.6.0": { + "metadata": { + "last_updated": "2026-01-25T13:39:52Z", + "pulls": 5060, + "stars": 896 + }, + "permissions": { + "network": { + "outbound": { + "allow_port": [ + 443, + 27017, + 27018, + 27019, + 27020 + ], + "insecure_allow_all": true + } + } + }, + "status": "Active", + "tags": [ + "mongodb", + "mongo", + "atlas", + "database", + "data", + "query" + ], + "tier": "Official", + "tools": [ + "aggregate", + "collection-indexes", + "collection-schema", + "collection-storage-size", + "connect", + "count", + "create-collection", + "create-index", + "db-stats", + "delete-many", + "drop-collection", + "drop-database", + "explain", + "export", + "find", + "insert-many", + "list-collections", + "list-databases", + "mongodb-logs", + "rename-collection", + "update-many" + ] + } + } + } + } +} diff --git a/registries/toolhive/servers/neon/server.json b/registries/toolhive/servers/neon/server.json new file mode 100644 index 00000000..369177e1 --- /dev/null +++ b/registries/toolhive/servers/neon/server.json @@ -0,0 +1,66 @@ +{ + "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json", + "name": "io.github.stacklok/neon", + "description": "Neon's official remote MCP server for serverless Postgres with branching and migrations", + "title": "neon", + "version": "1.0.0", + "remotes": [ + { + "type": "streamable-http", + "url": "https://mcp.neon.tech/mcp" + } + ], + "_meta": { + "io.modelcontextprotocol.registry/publisher-provided": { + "io.github.stacklok": { + "https://mcp.neon.tech/mcp": { + "custom_metadata": { + "author": "Neon" + }, + "metadata": { + "last_updated": "2026-02-07T02:54:31Z" + }, + "status": "Active", + "tags": [ + "remote", + "neon", + "postgres", + "serverless", + "database", + "oauth", + "project-management", + "branching", + "sql", + "query-tuning" + ], + "tier": "Official", + "tools": [ + "list_projects", + "list_organizations", + "list_shared_projects", + "create_project", + "delete_project", + "describe_project", + "run_sql", + "run_sql_transaction", + "describe_table_schema", + "get_database_tables", + "explain_sql_statement", + "create_branch", + "describe_branch", + "delete_branch", + "reset_from_parent", + "prepare_database_migration", + "complete_database_migration", + "list_branch_computes", + "get_connection_string", + "provision_neon_auth", + "prepare_query_tuning", + "complete_query_tuning", + "list_slow_queries" + ] + } + } + } + } +} diff --git a/registries/toolhive/servers/netbird/server.json b/registries/toolhive/servers/netbird/server.json new file mode 100644 index 00000000..24914490 --- /dev/null +++ b/registries/toolhive/servers/netbird/server.json @@ -0,0 +1,86 @@ +{ + "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json", + "name": "io.github.stacklok/netbird", + "description": "Enables management of an NetBird network.", + "title": "netbird", + "repository": { + "url": "https://github.com/aantti/mcp-netbird", + "source": "github" + }, + "version": "1.0.0", + "packages": [ + { + "registryType": "oci", + "identifier": "ghcr.io/stacklok/dockyard/go/netbird:latest", + "transport": { + "type": "sse", + "url": "http://localhost:8001" + }, + "environmentVariables": [ + { + "description": "NetBird API token for authentication", + "isRequired": true, + "isSecret": true, + "name": "NETBIRD_API_TOKEN" + }, + { + "description": "NetBird API host (default is api.netbird.io)", + "name": "NETBIRD_HOST" + } + ] + } + ], + "_meta": { + "io.modelcontextprotocol.registry/publisher-provided": { + "io.github.stacklok": { + "ghcr.io/stacklok/dockyard/go/netbird:latest": { + "args": [ + "--transport", + "sse", + "--sse-address", + ":8001" + ], + "metadata": { + "last_updated": "2026-02-05T04:47:24Z", + "pulls": 10904, + "stars": 41 + }, + "permissions": { + "network": { + "outbound": { + "allow_host": [ + "api.netbird.io" + ], + "allow_port": [ + 443 + ], + "insecure_allow_all": true + } + } + }, + "status": "Active", + "tags": [ + "netbird", + "vpn", + "networking", + "peer", + "route", + "dns", + "setup-key", + "management" + ], + "tier": "Community", + "tools": [ + "list_netbird_peers", + "list_netbird_port_allocations", + "list_netbird_groups", + "list_netbird_policies", + "list_netbird_posture_checks", + "list_netbird_networks", + "list_netbird_nameservers" + ] + } + } + } + } +} diff --git a/registries/toolhive/servers/notion-remote/server.json b/registries/toolhive/servers/notion-remote/server.json new file mode 100644 index 00000000..705a8277 --- /dev/null +++ b/registries/toolhive/servers/notion-remote/server.json @@ -0,0 +1,57 @@ +{ + "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json", + "name": "io.github.stacklok/notion-remote", + "description": "Notion's official remote MCP server for workspaces, pages, databases, and comments", + "title": "notion-remote", + "version": "1.0.0", + "remotes": [ + { + "type": "streamable-http", + "url": "https://mcp.notion.com/mcp" + } + ], + "_meta": { + "io.modelcontextprotocol.registry/publisher-provided": { + "io.github.stacklok": { + "https://mcp.notion.com/mcp": { + "custom_metadata": { + "author": "Notion", + "homepage": "https://developers.notion.com/docs/get-started-with-mcp" + }, + "metadata": { + "last_updated": "2026-02-05T04:49:20Z" + }, + "status": "Active", + "tags": [ + "remote", + "notion", + "workspace", + "pages", + "databases", + "comments", + "oauth", + "productivity", + "collaboration" + ], + "tier": "Official", + "tools": [ + "notion-search", + "notion-fetch", + "notion-create-pages", + "notion-update-page", + "notion-move-pages", + "notion-duplicate-page", + "notion-create-database", + "notion-update-database", + "notion-create-comment", + "notion-get-comments", + "notion-get-teams", + "notion-get-users", + "notion-get-user", + "notion-get-self" + ] + } + } + } + } +} diff --git a/registries/toolhive/servers/notion/server.json b/registries/toolhive/servers/notion/server.json new file mode 100644 index 00000000..416a26e9 --- /dev/null +++ b/registries/toolhive/servers/notion/server.json @@ -0,0 +1,88 @@ +{ + "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json", + "name": "io.github.stacklok/notion", + "description": "Provides integration with Notion APIs through a local Notion MCP Server.", + "title": "notion", + "repository": { + "url": "https://github.com/makenotion/notion-mcp-server", + "source": "github" + }, + "version": "1.0.0", + "packages": [ + { + "registryType": "oci", + "identifier": "ghcr.io/stacklok/dockyard/npx/notion:2.1.0", + "transport": { + "type": "stdio" + }, + "environmentVariables": [ + { + "description": "Notion integration token (ntn_****).", + "isRequired": true, + "isSecret": true, + "name": "NOTION_TOKEN" + }, + { + "description": "HTTP headers for Notion API requests in JSON format (advanced use cases). Example: {\"Authorization\":\"Bearer ntn_****\",\"Notion-Version\":\"2022-06-28\"}", + "isSecret": true, + "name": "OPENAPI_MCP_HEADERS" + } + ] + } + ], + "_meta": { + "io.modelcontextprotocol.registry/publisher-provided": { + "io.github.stacklok": { + "ghcr.io/stacklok/dockyard/npx/notion:2.1.0": { + "metadata": { + "last_updated": "2026-02-05T04:49:19Z", + "pulls": 40073, + "stars": 3846 + }, + "permissions": { + "network": { + "outbound": { + "allow_host": [ + "api.notion.com" + ], + "allow_port": [ + 443 + ] + } + } + }, + "status": "Active", + "tags": [ + "notion", + "notes" + ], + "tier": "Official", + "tools": [ + "API-create-a-comment", + "API-create-a-data-source", + "API-delete-a-block", + "API-get-block-children", + "API-get-self", + "API-get-user", + "API-get-users", + "API-list-data-source-templates", + "API-move-page", + "API-patch-block-children", + "API-patch-page", + "API-post-page", + "API-post-search", + "API-query-data-source", + "API-retrieve-a-block", + "API-retrieve-a-comment", + "API-retrieve-a-data-source", + "API-retrieve-a-database", + "API-retrieve-a-page", + "API-retrieve-a-page-property", + "API-update-a-block", + "API-update-a-data-source" + ] + } + } + } + } +} diff --git a/registries/toolhive/servers/oci-registry/server.json b/registries/toolhive/servers/oci-registry/server.json new file mode 100644 index 00000000..c5fbdbbc --- /dev/null +++ b/registries/toolhive/servers/oci-registry/server.json @@ -0,0 +1,86 @@ +{ + "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json", + "name": "io.github.stacklok/oci-registry", + "description": "Secure OCI container registry querying with image introspection and manifest retrieval", + "title": "oci-registry", + "repository": { + "url": "https://github.com/StacklokLabs/ocireg-mcp", + "source": "github" + }, + "version": "1.0.0", + "packages": [ + { + "registryType": "oci", + "identifier": "ghcr.io/stackloklabs/ocireg-mcp/server:0.1.0", + "transport": { + "type": "streamable-http", + "url": "http://localhost:8080" + }, + "environmentVariables": [ + { + "description": "Bearer token for OCI registry authentication", + "isSecret": true, + "name": "OCI_TOKEN" + }, + { + "description": "Username for registry authentication", + "name": "OCI_USERNAME" + }, + { + "description": "Password for registry authentication", + "isSecret": true, + "name": "OCI_PASSWORD" + } + ] + } + ], + "_meta": { + "io.modelcontextprotocol.registry/publisher-provided": { + "io.github.stacklok": { + "ghcr.io/stackloklabs/ocireg-mcp/server:0.1.0": { + "metadata": { + "last_updated": "2026-02-05T04:47:24Z", + "pulls": 8029, + "stars": 11 + }, + "permissions": { + "network": { + "outbound": { + "allow_port": [ + 443 + ], + "insecure_allow_all": true + } + } + }, + "provenance": { + "cert_issuer": "https://token.actions.githubusercontent.com", + "repository_uri": "https://github.com/StacklokLabs/ocireg-mcp", + "runner_environment": "github-hosted", + "signer_identity": "/.github/workflows/release.yml", + "sigstore_url": "tuf-repo-cdn.sigstore.dev" + }, + "status": "Active", + "tags": [ + "oci", + "registry", + "containers", + "images", + "tags", + "manifest", + "config", + "mcp", + "docker" + ], + "tier": "Community", + "tools": [ + "get_image_info", + "list_tags", + "get_image_manifest", + "get_image_config" + ] + } + } + } + } +} diff --git a/registries/toolhive/servers/onchain-mcp/server.json b/registries/toolhive/servers/onchain-mcp/server.json new file mode 100644 index 00000000..dbe05707 --- /dev/null +++ b/registries/toolhive/servers/onchain-mcp/server.json @@ -0,0 +1,81 @@ +{ + "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json", + "name": "io.github.stacklok/onchain-mcp", + "description": "MCP server for blockchain data interaction through the Bankless API", + "title": "onchain-mcp", + "repository": { + "url": "https://github.com/Bankless/onchain-mcp", + "source": "github" + }, + "version": "1.0.0", + "packages": [ + { + "registryType": "oci", + "identifier": "ghcr.io/stacklok/dockyard/npx/onchain-mcp:1.0.6", + "transport": { + "type": "stdio" + }, + "environmentVariables": [ + { + "description": "API token for Bankless API authentication", + "isRequired": true, + "isSecret": true, + "name": "BANKLESS_API_TOKEN" + } + ] + } + ], + "_meta": { + "io.modelcontextprotocol.registry/publisher-provided": { + "io.github.stacklok": { + "ghcr.io/stacklok/dockyard/npx/onchain-mcp:1.0.6": { + "metadata": { + "last_updated": "2026-02-05T04:49:16Z", + "stars": 75 + }, + "permissions": { + "network": { + "outbound": { + "allow_host": [ + "api.bankless.com" + ], + "allow_port": [ + 443 + ] + } + } + }, + "provenance": { + "cert_issuer": "https://token.actions.githubusercontent.com", + "repository_uri": "https://github.com/stacklok/dockyard", + "runner_environment": "github-hosted", + "signer_identity": "/.github/workflows/build-containers.yml", + "sigstore_url": "tuf-repo-cdn.sigstore.dev" + }, + "status": "Active", + "tags": [ + "blockchain", + "ethereum", + "web3", + "smart-contracts", + "defi", + "crypto" + ], + "tier": "Official", + "tools": [ + "read_contract", + "get_proxy", + "get_abi", + "get_source", + "get_events", + "build_event_topic", + "get_transaction_history_for_user", + "get_transaction_info", + "get_token_balances_on_network", + "get_block_info" + ] + } + } + } + } +} diff --git a/registries/toolhive/servers/osv/server.json b/registries/toolhive/servers/osv/server.json new file mode 100644 index 00000000..cd4dda5f --- /dev/null +++ b/registries/toolhive/servers/osv/server.json @@ -0,0 +1,71 @@ +{ + "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json", + "name": "io.github.stacklok/osv", + "description": "OSV (Open Source Vulnerabilities) database access for querying package and commit vulnerabilities", + "title": "osv", + "repository": { + "url": "https://github.com/StacklokLabs/osv-mcp", + "source": "github" + }, + "version": "1.0.0", + "packages": [ + { + "registryType": "oci", + "identifier": "ghcr.io/stackloklabs/osv-mcp/server:0.1.0", + "transport": { + "type": "streamable-http", + "url": "http://localhost:8080" + } + } + ], + "_meta": { + "io.modelcontextprotocol.registry/publisher-provided": { + "io.github.stacklok": { + "ghcr.io/stackloklabs/osv-mcp/server:0.1.0": { + "metadata": { + "last_updated": "2026-01-30T02:55:46Z", + "stars": 26 + }, + "permissions": { + "network": { + "outbound": { + "allow_host": [ + "api.osv.dev" + ], + "allow_port": [ + 443 + ] + } + } + }, + "provenance": { + "cert_issuer": "https://token.actions.githubusercontent.com", + "repository_uri": "https://github.com/StacklokLabs/osv-mcp", + "runner_environment": "github-hosted", + "signer_identity": "/.github/workflows/release.yml", + "sigstore_url": "tuf-repo-cdn.sigstore.dev" + }, + "status": "Active", + "tags": [ + "vulnerability", + "security", + "osv", + "open-source", + "cve", + "packages", + "dependencies", + "scanning", + "security-scanning", + "vulnerability-detection" + ], + "tier": "Community", + "tools": [ + "query_vulnerability", + "query_vulnerabilities_batch", + "get_vulnerability" + ] + } + } + } + } +} diff --git a/registries/toolhive/servers/pagerduty/server.json b/registries/toolhive/servers/pagerduty/server.json new file mode 100644 index 00000000..63842b0c --- /dev/null +++ b/registries/toolhive/servers/pagerduty/server.json @@ -0,0 +1,137 @@ +{ + "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json", + "name": "io.github.stacklok/pagerduty", + "description": "Interact with PagerDuty for managing incidents, services, schedules, and event orchestrations", + "title": "pagerduty", + "repository": { + "url": "https://github.com/PagerDuty/pagerduty-mcp-server", + "source": "github" + }, + "version": "1.0.0", + "packages": [ + { + "registryType": "oci", + "identifier": "ghcr.io/stacklok/dockyard/uvx/pagerduty-mcp:0.12.0", + "transport": { + "type": "stdio" + }, + "environmentVariables": [ + { + "description": "User API token for PagerDuty authentication", + "isRequired": true, + "isSecret": true, + "name": "PAGERDUTY_USER_API_KEY" + }, + { + "description": "PagerDuty API endpoint URL", + "default": "https://api.pagerduty.com", + "name": "PAGERDUTY_API_HOST" + } + ] + } + ], + "_meta": { + "io.modelcontextprotocol.registry/publisher-provided": { + "io.github.stacklok": { + "ghcr.io/stacklok/dockyard/uvx/pagerduty-mcp:0.12.0": { + "args": [ + "--enable-write-tools" + ], + "metadata": { + "last_updated": "2026-02-05T04:47:22Z", + "stars": 45 + }, + "permissions": { + "network": { + "outbound": { + "allow_host": [ + "api.pagerduty.com", + "api.eu.pagerduty.com" + ], + "allow_port": [ + 443 + ] + } + } + }, + "status": "Active", + "tags": [ + "integration", + "api", + "incident-management", + "on-call", + "monitoring", + "alerting" + ], + "tier": "Official", + "tools": [ + "add_note_to_incident", + "add_responders", + "add_team_member", + "append_event_orchestration_router_rule", + "create_alert_grouping_setting", + "create_incident", + "create_schedule", + "create_schedule_override", + "create_service", + "create_status_page_post", + "create_status_page_post_update", + "create_team", + "delete_alert_grouping_setting", + "delete_team", + "get_alert_from_incident", + "get_alert_grouping_setting", + "get_change_event", + "get_escalation_policy", + "get_event_orchestration", + "get_event_orchestration_global", + "get_event_orchestration_router", + "get_event_orchestration_service", + "get_incident", + "get_incident_workflow", + "get_log_entry", + "get_outlier_incident", + "get_past_incidents", + "get_related_incidents", + "get_schedule", + "get_service", + "get_status_page_post", + "get_team", + "get_user_data", + "list_alert_grouping_settings", + "list_alerts_from_incident", + "list_change_events", + "list_escalation_policies", + "list_event_orchestrations", + "list_incident_change_events", + "list_incident_notes", + "list_incident_workflows", + "list_incidents", + "list_log_entries", + "list_oncalls", + "list_schedule_users", + "list_schedules", + "list_service_change_events", + "list_services", + "list_status_page_impacts", + "list_status_page_post_updates", + "list_status_page_severities", + "list_status_page_statuses", + "list_status_pages", + "list_team_members", + "list_teams", + "list_users", + "manage_incidents", + "remove_team_member", + "start_incident_workflow", + "update_alert_grouping_setting", + "update_event_orchestration_router", + "update_schedule", + "update_service", + "update_team" + ] + } + } + } + } +} diff --git a/registries/toolhive/servers/paypal/server.json b/registries/toolhive/servers/paypal/server.json new file mode 100644 index 00000000..ca3f29df --- /dev/null +++ b/registries/toolhive/servers/paypal/server.json @@ -0,0 +1,72 @@ +{ + "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json", + "name": "io.github.stacklok/paypal", + "description": "PayPal's MCP server for payment processing, invoices, and business operations", + "title": "paypal", + "version": "1.0.0", + "remotes": [ + { + "type": "streamable-http", + "url": "https://mcp.paypal.com/mcp" + } + ], + "_meta": { + "io.modelcontextprotocol.registry/publisher-provided": { + "io.github.stacklok": { + "https://mcp.paypal.com/mcp": { + "custom_metadata": { + "author": "PayPal", + "homepage": "https://www.paypal.ai/docs/tools/mcp-quickstart" + }, + "metadata": { + "last_updated": "2026-02-07T02:54:31Z" + }, + "status": "Active", + "tags": [ + "remote", + "payments", + "ecommerce", + "financial", + "paypal", + "transactions", + "invoices" + ], + "tier": "Official", + "tools": [ + "create_product", + "list_product", + "show_product_details", + "list_disputes", + "get_dispute", + "accept_dispute_claim", + "create_invoice", + "list_invoices", + "get_invoice", + "send_invoice", + "send_invoice_reminder", + "cancel_sent_invoice", + "generate_invoice_qr_code", + "create_order", + "create_refund", + "get_order", + "get_refund", + "pay_order", + "list_transaction", + "create_shipment_tracking", + "get_shipment_tracking", + "cancel_subscription", + "create_subscription", + "create_subscription_plan", + "list_subscription_plans", + "show_subscription_details", + "show_subscription_plan_details", + "update_subscription", + "search_product", + "create_cart", + "checkout_cart" + ] + } + } + } + } +} diff --git a/registries/toolhive/servers/perplexity-ask/server.json b/registries/toolhive/servers/perplexity-ask/server.json new file mode 100644 index 00000000..d48c1b99 --- /dev/null +++ b/registries/toolhive/servers/perplexity-ask/server.json @@ -0,0 +1,65 @@ +{ + "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json", + "name": "io.github.stacklok/perplexity-ask", + "description": "Integrates Perplexity AI's Sonar API for live web searches, in-depth research, and reasoning tasks.", + "title": "perplexity-ask", + "repository": { + "url": "https://github.com/ppl-ai/modelcontextprotocol", + "source": "github" + }, + "version": "1.0.0", + "packages": [ + { + "registryType": "oci", + "identifier": "docker.io/mcp/perplexity-ask:latest", + "transport": { + "type": "stdio" + }, + "environmentVariables": [ + { + "description": "Perplexity API key", + "isRequired": true, + "isSecret": true, + "name": "PERPLEXITY_API_KEY" + } + ] + } + ], + "_meta": { + "io.modelcontextprotocol.registry/publisher-provided": { + "io.github.stacklok": { + "docker.io/mcp/perplexity-ask:latest": { + "metadata": { + "last_updated": "2026-02-05T02:58:03Z", + "pulls": 15188, + "stars": 1921 + }, + "permissions": { + "network": { + "outbound": { + "allow_host": [ + "api.perplexity.ai" + ], + "allow_port": [ + 443 + ] + } + } + }, + "status": "Active", + "tags": [ + "ask", + "perplexity", + "perplexity-ask" + ], + "tier": "Official", + "tools": [ + "perplexity_ask", + "perplexity_research", + "perplexity_reason" + ] + } + } + } + } +} diff --git a/registries/toolhive/servers/phoenix/server.json b/registries/toolhive/servers/phoenix/server.json new file mode 100644 index 00000000..da937209 --- /dev/null +++ b/registries/toolhive/servers/phoenix/server.json @@ -0,0 +1,89 @@ +{ + "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json", + "name": "io.github.stacklok/phoenix", + "description": "MCP server for Arize Phoenix observability platform", + "title": "phoenix", + "repository": { + "url": "https://github.com/Arize-ai/phoenix", + "source": "github" + }, + "version": "1.0.0", + "packages": [ + { + "registryType": "oci", + "identifier": "ghcr.io/stacklok/dockyard/npx/phoenix-mcp:2.3.6", + "transport": { + "type": "stdio" + }, + "environmentVariables": [ + { + "description": "API key for Phoenix authentication", + "isSecret": true, + "name": "PHOENIX_API_KEY" + }, + { + "description": "Base URL for Phoenix instance", + "default": "http://localhost:6006", + "name": "PHOENIX_BASE_URL" + } + ] + } + ], + "_meta": { + "io.modelcontextprotocol.registry/publisher-provided": { + "io.github.stacklok": { + "ghcr.io/stacklok/dockyard/npx/phoenix-mcp:2.3.6": { + "metadata": { + "last_updated": "2026-02-05T04:49:18Z", + "stars": 8461 + }, + "permissions": { + "network": { + "outbound": { + "insecure_allow_all": true + } + } + }, + "provenance": { + "cert_issuer": "https://token.actions.githubusercontent.com", + "repository_uri": "https://github.com/stacklok/dockyard", + "runner_environment": "github-hosted", + "signer_identity": "/.github/workflows/build-containers.yml", + "sigstore_url": "tuf-repo-cdn.sigstore.dev" + }, + "status": "Active", + "tags": [ + "observability", + "llm", + "monitoring", + "tracing", + "evaluation", + "ai" + ], + "tier": "Official", + "tools": [ + "add-dataset-examples", + "add-prompt-version-tag", + "get-dataset-examples", + "get-dataset-experiments", + "get-experiment-by-id", + "get-latest-prompt", + "get-prompt-by-identifier", + "get-prompt-version", + "get-prompt-version-by-tag", + "get-span-annotations", + "get-spans", + "list-datasets", + "list-experiments-for-dataset", + "list-projects", + "list-prompt-version-tags", + "list-prompt-versions", + "list-prompts", + "phoenix-support", + "upsert-prompt" + ] + } + } + } + } +} diff --git a/registries/toolhive/servers/playwright/server.json b/registries/toolhive/servers/playwright/server.json new file mode 100644 index 00000000..10b6f557 --- /dev/null +++ b/registries/toolhive/servers/playwright/server.json @@ -0,0 +1,78 @@ +{ + "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json", + "name": "io.github.stacklok/playwright", + "description": "Provides browser automation capabilities using Playwright", + "title": "playwright", + "repository": { + "url": "https://github.com/microsoft/playwright-mcp", + "source": "github" + }, + "version": "1.0.0", + "packages": [ + { + "registryType": "oci", + "identifier": "mcr.microsoft.com/playwright/mcp:v0.0.68", + "transport": { + "type": "stdio" + } + } + ], + "_meta": { + "io.modelcontextprotocol.registry/publisher-provided": { + "io.github.stacklok": { + "mcr.microsoft.com/playwright/mcp:v0.0.68": { + "metadata": { + "last_updated": "2026-01-27T02:45:00Z", + "pulls": 23622, + "stars": 26247 + }, + "permissions": { + "network": { + "outbound": { + "allow_port": [ + 443 + ], + "insecure_allow_all": true + } + } + }, + "status": "Active", + "tags": [ + "playwright", + "automation", + "browser", + "navigate", + "testing", + "web", + "accessibility" + ], + "tier": "Official", + "tools": [ + "browser_click", + "browser_close", + "browser_console_messages", + "browser_drag", + "browser_evaluate", + "browser_file_upload", + "browser_fill_form", + "browser_handle_dialog", + "browser_hover", + "browser_install", + "browser_navigate", + "browser_navigate_back", + "browser_network_requests", + "browser_press_key", + "browser_resize", + "browser_run_code", + "browser_select_option", + "browser_snapshot", + "browser_tabs", + "browser_take_screenshot", + "browser_type", + "browser_wait_for" + ] + } + } + } + } +} diff --git a/registries/toolhive/servers/plotting/server.json b/registries/toolhive/servers/plotting/server.json new file mode 100644 index 00000000..9fc28371 --- /dev/null +++ b/registries/toolhive/servers/plotting/server.json @@ -0,0 +1,62 @@ +{ + "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json", + "name": "io.github.stacklok/plotting", + "description": "Provides plotting capabilities for visualizing data in various formats.", + "title": "plotting", + "repository": { + "url": "https://github.com/StacklokLabs/plotting-mcp", + "source": "github" + }, + "version": "1.0.0", + "packages": [ + { + "registryType": "oci", + "identifier": "ghcr.io/stackloklabs/plotting-mcp:v0.0.2", + "transport": { + "type": "streamable-http", + "url": "http://localhost:9090" + } + } + ], + "_meta": { + "io.modelcontextprotocol.registry/publisher-provided": { + "io.github.stacklok": { + "ghcr.io/stackloklabs/plotting-mcp:v0.0.2": { + "metadata": { + "last_updated": "2026-02-05T04:47:23Z", + "pulls": 1314, + "stars": 7 + }, + "permissions": { + "network": { + "outbound": {} + } + }, + "provenance": { + "cert_issuer": "https://token.actions.githubusercontent.com", + "repository_uri": "https://github.com/StacklokLabs/plotting-mcp", + "runner_environment": "github-hosted", + "signer_identity": "/.github/workflows/release.yml", + "sigstore_url": "tuf-repo-cdn.sigstore.dev" + }, + "status": "Active", + "tags": [ + "plotting", + "visualization", + "data", + "charts", + "graphs", + "matplotlib", + "seaborn", + "cartopy", + "maps" + ], + "tier": "Community", + "tools": [ + "generate_plot" + ] + } + } + } + } +} diff --git a/registries/toolhive/servers/postgres-mcp-pro/server.json b/registries/toolhive/servers/postgres-mcp-pro/server.json new file mode 100644 index 00000000..cccf52f3 --- /dev/null +++ b/registries/toolhive/servers/postgres-mcp-pro/server.json @@ -0,0 +1,84 @@ +{ + "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json", + "name": "io.github.stacklok/postgres-mcp-pro", + "description": "Provides configurable read/write access and performance analysis for PostgreSQL databases.", + "title": "postgres-mcp-pro", + "repository": { + "url": "https://github.com/crystaldba/postgres-mcp", + "source": "github" + }, + "version": "1.0.0", + "packages": [ + { + "registryType": "oci", + "identifier": "crystaldba/postgres-mcp:0.3.0", + "transport": { + "type": "sse", + "url": "http://localhost:8000" + }, + "environmentVariables": [ + { + "description": "PostgreSQL connection string, like 'postgresql://username:password@host.docker.internal:5432/dbname'", + "isRequired": true, + "isSecret": true, + "name": "DATABASE_URI" + }, + { + "description": "OpenAI API key for experimental index tuning by LLM", + "isSecret": true, + "name": "OPENAI_API_KEY" + } + ] + } + ], + "_meta": { + "io.modelcontextprotocol.registry/publisher-provided": { + "io.github.stacklok": { + "crystaldba/postgres-mcp:0.3.0": { + "args": [ + "--transport=sse", + "--sse-host=0.0.0.0", + "--sse-port=8000" + ], + "metadata": { + "last_updated": "2026-01-27T02:45:00Z", + "pulls": 70324, + "stars": 1903 + }, + "permissions": { + "network": { + "outbound": { + "insecure_allow_all": true + } + } + }, + "status": "Active", + "tags": [ + "database", + "data", + "postgres", + "postgresql", + "sql", + "query", + "storage", + "analytics", + "performance", + "monitoring" + ], + "tier": "Official", + "tools": [ + "list_schemas", + "list_objects", + "get_object_details", + "execute_sql", + "explain_query", + "get_top_queries", + "analyze_workload_indexes", + "analyze_query_indexes", + "analyze_db_health" + ] + } + } + } + } +} diff --git a/registries/toolhive/servers/prometheus/server.json b/registries/toolhive/servers/prometheus/server.json new file mode 100644 index 00000000..67288774 --- /dev/null +++ b/registries/toolhive/servers/prometheus/server.json @@ -0,0 +1,93 @@ +{ + "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json", + "name": "io.github.stacklok/prometheus", + "description": "Provides access to Prometheus metrics and queries", + "title": "prometheus", + "repository": { + "url": "https://github.com/pab1it0/prometheus-mcp-server", + "source": "github" + }, + "version": "1.0.0", + "packages": [ + { + "registryType": "oci", + "identifier": "ghcr.io/pab1it0/prometheus-mcp-server:1.5.3", + "transport": { + "type": "stdio" + }, + "environmentVariables": [ + { + "description": "URL of your Prometheus server", + "isRequired": true, + "name": "PROMETHEUS_URL" + }, + { + "description": "Username for basic authentication", + "name": "PROMETHEUS_USERNAME" + }, + { + "description": "Password for basic authentication", + "isSecret": true, + "name": "PROMETHEUS_PASSWORD" + }, + { + "description": "Bearer token for authentication", + "isSecret": true, + "name": "PROMETHEUS_TOKEN" + }, + { + "description": "Set to False to disable SSL verification", + "name": "PROMETHEUS_URL_SSL_VERIFY" + }, + { + "description": "Set to True to disable Prometheus UI links in query results (saves context tokens)", + "name": "PROMETHEUS_DISABLE_LINKS" + }, + { + "description": "Organization ID for multi-tenant setups", + "name": "ORG_ID" + }, + { + "description": "Custom headers as JSON string", + "name": "PROMETHEUS_CUSTOM_HEADERS" + } + ] + } + ], + "_meta": { + "io.modelcontextprotocol.registry/publisher-provided": { + "io.github.stacklok": { + "ghcr.io/pab1it0/prometheus-mcp-server:1.5.3": { + "metadata": { + "last_updated": "2026-01-30T02:55:46Z", + "stars": 360 + }, + "permissions": { + "network": { + "outbound": { + "insecure_allow_all": true + } + } + }, + "status": "Active", + "tags": [ + "monitoring", + "metrics", + "prometheus", + "observability", + "query" + ], + "tier": "Community", + "tools": [ + "health_check", + "execute_query", + "execute_range_query", + "list_metrics", + "get_metric_metadata", + "get_targets" + ] + } + } + } + } +} diff --git a/registries/toolhive/servers/redhat-linux/server.json b/registries/toolhive/servers/redhat-linux/server.json new file mode 100644 index 00000000..432a916a --- /dev/null +++ b/registries/toolhive/servers/redhat-linux/server.json @@ -0,0 +1,88 @@ +{ + "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json", + "name": "io.github.stacklok/redhat-linux", + "description": "Read-only Linux sysadmin, diagnostics, and troubleshooting optimized for RHEL-based systems", + "title": "redhat-linux", + "repository": { + "url": "https://github.com/rhel-lightspeed/linux-mcp-server", + "source": "github" + }, + "version": "1.0.0", + "packages": [ + { + "registryType": "oci", + "identifier": "quay.io/redhat-services-prod/rhel-lightspeed-tenant/linux-mcp-server:1.3.0", + "transport": { + "type": "stdio" + }, + "environmentVariables": [ + { + "description": "Username for SSH connections", + "name": "LINUX_MCP_USER" + }, + { + "description": "Passphrase for encrypted SSH key decryption", + "isSecret": true, + "name": "LINUX_MCP_KEY_PASSPHRASE" + }, + { + "description": "Path to SSH private key for remote execution (mounted into container)", + "name": "LINUX_MCP_SSH_KEY_PATH" + }, + { + "description": "Comma-separated whitelist of accessible log file paths", + "name": "LINUX_MCP_ALLOWED_LOG_PATHS" + }, + { + "description": "Logging level (DEBUG, INFO, WARNING, ERROR, or CRITICAL)", + "default": "INFO", + "name": "LINUX_MCP_LOG_LEVEL" + } + ] + } + ], + "_meta": { + "io.modelcontextprotocol.registry/publisher-provided": { + "io.github.stacklok": { + "quay.io/redhat-services-prod/rhel-lightspeed-tenant/linux-mcp-server:1.3.0": { + "metadata": { + "last_updated": "2026-02-05T04:49:21Z", + "stars": 160 + }, + "status": "Active", + "tags": [ + "linux", + "diagnostics", + "sysadmin", + "rhel", + "monitoring", + "troubleshooting", + "networking" + ], + "tier": "Official", + "tools": [ + "get_cpu_information", + "get_disk_usage", + "get_hardware_information", + "get_journal_logs", + "get_listening_ports", + "get_memory_information", + "get_network_connections", + "get_network_interfaces", + "get_process_info", + "get_service_logs", + "get_service_status", + "get_system_information", + "list_block_devices", + "list_directories", + "list_files", + "list_processes", + "list_services", + "read_file", + "read_log_file" + ] + } + } + } + } +} diff --git a/registries/toolhive/servers/redis/server.json b/registries/toolhive/servers/redis/server.json new file mode 100644 index 00000000..ee60a158 --- /dev/null +++ b/registries/toolhive/servers/redis/server.json @@ -0,0 +1,156 @@ +{ + "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json", + "name": "io.github.stacklok/redis", + "description": "Enables LLMs to interact with Redis key-value databases through a set of standardized tools.", + "title": "redis", + "repository": { + "url": "https://github.com/redis/mcp-redis", + "source": "github" + }, + "version": "1.0.0", + "packages": [ + { + "registryType": "oci", + "identifier": "docker.io/mcp/redis:latest", + "transport": { + "type": "stdio" + }, + "environmentVariables": [ + { + "description": "Redis IP or hostname (default \"127.0.0.1\")", + "isRequired": true, + "name": "REDIS_HOST" + }, + { + "description": "Redis port (default 6379)", + "name": "REDIS_PORT" + }, + { + "description": "Redis database number (default 0)", + "name": "REDIS_DB" + }, + { + "description": "Redis username (default \"default\")", + "name": "REDIS_USERNAME" + }, + { + "description": "Redis password (default empty)", + "isSecret": true, + "name": "REDIS_PWD" + }, + { + "description": "Redis TLS connection (True|False, default False)", + "name": "REDIS_SSL" + }, + { + "description": "CA certificate for verifying server", + "name": "REDIS_CA_PATH" + }, + { + "description": "Client's private key file for client authentication", + "name": "REDIS_SSL_KEYFILE" + }, + { + "description": "Client's certificate file for client authentication", + "name": "REDIS_SSL_CERTFILE" + }, + { + "description": "Whether the client should verify the server's certificate (default \"required\")", + "name": "REDIS_CERT_REQS" + }, + { + "description": "Path to the trusted CA certificates file", + "name": "REDIS_CA_CERTS" + }, + { + "description": "Enable Redis Cluster mode (True|False, default False)", + "name": "REDIS_CLUSTER_MODE" + }, + { + "description": "Use the stdio or sse transport (default stdio)", + "name": "MCP_TRANSPORT" + } + ] + } + ], + "_meta": { + "io.modelcontextprotocol.registry/publisher-provided": { + "io.github.stacklok": { + "docker.io/mcp/redis:latest": { + "metadata": { + "last_updated": "2026-02-05T02:58:03Z", + "pulls": 10366, + "stars": 416 + }, + "permissions": { + "network": { + "outbound": { + "allow_port": [ + 443, + 6379 + ], + "insecure_allow_all": true + } + } + }, + "status": "Active", + "tags": [ + "redis", + "database", + "key-value", + "storage", + "cache", + "data" + ], + "tier": "Official", + "tools": [ + "dbsize", + "info", + "client_list", + "delete", + "type", + "expire", + "rename", + "scan_keys", + "scan_all_keys", + "get_indexes", + "get_index_info", + "get_indexed_keys_number", + "create_vector_index_hash", + "vector_search_hash", + "hset", + "hget", + "hdel", + "hgetall", + "hexists", + "set_vector_in_hash", + "get_vector_from_hash", + "lpush", + "rpush", + "lpop", + "rpop", + "lrange", + "llen", + "set", + "get", + "json_set", + "json_get", + "json_del", + "zadd", + "zrange", + "zrem", + "sadd", + "srem", + "smembers", + "xadd", + "xrange", + "xdel", + "publish", + "subscribe", + "unsubscribe" + ] + } + } + } + } +} diff --git a/registries/toolhive/servers/replicate/server.json b/registries/toolhive/servers/replicate/server.json new file mode 100644 index 00000000..b3250c6e --- /dev/null +++ b/registries/toolhive/servers/replicate/server.json @@ -0,0 +1,68 @@ +{ + "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json", + "name": "io.github.stacklok/replicate", + "description": "Replicate's official MCP server for AI models and ML workflows", + "title": "replicate", + "version": "1.0.0", + "remotes": [ + { + "type": "sse", + "url": "https://mcp.replicate.com/sse" + } + ], + "_meta": { + "io.modelcontextprotocol.registry/publisher-provided": { + "io.github.stacklok": { + "https://mcp.replicate.com/sse": { + "custom_metadata": { + "author": "Replicate", + "homepage": "https://replicate.com/docs/reference/mcp" + }, + "metadata": { + "last_updated": "2026-02-07T02:54:31Z" + }, + "status": "Active", + "tags": [ + "remote", + "ai", + "replicate", + "model-hosting", + "ml-workflows", + "inference", + "predictions" + ], + "tier": "Official", + "tools": [ + "list_collections", + "get_collections", + "create_deployments", + "update_deployments", + "list_deployments", + "get_deployments", + "create_deployments_predictions", + "list_hardware", + "get_account", + "create_models", + "list_models", + "get_models", + "search_models", + "list_models_examples", + "create_models_predictions", + "get_models_readme", + "list_models_versions", + "get_models_versions", + "create_predictions", + "list_predictions", + "cancel_predictions", + "get_predictions", + "create_trainings", + "list_trainings", + "cancel_trainings", + "get_trainings", + "get_default_webhooks_secret" + ] + } + } + } + } +} diff --git a/registries/toolhive/servers/semgrep-remote/server.json b/registries/toolhive/servers/semgrep-remote/server.json new file mode 100644 index 00000000..ff86a932 --- /dev/null +++ b/registries/toolhive/servers/semgrep-remote/server.json @@ -0,0 +1,56 @@ +{ + "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json", + "name": "io.github.stacklok/semgrep-remote", + "description": "Official Semgrep MCP server for code security scanning and vulnerability detection", + "title": "semgrep-remote", + "repository": { + "url": "https://github.com/semgrep/mcp", + "source": "github" + }, + "version": "1.0.0", + "remotes": [ + { + "type": "streamable-http", + "url": "https://mcp.semgrep.ai/mcp" + } + ], + "_meta": { + "io.modelcontextprotocol.registry/publisher-provided": { + "io.github.stacklok": { + "https://mcp.semgrep.ai/mcp": { + "custom_metadata": { + "author": "Semgrep", + "homepage": "https://mcp.semgrep.ai", + "license": "MIT" + }, + "metadata": { + "last_updated": "2026-02-08T03:02:26Z", + "stars": 636 + }, + "status": "Active", + "tags": [ + "remote", + "security", + "semgrep", + "static-analysis", + "vulnerability-detection", + "code-scanning", + "security-audit" + ], + "tier": "Official", + "tools": [ + "semgrep_rule_schema", + "get_supported_languages", + "semgrep_findings", + "semgrep_scan_with_custom_rule", + "semgrep_scan", + "semgrep_scan_rpc", + "semgrep_scan_local", + "security_check", + "get_abstract_syntax_tree" + ] + } + } + } + } +} diff --git a/registries/toolhive/servers/semgrep/server.json b/registries/toolhive/servers/semgrep/server.json new file mode 100644 index 00000000..eaf1dfe3 --- /dev/null +++ b/registries/toolhive/servers/semgrep/server.json @@ -0,0 +1,79 @@ +{ + "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json", + "name": "io.github.stacklok/semgrep", + "description": "Scan code for security vulnerabilities using Semgrep with 5,000+ semantic analysis rules", + "title": "semgrep", + "repository": { + "url": "https://github.com/semgrep/semgrep", + "source": "github" + }, + "version": "1.0.0", + "packages": [ + { + "registryType": "oci", + "identifier": "semgrep/semgrep:latest", + "transport": { + "type": "stdio" + }, + "environmentVariables": [ + { + "description": "Token for connecting to Semgrep AppSec Platform", + "isSecret": true, + "name": "SEMGREP_APP_TOKEN" + } + ] + } + ], + "_meta": { + "io.modelcontextprotocol.registry/publisher-provided": { + "io.github.stacklok": { + "semgrep/semgrep:latest": { + "args": [ + "semgrep", + "mcp" + ], + "metadata": { + "last_updated": "2026-02-05T04:47:25Z", + "pulls": 21632974, + "stars": 14048 + }, + "permissions": { + "network": { + "outbound": { + "allow_host": [ + "semgrep.dev" + ], + "allow_port": [ + 443 + ] + } + } + }, + "status": "Active", + "tags": [ + "security", + "static-analysis", + "code-scanning", + "vulnerability-detection", + "sast", + "code-quality", + "security-scanning", + "semgrep", + "ast", + "code-analysis" + ], + "tier": "Official", + "tools": [ + "get_abstract_syntax_tree", + "get_supported_languages", + "semgrep_findings", + "semgrep_rule_schema", + "semgrep_scan", + "semgrep_scan_supply_chain", + "semgrep_scan_with_custom_rule" + ] + } + } + } + } +} diff --git a/registries/toolhive/servers/sentry-remote/server.json b/registries/toolhive/servers/sentry-remote/server.json new file mode 100644 index 00000000..6c7a038b --- /dev/null +++ b/registries/toolhive/servers/sentry-remote/server.json @@ -0,0 +1,58 @@ +{ + "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json", + "name": "io.github.stacklok/sentry-remote", + "description": "Sentry's official remote MCP server for error monitoring and performance tracking", + "title": "sentry-remote", + "version": "1.0.0", + "remotes": [ + { + "type": "streamable-http", + "url": "https://mcp.sentry.dev/mcp" + } + ], + "_meta": { + "io.modelcontextprotocol.registry/publisher-provided": { + "io.github.stacklok": { + "https://mcp.sentry.dev/mcp": { + "custom_metadata": { + "author": "Sentry", + "homepage": "https://sentry.io" + }, + "metadata": { + "last_updated": "2026-02-08T03:02:26Z" + }, + "status": "Active", + "tags": [ + "remote", + "sentry", + "error-monitoring", + "performance-tracking", + "debugging", + "oauth", + "issues", + "traces", + "projects", + "releases" + ], + "tier": "Official", + "tools": [ + "whoami", + "find_organizations", + "find_teams", + "find_projects", + "find_releases", + "get_issue_details", + "get_trace_details", + "get_event_attachment", + "search_events", + "find_dsns", + "analyze_issue_with_seer", + "search_docs", + "get_doc", + "search_issues" + ] + } + } + } + } +} diff --git a/registries/toolhive/servers/sentry/server.json b/registries/toolhive/servers/sentry/server.json new file mode 100644 index 00000000..77122998 --- /dev/null +++ b/registries/toolhive/servers/sentry/server.json @@ -0,0 +1,103 @@ +{ + "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json", + "name": "io.github.stacklok/sentry", + "description": "Sentry MCP service for human-in-the-loop coding agents and developer workflow debugging", + "title": "sentry", + "repository": { + "url": "https://github.com/getsentry/sentry-mcp", + "source": "github" + }, + "version": "1.0.0", + "packages": [ + { + "registryType": "oci", + "identifier": "ghcr.io/stacklok/dockyard/npx/sentry-mcp-server:0.29.0", + "transport": { + "type": "stdio" + }, + "environmentVariables": [ + { + "description": "Sentry user auth token with necessary scopes", + "isRequired": true, + "isSecret": true, + "name": "SENTRY_ACCESS_TOKEN" + }, + { + "description": "Sentry host URL (e.g., sentry.example.com)", + "name": "SENTRY_HOST" + }, + { + "description": "OpenAI API key for AI-powered search tools (search_events, search_issues)", + "isSecret": true, + "name": "OPENAI_API_KEY" + } + ] + } + ], + "_meta": { + "io.modelcontextprotocol.registry/publisher-provided": { + "io.github.stacklok": { + "ghcr.io/stacklok/dockyard/npx/sentry-mcp-server:0.29.0": { + "metadata": { + "last_updated": "2026-01-25T13:39:53Z", + "pulls": 127, + "stars": 519 + }, + "permissions": { + "network": { + "outbound": { + "allow_host": [ + ".sentry.io", + "sentry.io" + ], + "allow_port": [ + 443 + ] + } + } + }, + "provenance": { + "cert_issuer": "https://token.actions.githubusercontent.com", + "repository_uri": "https://github.com/stacklok/dockyard", + "runner_environment": "github-hosted", + "signer_identity": "/.github/workflows/build-containers.yml", + "sigstore_url": "tuf-repo-cdn.sigstore.dev" + }, + "status": "Active", + "tags": [ + "sentry", + "debugging", + "monitoring", + "error-tracking", + "observability" + ], + "tier": "Official", + "tools": [ + "analyze_issue_with_seer", + "create_dsn", + "create_project", + "create_team", + "find_dsns", + "find_organizations", + "find_projects", + "find_releases", + "find_teams", + "get_doc", + "get_event_attachment", + "get_issue_details", + "get_issue_tag_values", + "get_profile", + "get_trace_details", + "search_docs", + "search_events", + "search_issue_events", + "search_issues", + "update_issue", + "update_project", + "whoami" + ] + } + } + } + } +} diff --git a/registries/toolhive/servers/sequentialthinking/server.json b/registries/toolhive/servers/sequentialthinking/server.json new file mode 100644 index 00000000..55d680bb --- /dev/null +++ b/registries/toolhive/servers/sequentialthinking/server.json @@ -0,0 +1,57 @@ +{ + "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json", + "name": "io.github.stacklok/sequentialthinking", + "description": "Dynamic problem-solving with structured, reflective approach that adapts as understanding deepens", + "title": "sequentialthinking", + "repository": { + "url": "https://github.com/modelcontextprotocol/servers", + "source": "github" + }, + "version": "1.0.0", + "packages": [ + { + "registryType": "oci", + "identifier": "docker.io/mcp/sequentialthinking:latest", + "transport": { + "type": "stdio" + } + } + ], + "_meta": { + "io.modelcontextprotocol.registry/publisher-provided": { + "io.github.stacklok": { + "docker.io/mcp/sequentialthinking:latest": { + "metadata": { + "last_updated": "2026-02-05T02:58:04Z", + "pulls": 14819, + "stars": 78012 + }, + "permissions": { + "network": { + "outbound": {} + } + }, + "status": "Active", + "tags": [ + "approach", + "dynamic", + "enabling", + "problem", + "reflective", + "sequentialthinking", + "solving", + "structured", + "step-by-step", + "revision", + "branching", + "chain-of-thought" + ], + "tier": "Community", + "tools": [ + "sequentialthinking" + ] + } + } + } + } +} diff --git a/registries/toolhive/servers/slack-mcp-server/server.json b/registries/toolhive/servers/slack-mcp-server/server.json new file mode 100644 index 00000000..dd06913f --- /dev/null +++ b/registries/toolhive/servers/slack-mcp-server/server.json @@ -0,0 +1,99 @@ +{ + "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json", + "name": "io.github.stacklok/slack-mcp-server", + "description": "MCP server for Slack with channels, DMs, message history, search, and smart pagination", + "title": "slack-mcp-server", + "repository": { + "url": "https://github.com/korotovsky/slack-mcp-server", + "source": "github" + }, + "version": "1.0.0", + "packages": [ + { + "registryType": "oci", + "identifier": "ghcr.io/korotovsky/slack-mcp-server:v1.1.28", + "transport": { + "type": "sse", + "url": "http://localhost:13080" + }, + "environmentVariables": [ + { + "description": "Host address for the MCP server to bind to. Required for container accessibility.", + "default": "0.0.0.0", + "name": "SLACK_MCP_HOST" + }, + { + "description": "User OAuth token (xoxp-...) for Slack API access. Recommended authentication method.", + "isSecret": true, + "name": "SLACK_MCP_XOXP_TOKEN" + }, + { + "description": "Bot token (xoxb-...) for Slack API access. Bot has limited access (invited channels only, no search).", + "isSecret": true, + "name": "SLACK_MCP_XOXB_TOKEN" + }, + { + "description": "Browser session token (xoxc-...). Requires SLACK_MCP_XOXD_TOKEN as well.", + "isSecret": true, + "name": "SLACK_MCP_XOXC_TOKEN" + }, + { + "description": "Browser cookie d value (xoxd-...). Used with SLACK_MCP_XOXC_TOKEN.", + "isSecret": true, + "name": "SLACK_MCP_XOXD_TOKEN" + }, + { + "description": "Enable message posting. Set to 'true' for all channels, or comma-separated channel IDs to whitelist.", + "name": "SLACK_MCP_ADD_MESSAGE_TOOL" + }, + { + "description": "Log level (debug, info, warn, error, panic, fatal). Default is 'info'.", + "name": "SLACK_MCP_LOG_LEVEL" + } + ] + } + ], + "_meta": { + "io.modelcontextprotocol.registry/publisher-provided": { + "io.github.stacklok": { + "ghcr.io/korotovsky/slack-mcp-server:v1.1.28": { + "metadata": { + "last_updated": "2026-02-05T04:47:26Z", + "stars": 1279 + }, + "permissions": { + "network": { + "outbound": { + "allow_host": [ + ".slack.com", + ".slack-edge.com" + ], + "allow_port": [ + 443 + ] + } + } + }, + "status": "Active", + "tags": [ + "slack", + "messaging", + "channels", + "search", + "history", + "communication", + "workspace" + ], + "tier": "Community", + "tools": [ + "conversations_history", + "conversations_replies", + "conversations_add_message", + "conversations_search_messages", + "channels_list" + ] + } + } + } + } +} diff --git a/registries/toolhive/servers/sonarqube/server.json b/registries/toolhive/servers/sonarqube/server.json new file mode 100644 index 00000000..9a085132 --- /dev/null +++ b/registries/toolhive/servers/sonarqube/server.json @@ -0,0 +1,110 @@ +{ + "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json", + "name": "io.github.stacklok/sonarqube", + "description": "Integration with SonarQube Server or Cloud for code quality and security analysis.", + "title": "sonarqube", + "repository": { + "url": "https://github.com/SonarSource/sonarqube-mcp-server", + "source": "github" + }, + "version": "1.0.0", + "packages": [ + { + "registryType": "oci", + "identifier": "docker.io/mcp/sonarqube:latest", + "transport": { + "type": "stdio" + }, + "environmentVariables": [ + { + "description": "SonarQube user auth token with necessary scopes", + "isRequired": true, + "isSecret": true, + "name": "SONARQUBE_TOKEN" + }, + { + "description": "SonarQube Server base URL (required for self-hosted instances)", + "name": "SONARQUBE_URL" + }, + { + "description": "SonarQube Cloud organization key (required for SonarQube Cloud)", + "name": "SONARQUBE_ORG" + }, + { + "description": "Comma-separated list of toolsets to enable (e.g., analysis,issues,quality-gates - see docs)", + "name": "SONARQUBE_TOOLSETS" + }, + { + "description": "Set to \"true\" to disable all write operations", + "name": "SONARQUBE_READ_ONLY" + } + ] + } + ], + "_meta": { + "io.modelcontextprotocol.registry/publisher-provided": { + "io.github.stacklok": { + "docker.io/mcp/sonarqube:latest": { + "metadata": { + "last_updated": "2026-02-09T09:15:25Z", + "pulls": 93353, + "stars": 374 + }, + "permissions": { + "network": { + "outbound": { + "allow_port": [ + 443 + ], + "insecure_allow_all": true + } + } + }, + "status": "Active", + "tags": [ + "sonarqube", + "code-quality", + "security", + "analysis", + "quality-gates", + "sast", + "sca", + "software-composition-analysis", + "secrets-detection", + "iac-scanning", + "code-coverage", + "code-smells" + ], + "tier": "Official", + "tools": [ + "analyze_code_snippet", + "analyze_file_list", + "change_sonar_issue_status", + "create_webhook", + "get_component_measures", + "get_project_quality_gate_status", + "get_raw_source", + "get_scm_info", + "get_system_health", + "get_system_info", + "get_system_logs", + "get_system_status", + "list_enterprises", + "list_languages", + "list_portfolios", + "list_quality_gates", + "list_rule_repositories", + "list_webhooks", + "ping_system", + "search_dependency_risks", + "search_metrics", + "search_my_sonarqube_projects", + "search_sonar_issues_in_projects", + "show_rule", + "toggle_automatic_analysis" + ] + } + } + } + } +} diff --git a/registries/toolhive/servers/sqlite/server.json b/registries/toolhive/servers/sqlite/server.json new file mode 100644 index 00000000..a943dadf --- /dev/null +++ b/registries/toolhive/servers/sqlite/server.json @@ -0,0 +1,54 @@ +{ + "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json", + "name": "io.github.stacklok/sqlite", + "description": "Provides tools and resources for querying SQLite databases.", + "title": "sqlite", + "repository": { + "url": "https://github.com/StacklokLabs/sqlite-mcp", + "source": "github" + }, + "version": "1.0.0", + "packages": [ + { + "registryType": "oci", + "identifier": "ghcr.io/stackloklabs/sqlite-mcp/server:0.1.0", + "transport": { + "type": "streamable-http", + "url": "http://localhost:8080" + } + } + ], + "_meta": { + "io.modelcontextprotocol.registry/publisher-provided": { + "io.github.stacklok": { + "ghcr.io/stackloklabs/sqlite-mcp/server:0.1.0": { + "metadata": { + "last_updated": "2026-01-25T13:39:53Z", + "pulls": 4212, + "stars": 12 + }, + "permissions": { + "network": { + "outbound": {} + } + }, + "status": "Active", + "tags": [ + "data", + "database", + "query", + "sql", + "sqlite" + ], + "tier": "Community", + "tools": [ + "execute_query", + "execute_statement", + "list_tables", + "describe_table" + ] + } + } + } + } +} diff --git a/registries/toolhive/servers/square/server.json b/registries/toolhive/servers/square/server.json new file mode 100644 index 00000000..bcaa020d --- /dev/null +++ b/registries/toolhive/servers/square/server.json @@ -0,0 +1,52 @@ +{ + "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json", + "name": "io.github.stacklok/square", + "description": "Square's official remote MCP server for payment processing and commerce", + "title": "square", + "repository": { + "url": "https://github.com/square/square-mcp-server", + "source": "github" + }, + "version": "1.0.0", + "remotes": [ + { + "type": "sse", + "url": "https://mcp.squareup.com/sse" + } + ], + "_meta": { + "io.modelcontextprotocol.registry/publisher-provided": { + "io.github.stacklok": { + "https://mcp.squareup.com/sse": { + "custom_metadata": { + "author": "Square", + "homepage": "https://developer.squareup.com/docs/mcp" + }, + "metadata": { + "last_updated": "2026-02-09T03:01:52Z", + "stars": 91 + }, + "status": "Active", + "tags": [ + "remote", + "square", + "payments", + "ecommerce", + "oauth", + "commerce", + "customers", + "orders", + "items", + "beta" + ], + "tier": "Official", + "tools": [ + "make_api_request", + "get_type_info", + "get_service_info" + ] + } + } + } + } +} diff --git a/registries/toolhive/servers/stripe-remote/server.json b/registries/toolhive/servers/stripe-remote/server.json new file mode 100644 index 00000000..4a4ef975 --- /dev/null +++ b/registries/toolhive/servers/stripe-remote/server.json @@ -0,0 +1,67 @@ +{ + "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json", + "name": "io.github.stacklok/stripe-remote", + "description": "Stripe's official remote MCP server for payment processing and subscriptions", + "title": "stripe-remote", + "version": "1.0.0", + "remotes": [ + { + "type": "streamable-http", + "url": "https://mcp.stripe.com" + } + ], + "_meta": { + "io.modelcontextprotocol.registry/publisher-provided": { + "io.github.stacklok": { + "https://mcp.stripe.com": { + "custom_metadata": { + "author": "Stripe", + "homepage": "https://docs.stripe.com/mcp" + }, + "metadata": { + "last_updated": "2026-02-09T03:01:52Z" + }, + "status": "Active", + "tags": [ + "remote", + "payments", + "ecommerce", + "financial", + "stripe", + "subscriptions", + "invoices", + "customers" + ], + "tier": "Official", + "tools": [ + "get_stripe_account_info", + "retrieve_balance", + "create_coupon", + "list_coupons", + "create_customer", + "list_customers", + "list_disputes", + "update_dispute", + "create_invoice", + "create_invoice_item", + "finalise_invoice", + "list_invoices", + "create_payment_link", + "list_payment_intents", + "create_price", + "list_prices", + "create_product", + "list_products", + "create_refund", + "cancel_subscription", + "list_subscriptions", + "update_subscription", + "search_stripe_resources", + "fetch_stripe_resources", + "search_stripe_documentation" + ] + } + } + } + } +} diff --git a/registries/toolhive/servers/stripe/server.json b/registries/toolhive/servers/stripe/server.json new file mode 100644 index 00000000..b1bf10ba --- /dev/null +++ b/registries/toolhive/servers/stripe/server.json @@ -0,0 +1,88 @@ +{ + "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json", + "name": "io.github.stacklok/stripe", + "description": "Allows you to integrate with Stripe APIs through the Stripe Agent Toolkit.", + "title": "stripe", + "repository": { + "url": "https://github.com/stripe/agent-toolkit", + "source": "github" + }, + "version": "1.0.0", + "packages": [ + { + "registryType": "oci", + "identifier": "docker.io/mcp/stripe:latest", + "transport": { + "type": "stdio" + }, + "environmentVariables": [ + { + "description": "Your Stripe secret API key, available in the Stripe Dashboard.", + "isRequired": true, + "name": "STRIPE_SECRET_KEY" + } + ] + } + ], + "_meta": { + "io.modelcontextprotocol.registry/publisher-provided": { + "io.github.stacklok": { + "docker.io/mcp/stripe:latest": { + "args": [ + "--tools=all" + ], + "metadata": { + "last_updated": "2026-02-09T09:15:25Z", + "pulls": 18407, + "stars": 1258 + }, + "permissions": { + "network": { + "outbound": { + "allow_host": [ + "api.stripe.com", + "docs.stripe.com" + ], + "allow_port": [ + 443 + ] + } + } + }, + "status": "Active", + "tags": [ + "stripe", + "agent-toolkit", + "payments", + "mcp" + ], + "tier": "Official", + "tools": [ + "cancel_subscription", + "create_coupon", + "create_customer", + "create_invoice", + "create_invoice_item", + "create_payment_link", + "create_price", + "create_product", + "create_refund", + "finalize_invoice", + "list_coupons", + "list_customers", + "list_disputes", + "list_invoices", + "list_payment_intents", + "list_prices", + "list_products", + "list_subscriptions", + "retrieve_balance", + "search_stripe_documentation", + "update_dispute", + "update_subscription" + ] + } + } + } + } +} diff --git a/registries/toolhive/servers/supabase/server.json b/registries/toolhive/servers/supabase/server.json new file mode 100644 index 00000000..192c861c --- /dev/null +++ b/registries/toolhive/servers/supabase/server.json @@ -0,0 +1,101 @@ +{ + "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json", + "name": "io.github.stacklok/supabase", + "description": "Connect Supabase projects to AI assistants for table management, config, and data querying", + "title": "supabase", + "repository": { + "url": "https://github.com/supabase-community/supabase-mcp", + "source": "github" + }, + "version": "1.0.0", + "packages": [ + { + "registryType": "oci", + "identifier": "ghcr.io/stacklok/dockyard/npx/supabase-mcp-server:0.6.3", + "transport": { + "type": "stdio" + }, + "environmentVariables": [ + { + "description": "Personal access token from Supabase dashboard", + "isRequired": true, + "isSecret": true, + "name": "SUPABASE_ACCESS_TOKEN" + } + ] + } + ], + "_meta": { + "io.modelcontextprotocol.registry/publisher-provided": { + "io.github.stacklok": { + "ghcr.io/stacklok/dockyard/npx/supabase-mcp-server:0.6.3": { + "metadata": { + "last_updated": "2026-01-26T02:48:40Z", + "pulls": 102, + "stars": 2421 + }, + "permissions": { + "network": { + "outbound": { + "allow_host": [ + ".supabase.co", + ".supabase.com" + ], + "allow_port": [ + 443 + ] + } + } + }, + "provenance": { + "cert_issuer": "https://token.actions.githubusercontent.com", + "repository_uri": "https://github.com/stacklok/dockyard", + "runner_environment": "github-hosted", + "signer_identity": "/.github/workflows/build-containers.yml", + "sigstore_url": "tuf-repo-cdn.sigstore.dev" + }, + "status": "Active", + "tags": [ + "supabase", + "database", + "backend", + "baas", + "postgresql" + ], + "tier": "Official", + "tools": [ + "apply_migration", + "confirm_cost", + "create_branch", + "create_project", + "delete_branch", + "deploy_edge_function", + "execute_sql", + "generate_typescript_types", + "get_advisors", + "get_cost", + "get_edge_function", + "get_logs", + "get_organization", + "get_project", + "get_project_url", + "get_publishable_keys", + "list_branches", + "list_edge_functions", + "list_extensions", + "list_migrations", + "list_organizations", + "list_projects", + "list_tables", + "merge_branch", + "pause_project", + "rebase_branch", + "reset_branch", + "restore_project", + "search_docs" + ] + } + } + } + } +} diff --git a/registries/toolhive/servers/tavily-mcp/server.json b/registries/toolhive/servers/tavily-mcp/server.json new file mode 100644 index 00000000..1c6172f4 --- /dev/null +++ b/registries/toolhive/servers/tavily-mcp/server.json @@ -0,0 +1,80 @@ +{ + "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json", + "name": "io.github.stacklok/tavily-mcp", + "description": "MCP server for advanced web search using Tavily's AI search engine", + "title": "tavily-mcp", + "repository": { + "url": "https://github.com/tavily-ai/tavily-mcp", + "source": "github" + }, + "version": "1.0.0", + "packages": [ + { + "registryType": "oci", + "identifier": "ghcr.io/stacklok/dockyard/npx/tavily-mcp:0.2.16", + "transport": { + "type": "stdio" + }, + "environmentVariables": [ + { + "description": "API key for Tavily search service", + "isRequired": true, + "isSecret": true, + "name": "TAVILY_API_KEY" + } + ] + } + ], + "_meta": { + "io.modelcontextprotocol.registry/publisher-provided": { + "io.github.stacklok": { + "ghcr.io/stacklok/dockyard/npx/tavily-mcp:0.2.16": { + "metadata": { + "last_updated": "2026-01-25T13:39:52Z", + "pulls": 80, + "stars": 1119 + }, + "permissions": { + "network": { + "outbound": { + "allow_host": [ + "api.tavily.com", + "mcp.tavily.com" + ], + "allow_port": [ + 443, + 80 + ] + } + } + }, + "provenance": { + "cert_issuer": "https://token.actions.githubusercontent.com", + "repository_uri": "https://github.com/stacklok/dockyard", + "runner_environment": "github-hosted", + "signer_identity": "/.github/workflows/build-containers.yml", + "sigstore_url": "tuf-repo-cdn.sigstore.dev" + }, + "status": "Active", + "tags": [ + "search", + "web-search", + "ai-search", + "crawl", + "extract", + "api", + "real-time" + ], + "tier": "Official", + "tools": [ + "tavily_crawl", + "tavily_extract", + "tavily_map", + "tavily_research", + "tavily_search" + ] + } + } + } + } +} diff --git a/registries/toolhive/servers/terraform/server.json b/registries/toolhive/servers/terraform/server.json new file mode 100644 index 00000000..d84c24ff --- /dev/null +++ b/registries/toolhive/servers/terraform/server.json @@ -0,0 +1,130 @@ +{ + "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json", + "name": "io.github.stacklok/terraform", + "description": "Terraform registry and Cloud/Enterprise integration for providers, modules, policies, and workspaces", + "title": "terraform", + "repository": { + "url": "https://github.com/hashicorp/terraform-mcp-server", + "source": "github" + }, + "version": "1.0.0", + "packages": [ + { + "registryType": "oci", + "identifier": "docker.io/hashicorp/terraform-mcp-server:0.4.0", + "transport": { + "type": "stdio" + }, + "environmentVariables": [ + { + "description": "API authentication token for Terraform Cloud/Enterprise access", + "isSecret": true, + "name": "TFE_TOKEN" + }, + { + "description": "HCP Terraform/Enterprise endpoint URL", + "default": "https://app.terraform.io", + "name": "TFE_ADDRESS" + }, + { + "description": "Enable destructive operations (use cautiously in production)", + "default": "false", + "name": "ENABLE_TF_OPERATIONS" + }, + { + "description": "Bypass TLS certificate validation (development only)", + "default": "false", + "name": "TFE_SKIP_TLS_VERIFY" + }, + { + "description": "Protocol selection (stdio or streamable-http)", + "default": "stdio", + "name": "TRANSPORT_MODE" + } + ] + } + ], + "_meta": { + "io.modelcontextprotocol.registry/publisher-provided": { + "io.github.stacklok": { + "docker.io/hashicorp/terraform-mcp-server:0.4.0": { + "metadata": { + "last_updated": "2026-02-05T04:47:27Z", + "pulls": 9600, + "stars": 1206 + }, + "permissions": { + "network": { + "outbound": { + "allow_host": [ + "registry.terraform.io", + "app.terraform.io" + ], + "allow_port": [ + 443 + ] + } + } + }, + "status": "Active", + "tags": [ + "terraform", + "iac", + "infrastructure", + "automation", + "providers", + "modules", + "hcl", + "workspace", + "variables", + "governance", + "policy", + "registry" + ], + "tier": "Official", + "tools": [ + "attach_policy_set_to_workspaces", + "attach_variable_set_to_workspaces", + "create_no_code_workspace", + "create_run", + "create_variable_in_variable_set", + "create_variable_set", + "create_workspace", + "create_workspace_tags", + "create_workspace_variable", + "delete_variable_in_variable_set", + "detach_variable_set_from_workspaces", + "get_latest_module_version", + "get_latest_provider_version", + "get_module_details", + "get_policy_details", + "get_private_module_details", + "get_private_provider_details", + "get_provider_capabilities", + "get_provider_details", + "get_run_details", + "get_stack_details", + "get_token_permissions", + "get_workspace_details", + "list_runs", + "list_stacks", + "list_terraform_orgs", + "list_terraform_projects", + "list_variable_sets", + "list_workspace_policy_sets", + "list_workspace_variables", + "list_workspaces", + "read_workspace_tags", + "search_modules", + "search_policies", + "search_private_modules", + "search_private_providers", + "search_providers", + "update_workspace", + "update_workspace_variable" + ] + } + } + } + } +} diff --git a/registries/toolhive/servers/time/server.json b/registries/toolhive/servers/time/server.json new file mode 100644 index 00000000..666d7978 --- /dev/null +++ b/registries/toolhive/servers/time/server.json @@ -0,0 +1,56 @@ +{ + "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json", + "name": "io.github.stacklok/time", + "description": "Provides time information and IANA timezone conversions with auto system timezone detection.", + "title": "time", + "repository": { + "url": "https://github.com/modelcontextprotocol/servers", + "source": "github" + }, + "version": "1.0.0", + "packages": [ + { + "registryType": "oci", + "identifier": "docker.io/mcp/time:latest", + "transport": { + "type": "stdio" + } + } + ], + "_meta": { + "io.modelcontextprotocol.registry/publisher-provided": { + "io.github.stacklok": { + "docker.io/mcp/time:latest": { + "metadata": { + "last_updated": "2026-01-28T02:42:36Z", + "pulls": 10986, + "stars": 77357 + }, + "permissions": { + "network": { + "outbound": {} + } + }, + "status": "Active", + "tags": [ + "auto", + "available", + "configuration", + "conversions", + "convert_time", + "customization", + "details", + "detection", + "example", + "examples" + ], + "tier": "Community", + "tools": [ + "get_current_time", + "convert_time" + ] + } + } + } + } +} diff --git a/registries/toolhive/servers/toolhive-doc-mcp-remote/server.json b/registries/toolhive/servers/toolhive-doc-mcp-remote/server.json new file mode 100644 index 00000000..499e9242 --- /dev/null +++ b/registries/toolhive/servers/toolhive-doc-mcp-remote/server.json @@ -0,0 +1,50 @@ +{ + "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json", + "name": "io.github.stacklok/toolhive-doc-mcp-remote", + "description": "Search ToolHive docs for help with using and contributing to the project (hosted version)", + "title": "toolhive-doc-mcp-remote", + "repository": { + "url": "https://github.com/StacklokLabs/toolhive-doc-mcp", + "source": "github" + }, + "version": "1.0.0", + "remotes": [ + { + "type": "streamable-http", + "url": "https://toolhive-doc-mcp.stacklok.dev/mcp" + } + ], + "_meta": { + "io.modelcontextprotocol.registry/publisher-provided": { + "io.github.stacklok": { + "https://toolhive-doc-mcp.stacklok.dev/mcp": { + "custom_metadata": { + "author": "Stacklok", + "homepage": "https://github.com/StacklokLabs/toolhive-doc-mcp", + "license": "Apache-2.0" + }, + "metadata": { + "last_updated": "2026-02-14T02:56:20Z", + "stars": 3 + }, + "status": "Active", + "tags": [ + "remote", + "docs", + "documentation", + "help", + "search", + "stacklok", + "support", + "toolhive" + ], + "tier": "Official", + "tools": [ + "query_docs", + "get_chunk" + ] + } + } + } + } +} diff --git a/registries/toolhive/servers/toolhive-doc-mcp/server.json b/registries/toolhive/servers/toolhive-doc-mcp/server.json new file mode 100644 index 00000000..79e3db5c --- /dev/null +++ b/registries/toolhive/servers/toolhive-doc-mcp/server.json @@ -0,0 +1,70 @@ +{ + "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json", + "name": "io.github.stacklok/toolhive-doc-mcp", + "description": "Search ToolHive docs for help with using and contributing to the project (local version)", + "title": "toolhive-doc-mcp", + "repository": { + "url": "https://github.com/StacklokLabs/toolhive-doc-mcp", + "source": "github" + }, + "version": "1.0.0", + "packages": [ + { + "registryType": "oci", + "identifier": "ghcr.io/stackloklabs/toolhive-doc-mcp:0.0.9", + "transport": { + "type": "streamable-http", + "url": "http://localhost:8080" + }, + "environmentVariables": [ + { + "description": "Enable/disable OpenTelemetry logging", + "default": "false", + "name": "OTEL_ENABLED" + }, + { + "description": "OpenTelemetry collector endpoint", + "name": "OTEL_ENDPOINT" + }, + { + "description": "Service name for telemetry", + "default": "toolhive-doc-mcp", + "name": "OTEL_SERVICE_NAME" + }, + { + "description": "Service version for telemetry", + "default": "1.0.0", + "name": "OTEL_SERVICE_VERSION" + } + ] + } + ], + "_meta": { + "io.modelcontextprotocol.registry/publisher-provided": { + "io.github.stacklok": { + "ghcr.io/stackloklabs/toolhive-doc-mcp:0.0.9": { + "metadata": { + "last_updated": "2026-02-16T03:01:21Z", + "stars": 3 + }, + "status": "Active", + "tags": [ + "docs", + "documentation", + "help", + "knowledge-base", + "search", + "stacklok", + "support", + "toolhive" + ], + "tier": "Official", + "tools": [ + "query_docs", + "get_chunk" + ] + } + } + } + } +} diff --git a/registries/toolhive/servers/vercel/server.json b/registries/toolhive/servers/vercel/server.json new file mode 100644 index 00000000..bd70161e --- /dev/null +++ b/registries/toolhive/servers/vercel/server.json @@ -0,0 +1,53 @@ +{ + "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json", + "name": "io.github.stacklok/vercel", + "description": "Vercel's official remote MCP server for deployment platform and project management", + "title": "vercel", + "version": "1.0.0", + "remotes": [ + { + "type": "streamable-http", + "url": "https://mcp.vercel.com" + } + ], + "_meta": { + "io.modelcontextprotocol.registry/publisher-provided": { + "io.github.stacklok": { + "https://mcp.vercel.com": { + "custom_metadata": { + "author": "Vercel", + "homepage": "https://vercel.com/docs/mcp/vercel-mcp" + }, + "metadata": { + "last_updated": "2026-02-09T03:01:53Z" + }, + "status": "Active", + "tags": [ + "remote", + "vercel", + "deployment", + "hosting", + "oauth", + "web-development", + "serverless", + "domains", + "environment-variables", + "projects" + ], + "tier": "Official", + "tools": [ + "search_vercel_documentation", + "list_teams", + "list_projects", + "get_project", + "list_deployments", + "get_deployment", + "get_deployment_events", + "get_access_to_vercel_url", + "web_fetch_vercel_url" + ] + } + } + } + } +} diff --git a/registries/toolhive/servers/wix/server.json b/registries/toolhive/servers/wix/server.json new file mode 100644 index 00000000..69af88d7 --- /dev/null +++ b/registries/toolhive/servers/wix/server.json @@ -0,0 +1,56 @@ +{ + "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json", + "name": "io.github.stacklok/wix", + "description": "Wix's official remote MCP server for development platform and site management", + "title": "wix", + "version": "1.0.0", + "remotes": [ + { + "type": "streamable-http", + "url": "https://mcp.wix.com/mcp" + } + ], + "_meta": { + "io.modelcontextprotocol.registry/publisher-provided": { + "io.github.stacklok": { + "https://mcp.wix.com/mcp": { + "custom_metadata": { + "author": "Wix", + "homepage": "https://dev.wix.com" + }, + "metadata": { + "last_updated": "2026-02-13T03:02:08Z" + }, + "status": "Active", + "tags": [ + "remote", + "wix", + "website-builder", + "documentation", + "sdk", + "headless", + "site-management", + "api", + "development", + "build-apps" + ], + "tier": "Official", + "tools": [ + "WixREADME", + "SearchWixWDSDocumentation", + "SearchWixRESTDocumentation", + "SearchWixSDKDocumentation", + "SearchBuildAppsDocumentation", + "SearchWixHeadlessDocumentation", + "ReadFullDocsArticle", + "ReadFullDocsMethodSchema", + "CallWixSiteAPI", + "ListWixSites", + "ManageWixSite", + "SupportAndFeedback" + ] + } + } + } + } +} diff --git a/registry/playwright/spec.yaml b/registry/playwright/spec.yaml index 195641f9..9294efa1 100644 --- a/registry/playwright/spec.yaml +++ b/registry/playwright/spec.yaml @@ -47,7 +47,6 @@ tags: - web - accessibility image: mcr.microsoft.com/playwright/mcp:v0.0.68 -target_port: 8931 permissions: network: outbound: diff --git a/registry/semgrep/spec.yaml b/registry/semgrep/spec.yaml index 3dc4653c..e890f7de 100644 --- a/registry/semgrep/spec.yaml +++ b/registry/semgrep/spec.yaml @@ -24,7 +24,6 @@ metadata: pulls: 21632974 last_updated: "2026-02-05T04:47:25Z" repository_url: https://github.com/semgrep/semgrep -target_port: 8000 tags: - security - static-analysis