diff --git a/.github/workflows/verify-docgen.yml b/.github/workflows/verify-docgen.yml index 0327839..ac00f80 100644 --- a/.github/workflows/verify-docgen.yml +++ b/.github/workflows/verify-docgen.yml @@ -12,9 +12,14 @@ jobs: steps: - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 + - name: Install Task + uses: arduino/setup-task@v2 + with: + version: 3.44.1 + repo-token: ${{ secrets.GITHUB_TOKEN }} - uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6 with: go-version-file: go.mod - name: Install swag - run: go install github.com/swaggo/swag/v2/cmd/swag@latest + run: task install-swagger - run: ./cmd/help/verify.sh diff --git a/.swaggo b/.swaggo new file mode 100644 index 0000000..1c65579 --- /dev/null +++ b/.swaggo @@ -0,0 +1,6 @@ +// replace internal/api/v0.EnvVarDetail EnvVarDetail +// replace internal/api/v0.ErrorResponse ErrorResponse +// replace internal/api/v0.ListServersResponse ListServersResponse +// replace internal/api/v0.RegistryInfoResponse RegistryInfoResponse +// replace internal/api/v0.ServerDetailResponse ServerDetailResponse +// replace internal/api/v0.ServerSummaryResponse ServerSummaryResponse diff --git a/Taskfile.yml b/Taskfile.yml index 4d04a87..4519d89 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -1,17 +1,20 @@ version: '3' -# includes: -# registry-api: -# taskfile: ./cmd/thv-registry-api/Taskfile.yml -# flatten: true - tasks: docs: desc: Regenerate the registry API documentation + vars: + SWAG_OUTPUT_DIR: '{{.SWAG_OUTPUT_DIR | default "docs/thv-registry-api"}}' cmds: - rm -rf docs/cli/* - go run cmd/help/main.go --dir docs/cli - - swag init -g cmd/thv-registry-api/docs.go --v3.1 -o docs/thv-registry-api + # NOTE: check out .swaggo file in the root of the project + # NOTE: We suppress the output to avoid "go/build" messages, it might + # drop messages we care about. + - | + swag init -q -g cmd/thv-registry-api/docs.go \ + --v3.1 --output {{.SWAG_OUTPUT_DIR}} \ + --parseDependencyLevel 1 2>/dev/null install-swagger: desc: Install the swag tool for OpenAPI/Swagger generation diff --git a/cmd/help/verify.sh b/cmd/help/verify.sh index 30ff9d3..0c6dcee 100755 --- a/cmd/help/verify.sh +++ b/cmd/help/verify.sh @@ -9,7 +9,7 @@ diff -Naur -I "^ date:" "$tmpdir" docs/cli/ # Generate API docs in temp directory that mimics the final structure api_tmpdir=$(mktemp -d) mkdir -p "$api_tmpdir/docs/thv-registry-api" -swag init -g cmd/thv-registry-api/docs.go --v3.1 -o "$api_tmpdir/docs/thv-registry-api" +task docs SWAG_OUTPUT_DIR="$api_tmpdir/docs/thv-registry-api" # Exclude README.md from diff as it's manually maintained diff -Naur --exclude="README.md" "$api_tmpdir/docs/thv-registry-api" docs/thv-registry-api/ diff --git a/docs/thv-registry-api/docs.go b/docs/thv-registry-api/docs.go index d85dfca..acde4fc 100644 --- a/docs/thv-registry-api/docs.go +++ b/docs/thv-registry-api/docs.go @@ -6,10 +6,10 @@ import "github.com/swaggo/swag/v2" const docTemplate = `{ "schemes": {{ marshal .Schemes }}, - "components": {"schemas":{"service.DeployedServer":{"properties":{"endpoint_url":{"type":"string"},"image":{"type":"string"},"name":{"type":"string"},"namespace":{"type":"string"},"ready":{"type":"boolean"},"status":{"type":"string"},"transport":{"type":"string"}},"type":"object"},"v0.EnvVarDetail":{"properties":{"default":{"type":"string"},"description":{"type":"string"},"name":{"type":"string"},"required":{"type":"boolean"},"secret":{"type":"boolean"}},"type":"object"},"v0.ErrorResponse":{"properties":{"error":{"type":"string"}},"type":"object"},"v0.ListServersResponse":{"properties":{"servers":{"items":{"$ref":"#/components/schemas/v0.ServerSummaryResponse"},"type":"array","uniqueItems":false},"total":{"type":"integer"}},"type":"object"},"v0.RegistryInfoResponse":{"properties":{"last_updated":{"type":"string"},"source":{"type":"string"},"total_servers":{"type":"integer"},"version":{"type":"string"}},"type":"object"},"v0.ServerDetailResponse":{"properties":{"args":{"items":{"type":"string"},"type":"array","uniqueItems":false},"description":{"type":"string"},"env_vars":{"items":{"$ref":"#/components/schemas/v0.EnvVarDetail"},"type":"array","uniqueItems":false},"image":{"type":"string"},"metadata":{"additionalProperties":{},"type":"object"},"name":{"type":"string"},"permissions":{"additionalProperties":{},"type":"object"},"repository_url":{"type":"string"},"status":{"type":"string"},"tags":{"items":{"type":"string"},"type":"array","uniqueItems":false},"tier":{"type":"string"},"tools":{"items":{"type":"string"},"type":"array","uniqueItems":false},"transport":{"type":"string"},"volumes":{"additionalProperties":{},"type":"object"}},"type":"object"},"v0.ServerSummaryResponse":{"properties":{"description":{"type":"string"},"name":{"type":"string"},"status":{"type":"string"},"tier":{"type":"string"},"tools_count":{"type":"integer"},"transport":{"type":"string"}},"type":"object"}}}, + "components": {"schemas":{"github_com_stacklok_toolhive-registry-server_internal_service.DeployedServer":{"properties":{"endpoint_url":{"type":"string"},"image":{"type":"string"},"name":{"type":"string"},"namespace":{"type":"string"},"ready":{"type":"boolean"},"status":{"type":"string"},"transport":{"type":"string"}},"type":"object"},"internal_api_v0.EnvVarDetail":{"properties":{"default":{"type":"string"},"description":{"type":"string"},"name":{"type":"string"},"required":{"type":"boolean"},"secret":{"type":"boolean"}},"type":"object"},"internal_api_v0.ErrorResponse":{"properties":{"error":{"type":"string"}},"type":"object"},"internal_api_v0.ListServersResponse":{"properties":{"servers":{"items":{"$ref":"#/components/schemas/internal_api_v0.ServerSummaryResponse"},"type":"array","uniqueItems":false},"total":{"type":"integer"}},"type":"object"},"internal_api_v0.RegistryInfoResponse":{"properties":{"last_updated":{"type":"string"},"source":{"type":"string"},"total_servers":{"type":"integer"},"version":{"type":"string"}},"type":"object"},"internal_api_v0.ServerDetailResponse":{"properties":{"args":{"items":{"type":"string"},"type":"array","uniqueItems":false},"description":{"type":"string"},"env_vars":{"items":{"$ref":"#/components/schemas/internal_api_v0.EnvVarDetail"},"type":"array","uniqueItems":false},"image":{"type":"string"},"metadata":{"additionalProperties":{},"type":"object"},"name":{"type":"string"},"permissions":{"additionalProperties":{},"type":"object"},"repository_url":{"type":"string"},"status":{"type":"string"},"tags":{"items":{"type":"string"},"type":"array","uniqueItems":false},"tier":{"type":"string"},"tools":{"items":{"type":"string"},"type":"array","uniqueItems":false},"transport":{"type":"string"},"volumes":{"additionalProperties":{},"type":"object"}},"type":"object"},"internal_api_v0.ServerSummaryResponse":{"properties":{"description":{"type":"string"},"name":{"type":"string"},"status":{"type":"string"},"tier":{"type":"string"},"tools_count":{"type":"integer"},"transport":{"type":"string"}},"type":"object"},"model.Argument":{"properties":{"choices":{"items":{"type":"string"},"type":"array","uniqueItems":false},"default":{"type":"string"},"description":{"type":"string"},"format":{"$ref":"#/components/schemas/model.Format"},"isRepeated":{"type":"boolean"},"isRequired":{"type":"boolean"},"isSecret":{"type":"boolean"},"name":{"example":"--port","type":"string"},"placeholder":{"type":"string"},"type":{"$ref":"#/components/schemas/model.ArgumentType"},"value":{"type":"string"},"valueHint":{"example":"file_path","type":"string"},"variables":{"additionalProperties":{"$ref":"#/components/schemas/model.Input"},"type":"object"}},"type":"object"},"model.ArgumentType":{"example":"positional","type":"string","x-enum-varnames":["ArgumentTypePositional","ArgumentTypeNamed"]},"model.Format":{"type":"string","x-enum-varnames":["FormatString","FormatNumber","FormatBoolean","FormatFilePath"]},"model.Icon":{"properties":{"mimeType":{"example":"image/png","type":"string"},"sizes":{"items":{"type":"string"},"type":"array","uniqueItems":false},"src":{"example":"https://example.com/icon.png","format":"uri","maxLength":255,"type":"string"},"theme":{"type":"string"}},"type":"object"},"model.Input":{"properties":{"choices":{"items":{"type":"string"},"type":"array","uniqueItems":false},"default":{"type":"string"},"description":{"type":"string"},"format":{"$ref":"#/components/schemas/model.Format"},"isRequired":{"type":"boolean"},"isSecret":{"type":"boolean"},"placeholder":{"type":"string"},"value":{"type":"string"}},"type":"object"},"model.KeyValueInput":{"properties":{"choices":{"items":{"type":"string"},"type":"array","uniqueItems":false},"default":{"type":"string"},"description":{"type":"string"},"format":{"$ref":"#/components/schemas/model.Format"},"isRequired":{"type":"boolean"},"isSecret":{"type":"boolean"},"name":{"example":"SOME_VARIABLE","type":"string"},"placeholder":{"type":"string"},"value":{"type":"string"},"variables":{"additionalProperties":{"$ref":"#/components/schemas/model.Input"},"type":"object"}},"type":"object"},"model.Package":{"properties":{"environmentVariables":{"description":"EnvironmentVariables are set when running the package","items":{"$ref":"#/components/schemas/model.KeyValueInput"},"type":"array","uniqueItems":false},"fileSha256":{"description":"FileSHA256 is the SHA-256 hash for integrity verification (required for mcpb, optional for others)","example":"fe333e598595000ae021bd27117db32ec69af6987f507ba7a63c90638ff633ce","pattern":"^[a-f0-9]{64}$","type":"string"},"identifier":{"description":"Identifier is the package identifier:\n - For NPM/PyPI/NuGet: package name or ID\n - For OCI: full image reference (e.g., \"ghcr.io/owner/repo:v1.0.0\")\n - For MCPB: direct download URL","example":"@modelcontextprotocol/server-brave-search","minLength":1,"type":"string"},"packageArguments":{"description":"PackageArguments are passed to the package's binary","items":{"$ref":"#/components/schemas/model.Argument"},"type":"array","uniqueItems":false},"registryBaseUrl":{"description":"RegistryBaseURL is the base URL of the package registry (used by npm, pypi, nuget; not used by oci, mcpb)","example":"https://registry.npmjs.org","format":"uri","type":"string"},"registryType":{"description":"RegistryType indicates how to download packages (e.g., \"npm\", \"pypi\", \"oci\", \"nuget\", \"mcpb\")","example":"npm","minLength":1,"type":"string"},"runtimeArguments":{"description":"RuntimeArguments are passed to the package's runtime command (e.g., docker, npx)","items":{"$ref":"#/components/schemas/model.Argument"},"type":"array","uniqueItems":false},"runtimeHint":{"description":"RunTimeHint suggests the appropriate runtime for the package","example":"npx","type":"string"},"transport":{"$ref":"#/components/schemas/model.Transport"},"version":{"description":"Version is the package version (required for npm, pypi, nuget; optional for mcpb; not used by oci where version is in the identifier)","example":"1.0.2","minLength":1,"type":"string"}},"type":"object"},"model.Repository":{"properties":{"id":{"example":"b94b5f7e-c7c6-d760-2c78-a5e9b8a5b8c9","type":"string"},"source":{"example":"github","type":"string"},"subfolder":{"example":"src/everything","type":"string"},"url":{"example":"https://github.com/modelcontextprotocol/servers","format":"uri","type":"string"}},"type":"object"},"model.Status":{"type":"string","x-enum-varnames":["StatusActive","StatusDeprecated","StatusDeleted"]},"model.Transport":{"description":"Transport is required and specifies the transport protocol configuration","properties":{"headers":{"items":{"$ref":"#/components/schemas/model.KeyValueInput"},"type":"array","uniqueItems":false},"type":{"example":"stdio","type":"string"},"url":{"example":"https://api.example.com/mcp","type":"string"}},"type":"object"},"v0.Metadata":{"properties":{"count":{"type":"integer"},"nextCursor":{"type":"string"}},"type":"object"},"v0.RegistryExtensions":{"properties":{"isLatest":{"type":"boolean"},"publishedAt":{"format":"date-time","type":"string"},"status":{"$ref":"#/components/schemas/model.Status"},"updatedAt":{"format":"date-time","type":"string"}},"type":"object"},"v0.ResponseMeta":{"properties":{"io.modelcontextprotocol.registry/official":{"$ref":"#/components/schemas/v0.RegistryExtensions"}},"type":"object"},"v0.ServerJSON":{"properties":{"$schema":{"example":"https://static.modelcontextprotocol.io/schemas/2025-10-17/server.schema.json","format":"uri","minLength":1,"type":"string"},"_meta":{"$ref":"#/components/schemas/v0.ServerMeta"},"description":{"example":"MCP server providing weather data and forecasts via OpenWeatherMap API","maxLength":100,"minLength":1,"type":"string"},"icons":{"items":{"$ref":"#/components/schemas/model.Icon"},"type":"array","uniqueItems":false},"name":{"example":"io.github.user/weather","maxLength":200,"minLength":3,"pattern":"^[a-zA-Z0-9.-]+/[a-zA-Z0-9._-]+$","type":"string"},"packages":{"items":{"$ref":"#/components/schemas/model.Package"},"type":"array","uniqueItems":false},"remotes":{"items":{"$ref":"#/components/schemas/model.Transport"},"type":"array","uniqueItems":false},"repository":{"$ref":"#/components/schemas/model.Repository"},"title":{"example":"Weather API","maxLength":100,"minLength":1,"type":"string"},"version":{"example":"1.0.2","type":"string"},"websiteUrl":{"example":"https://modelcontextprotocol.io/examples","format":"uri","type":"string"}},"type":"object"},"v0.ServerListResponse":{"properties":{"metadata":{"$ref":"#/components/schemas/v0.Metadata"},"servers":{"items":{"$ref":"#/components/schemas/v0.ServerResponse"},"type":"array","uniqueItems":false}},"type":"object"},"v0.ServerMeta":{"properties":{"io.modelcontextprotocol.registry/publisher-provided":{"additionalProperties":{},"type":"object"}},"type":"object"},"v0.ServerResponse":{"properties":{"_meta":{"$ref":"#/components/schemas/v0.ResponseMeta"},"server":{"$ref":"#/components/schemas/v0.ServerJSON"}},"type":"object"}}}, "info": {"contact":{"url":"https://github.com/stacklok/toolhive"},"description":"{{escape .Description}}","license":{"name":"Apache 2.0","url":"http://www.apache.org/licenses/LICENSE-2.0.html"},"title":"{{.Title}}","version":"{{.Version}}"}, "externalDocs": {"description":"","url":""}, - "paths": {"/api/v0/registry/info":{"get":{"deprecated":true,"description":"Get registry metadata including version, last updated time, and total servers","parameters":[{"description":"Response format","in":"query","name":"format","schema":{"default":"toolhive","enum":["toolhive","upstream"],"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object"}}}},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/v0.RegistryInfoResponse"}}},"description":"OK"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/v0.ErrorResponse"}}},"description":"Bad Request"},"501":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/v0.ErrorResponse"}}},"description":"Not Implemented"}},"summary":"Get registry information","tags":["registry"]}},"/api/v0/registry/openapi.yaml":{"get":{"deprecated":true,"description":"Returns the OpenAPI specification for the registry API in YAML format","responses":{"200":{"content":{"application/json":{"schema":{"type":"string"}},"application/x-yaml":{"schema":{"type":"string"}}},"description":"OpenAPI specification in YAML format"}},"summary":"Get OpenAPI specification","tags":["system"]}},"/api/v0/registry/servers":{"get":{"deprecated":true,"description":"Get a list of all available MCP servers in the registry","parameters":[{"description":"Response format","in":"query","name":"format","schema":{"default":"toolhive","enum":["toolhive","upstream"],"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object"}}}},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/v0.ListServersResponse"}}},"description":"OK"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/v0.ErrorResponse"}}},"description":"Bad Request"},"501":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/v0.ErrorResponse"}}},"description":"Not Implemented"}},"summary":"List all servers","tags":["servers"]}},"/api/v0/registry/servers/deployed":{"get":{"deprecated":true,"description":"Get a list of all currently deployed MCP servers","requestBody":{"content":{"application/json":{"schema":{"type":"object"}}}},"responses":{"200":{"content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/service.DeployedServer"},"type":"array"}}},"description":"OK"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/v0.ErrorResponse"}}},"description":"Internal Server Error"}},"summary":"List deployed servers","tags":["deployed-servers"]}},"/api/v0/registry/servers/deployed/{name}":{"get":{"deprecated":true,"description":"Get all deployed MCP servers that match the specified server registry name","parameters":[{"description":"Server registry name","in":"path","name":"name","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object"}}}},"responses":{"200":{"content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/service.DeployedServer"},"type":"array"}}},"description":"OK"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/v0.ErrorResponse"}}},"description":"Bad Request"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/v0.ErrorResponse"}}},"description":"Internal Server Error"}},"summary":"Get deployed servers by registry name","tags":["deployed-servers"]}},"/api/v0/registry/servers/{name}":{"get":{"deprecated":true,"description":"Get detailed information about a specific MCP server","parameters":[{"description":"Server name","in":"path","name":"name","required":true,"schema":{"type":"string"}},{"description":"Response format","in":"query","name":"format","schema":{"default":"toolhive","enum":["toolhive","upstream"],"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object"}}}},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/v0.ServerDetailResponse"}}},"description":"OK"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/v0.ErrorResponse"}}},"description":"Bad Request"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/v0.ErrorResponse"}}},"description":"Not Found"},"501":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/v0.ErrorResponse"}}},"description":"Not Implemented"}},"summary":"Get server by name","tags":["servers"]}},"/health":{"get":{"deprecated":true,"description":"Check if the registry API is healthy","responses":{"200":{"content":{"application/json":{"schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"description":"OK"}},"summary":"Health check","tags":["system"]}},"/readiness":{"get":{"deprecated":true,"description":"Check if the registry API is ready to serve requests","responses":{"200":{"content":{"application/json":{"schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"description":"OK"},"503":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/v0.ErrorResponse"}}},"description":"Service Unavailable"}},"summary":"Readiness check","tags":["system"]}},"/registry/v0.1/publish":{"post":{"description":"Publish a server to the registry","requestBody":{"content":{"application/json":{"schema":{"type":"object"}}}},"responses":{"501":{"content":{"application/json":{"schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"description":"Not implemented"}},"summary":"Publish server","tags":["registry","official"]}},"/registry/v0.1/servers":{"get":{"description":"Get a list of available servers in the registry","requestBody":{"content":{"application/json":{"schema":{"type":"object"}}}},"responses":{"501":{"content":{"application/json":{"schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"description":"Not implemented"}},"summary":"List servers","tags":["registry","official"]}},"/registry/v0.1/servers/{serverName}/versions":{"get":{"description":"Get a list of available versions for a specific server","parameters":[{"description":"Server name","in":"path","name":"serverName","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object"}}}},"responses":{"400":{"content":{"application/json":{"schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"description":"Bad request"},"501":{"content":{"application/json":{"schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"description":"Not implemented"}},"summary":"List server versions","tags":["registry","official"]}},"/registry/v0.1/servers/{serverName}/versions/{version}":{"get":{"description":"Get details for a specific version of a server","parameters":[{"description":"Server name","in":"path","name":"serverName","required":true,"schema":{"type":"string"}},{"description":"Version identifier","in":"path","name":"version","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object"}}}},"responses":{"400":{"content":{"application/json":{"schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"description":"Bad request"},"501":{"content":{"application/json":{"schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"description":"Not implemented"}},"summary":"Get server version","tags":["registry","official"]}},"/v0/publish":{"post":{"deprecated":true,"description":"Publish a new MCP server to the registry or update an existing one","requestBody":{"content":{"application/json":{"schema":{"type":"object"}}}},"responses":{"501":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/v0.ErrorResponse"}}},"description":"Not Implemented"}},"summary":"Publish MCP server (Not Implemented)","tags":["servers"]}},"/version":{"get":{"deprecated":true,"description":"Get version information about the registry API","responses":{"200":{"content":{"application/json":{"schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"description":"OK"}},"summary":"Version information","tags":["system"]}}}, + "paths": {"/api/v0/registry/info":{"get":{"deprecated":true,"description":"Get registry metadata including version, last updated time, and total servers","parameters":[{"description":"Response format","in":"query","name":"format","schema":{"default":"toolhive","enum":["toolhive","upstream"],"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object"}}}},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/internal_api_v0.RegistryInfoResponse"}}},"description":"OK"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/internal_api_v0.ErrorResponse"}}},"description":"Bad Request"},"501":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/internal_api_v0.ErrorResponse"}}},"description":"Not Implemented"}},"summary":"Get registry information","tags":["registry"]}},"/api/v0/registry/openapi.yaml":{"get":{"deprecated":true,"description":"Returns the OpenAPI specification for the registry API in YAML format","responses":{"200":{"content":{"application/json":{"schema":{"type":"string"}},"application/x-yaml":{"schema":{"type":"string"}}},"description":"OpenAPI specification in YAML format"}},"summary":"Get OpenAPI specification","tags":["system"]}},"/api/v0/registry/servers":{"get":{"deprecated":true,"description":"Get a list of all available MCP servers in the registry","parameters":[{"description":"Response format","in":"query","name":"format","schema":{"default":"toolhive","enum":["toolhive","upstream"],"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object"}}}},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/internal_api_v0.ListServersResponse"}}},"description":"OK"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/internal_api_v0.ErrorResponse"}}},"description":"Bad Request"},"501":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/internal_api_v0.ErrorResponse"}}},"description":"Not Implemented"}},"summary":"List all servers","tags":["servers"]}},"/api/v0/registry/servers/deployed":{"get":{"deprecated":true,"description":"Get a list of all currently deployed MCP servers","requestBody":{"content":{"application/json":{"schema":{"type":"object"}}}},"responses":{"200":{"content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/github_com_stacklok_toolhive-registry-server_internal_service.DeployedServer"},"type":"array"}}},"description":"OK"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/internal_api_v0.ErrorResponse"}}},"description":"Internal Server Error"}},"summary":"List deployed servers","tags":["deployed-servers"]}},"/api/v0/registry/servers/deployed/{name}":{"get":{"deprecated":true,"description":"Get all deployed MCP servers that match the specified server registry name","parameters":[{"description":"Server registry name","in":"path","name":"name","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object"}}}},"responses":{"200":{"content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/github_com_stacklok_toolhive-registry-server_internal_service.DeployedServer"},"type":"array"}}},"description":"OK"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/internal_api_v0.ErrorResponse"}}},"description":"Bad Request"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/internal_api_v0.ErrorResponse"}}},"description":"Internal Server Error"}},"summary":"Get deployed servers by registry name","tags":["deployed-servers"]}},"/api/v0/registry/servers/{name}":{"get":{"deprecated":true,"description":"Get detailed information about a specific MCP server","parameters":[{"description":"Server name","in":"path","name":"name","required":true,"schema":{"type":"string"}},{"description":"Response format","in":"query","name":"format","schema":{"default":"toolhive","enum":["toolhive","upstream"],"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object"}}}},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/internal_api_v0.ServerDetailResponse"}}},"description":"OK"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/internal_api_v0.ErrorResponse"}}},"description":"Bad Request"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/internal_api_v0.ErrorResponse"}}},"description":"Not Found"},"501":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/internal_api_v0.ErrorResponse"}}},"description":"Not Implemented"}},"summary":"Get server by name","tags":["servers"]}},"/health":{"get":{"deprecated":true,"description":"Check if the registry API is healthy","responses":{"200":{"content":{"application/json":{"schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"description":"OK"}},"summary":"Health check","tags":["system"]}},"/readiness":{"get":{"deprecated":true,"description":"Check if the registry API is ready to serve requests","responses":{"200":{"content":{"application/json":{"schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"description":"OK"},"503":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/internal_api_v0.ErrorResponse"}}},"description":"Service Unavailable"}},"summary":"Readiness check","tags":["system"]}},"/registry/v0.1/publish":{"post":{"description":"Publish a server to the registry","requestBody":{"content":{"application/json":{"schema":{"type":"object"}}}},"responses":{"501":{"content":{"application/json":{"schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"description":"Not implemented"}},"summary":"Publish server","tags":["registry","official"]}},"/registry/v0.1/servers":{"get":{"description":"Get a list of available servers in the registry","parameters":[{"description":"Pagination cursor for retrieving next set of results","in":"query","name":"cursor","schema":{"type":"string"}},{"description":"Maximum number of items to return","in":"query","name":"limit","schema":{"type":"integer"}},{"description":"Search servers by name (substring match)","in":"query","name":"search","schema":{"type":"string"}},{"description":"Filter by version ('latest' for latest version, or an exact version like '1.2.3')","in":"query","name":"version","schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object"}}}},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/v0.ServerListResponse"}}},"description":"OK"},"400":{"content":{"application/json":{"schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"description":"Bad request"}},"summary":"List servers","tags":["registry","official"]}},"/registry/v0.1/servers/{serverName}/versions":{"get":{"description":"Returns all available versions for a specific MCP server, ordered by publication date (newest first)","parameters":[{"description":"URL-encoded server name (e.g., \\","in":"path","name":"serverName","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object"}}}},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/v0.ServerListResponse"}}},"description":"A list of all versions for the server"},"400":{"content":{"application/json":{"schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"description":"Bad request"},"404":{"content":{"application/json":{"schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"description":"Server not found"}},"summary":"List all versions of an MCP server","tags":["servers"]}},"/registry/v0.1/servers/{serverName}/versions/{version}":{"get":{"description":"Returns detailed information about a specific version of an MCP server. Use the special version ` + "`" + `latest` + "`" + ` to get the latest version.","parameters":[{"description":"URL-encoded server name (e.g., \\","in":"path","name":"serverName","required":true,"schema":{"type":"string"}},{"description":"URL-encoded version to retrieve (e.g., \\","in":"path","name":"version","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object"}}}},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/v0.ServerResponse"}}},"description":"Detailed server information"},"400":{"content":{"application/json":{"schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"description":"Bad request"},"404":{"content":{"application/json":{"schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"description":"Server or version not found"}},"summary":"Get specific MCP server version","tags":["servers"]}},"/v0/publish":{"post":{"deprecated":true,"description":"Publish a new MCP server to the registry or update an existing one","requestBody":{"content":{"application/json":{"schema":{"type":"object"}}}},"responses":{"501":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/internal_api_v0.ErrorResponse"}}},"description":"Not Implemented"}},"summary":"Publish MCP server (Not Implemented)","tags":["servers"]}},"/version":{"get":{"deprecated":true,"description":"Get version information about the registry API","responses":{"200":{"content":{"application/json":{"schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"description":"OK"}},"summary":"Version information","tags":["system"]}}}, "openapi": "3.1.0", "tags": [ {"description":"Registry metadata and information","name":"registry"}, diff --git a/docs/thv-registry-api/swagger.json b/docs/thv-registry-api/swagger.json index 0265317..2eced78 100644 --- a/docs/thv-registry-api/swagger.json +++ b/docs/thv-registry-api/swagger.json @@ -1,8 +1,8 @@ { - "components": {"schemas":{"service.DeployedServer":{"properties":{"endpoint_url":{"type":"string"},"image":{"type":"string"},"name":{"type":"string"},"namespace":{"type":"string"},"ready":{"type":"boolean"},"status":{"type":"string"},"transport":{"type":"string"}},"type":"object"},"v0.EnvVarDetail":{"properties":{"default":{"type":"string"},"description":{"type":"string"},"name":{"type":"string"},"required":{"type":"boolean"},"secret":{"type":"boolean"}},"type":"object"},"v0.ErrorResponse":{"properties":{"error":{"type":"string"}},"type":"object"},"v0.ListServersResponse":{"properties":{"servers":{"items":{"$ref":"#/components/schemas/v0.ServerSummaryResponse"},"type":"array","uniqueItems":false},"total":{"type":"integer"}},"type":"object"},"v0.RegistryInfoResponse":{"properties":{"last_updated":{"type":"string"},"source":{"type":"string"},"total_servers":{"type":"integer"},"version":{"type":"string"}},"type":"object"},"v0.ServerDetailResponse":{"properties":{"args":{"items":{"type":"string"},"type":"array","uniqueItems":false},"description":{"type":"string"},"env_vars":{"items":{"$ref":"#/components/schemas/v0.EnvVarDetail"},"type":"array","uniqueItems":false},"image":{"type":"string"},"metadata":{"additionalProperties":{},"type":"object"},"name":{"type":"string"},"permissions":{"additionalProperties":{},"type":"object"},"repository_url":{"type":"string"},"status":{"type":"string"},"tags":{"items":{"type":"string"},"type":"array","uniqueItems":false},"tier":{"type":"string"},"tools":{"items":{"type":"string"},"type":"array","uniqueItems":false},"transport":{"type":"string"},"volumes":{"additionalProperties":{},"type":"object"}},"type":"object"},"v0.ServerSummaryResponse":{"properties":{"description":{"type":"string"},"name":{"type":"string"},"status":{"type":"string"},"tier":{"type":"string"},"tools_count":{"type":"integer"},"transport":{"type":"string"}},"type":"object"}}}, + "components": {"schemas":{"github_com_stacklok_toolhive-registry-server_internal_service.DeployedServer":{"properties":{"endpoint_url":{"type":"string"},"image":{"type":"string"},"name":{"type":"string"},"namespace":{"type":"string"},"ready":{"type":"boolean"},"status":{"type":"string"},"transport":{"type":"string"}},"type":"object"},"internal_api_v0.EnvVarDetail":{"properties":{"default":{"type":"string"},"description":{"type":"string"},"name":{"type":"string"},"required":{"type":"boolean"},"secret":{"type":"boolean"}},"type":"object"},"internal_api_v0.ErrorResponse":{"properties":{"error":{"type":"string"}},"type":"object"},"internal_api_v0.ListServersResponse":{"properties":{"servers":{"items":{"$ref":"#/components/schemas/internal_api_v0.ServerSummaryResponse"},"type":"array","uniqueItems":false},"total":{"type":"integer"}},"type":"object"},"internal_api_v0.RegistryInfoResponse":{"properties":{"last_updated":{"type":"string"},"source":{"type":"string"},"total_servers":{"type":"integer"},"version":{"type":"string"}},"type":"object"},"internal_api_v0.ServerDetailResponse":{"properties":{"args":{"items":{"type":"string"},"type":"array","uniqueItems":false},"description":{"type":"string"},"env_vars":{"items":{"$ref":"#/components/schemas/internal_api_v0.EnvVarDetail"},"type":"array","uniqueItems":false},"image":{"type":"string"},"metadata":{"additionalProperties":{},"type":"object"},"name":{"type":"string"},"permissions":{"additionalProperties":{},"type":"object"},"repository_url":{"type":"string"},"status":{"type":"string"},"tags":{"items":{"type":"string"},"type":"array","uniqueItems":false},"tier":{"type":"string"},"tools":{"items":{"type":"string"},"type":"array","uniqueItems":false},"transport":{"type":"string"},"volumes":{"additionalProperties":{},"type":"object"}},"type":"object"},"internal_api_v0.ServerSummaryResponse":{"properties":{"description":{"type":"string"},"name":{"type":"string"},"status":{"type":"string"},"tier":{"type":"string"},"tools_count":{"type":"integer"},"transport":{"type":"string"}},"type":"object"},"model.Argument":{"properties":{"choices":{"items":{"type":"string"},"type":"array","uniqueItems":false},"default":{"type":"string"},"description":{"type":"string"},"format":{"$ref":"#/components/schemas/model.Format"},"isRepeated":{"type":"boolean"},"isRequired":{"type":"boolean"},"isSecret":{"type":"boolean"},"name":{"example":"--port","type":"string"},"placeholder":{"type":"string"},"type":{"$ref":"#/components/schemas/model.ArgumentType"},"value":{"type":"string"},"valueHint":{"example":"file_path","type":"string"},"variables":{"additionalProperties":{"$ref":"#/components/schemas/model.Input"},"type":"object"}},"type":"object"},"model.ArgumentType":{"example":"positional","type":"string","x-enum-varnames":["ArgumentTypePositional","ArgumentTypeNamed"]},"model.Format":{"type":"string","x-enum-varnames":["FormatString","FormatNumber","FormatBoolean","FormatFilePath"]},"model.Icon":{"properties":{"mimeType":{"example":"image/png","type":"string"},"sizes":{"items":{"type":"string"},"type":"array","uniqueItems":false},"src":{"example":"https://example.com/icon.png","format":"uri","maxLength":255,"type":"string"},"theme":{"type":"string"}},"type":"object"},"model.Input":{"properties":{"choices":{"items":{"type":"string"},"type":"array","uniqueItems":false},"default":{"type":"string"},"description":{"type":"string"},"format":{"$ref":"#/components/schemas/model.Format"},"isRequired":{"type":"boolean"},"isSecret":{"type":"boolean"},"placeholder":{"type":"string"},"value":{"type":"string"}},"type":"object"},"model.KeyValueInput":{"properties":{"choices":{"items":{"type":"string"},"type":"array","uniqueItems":false},"default":{"type":"string"},"description":{"type":"string"},"format":{"$ref":"#/components/schemas/model.Format"},"isRequired":{"type":"boolean"},"isSecret":{"type":"boolean"},"name":{"example":"SOME_VARIABLE","type":"string"},"placeholder":{"type":"string"},"value":{"type":"string"},"variables":{"additionalProperties":{"$ref":"#/components/schemas/model.Input"},"type":"object"}},"type":"object"},"model.Package":{"properties":{"environmentVariables":{"description":"EnvironmentVariables are set when running the package","items":{"$ref":"#/components/schemas/model.KeyValueInput"},"type":"array","uniqueItems":false},"fileSha256":{"description":"FileSHA256 is the SHA-256 hash for integrity verification (required for mcpb, optional for others)","example":"fe333e598595000ae021bd27117db32ec69af6987f507ba7a63c90638ff633ce","pattern":"^[a-f0-9]{64}$","type":"string"},"identifier":{"description":"Identifier is the package identifier:\n - For NPM/PyPI/NuGet: package name or ID\n - For OCI: full image reference (e.g., \"ghcr.io/owner/repo:v1.0.0\")\n - For MCPB: direct download URL","example":"@modelcontextprotocol/server-brave-search","minLength":1,"type":"string"},"packageArguments":{"description":"PackageArguments are passed to the package's binary","items":{"$ref":"#/components/schemas/model.Argument"},"type":"array","uniqueItems":false},"registryBaseUrl":{"description":"RegistryBaseURL is the base URL of the package registry (used by npm, pypi, nuget; not used by oci, mcpb)","example":"https://registry.npmjs.org","format":"uri","type":"string"},"registryType":{"description":"RegistryType indicates how to download packages (e.g., \"npm\", \"pypi\", \"oci\", \"nuget\", \"mcpb\")","example":"npm","minLength":1,"type":"string"},"runtimeArguments":{"description":"RuntimeArguments are passed to the package's runtime command (e.g., docker, npx)","items":{"$ref":"#/components/schemas/model.Argument"},"type":"array","uniqueItems":false},"runtimeHint":{"description":"RunTimeHint suggests the appropriate runtime for the package","example":"npx","type":"string"},"transport":{"$ref":"#/components/schemas/model.Transport"},"version":{"description":"Version is the package version (required for npm, pypi, nuget; optional for mcpb; not used by oci where version is in the identifier)","example":"1.0.2","minLength":1,"type":"string"}},"type":"object"},"model.Repository":{"properties":{"id":{"example":"b94b5f7e-c7c6-d760-2c78-a5e9b8a5b8c9","type":"string"},"source":{"example":"github","type":"string"},"subfolder":{"example":"src/everything","type":"string"},"url":{"example":"https://github.com/modelcontextprotocol/servers","format":"uri","type":"string"}},"type":"object"},"model.Status":{"type":"string","x-enum-varnames":["StatusActive","StatusDeprecated","StatusDeleted"]},"model.Transport":{"description":"Transport is required and specifies the transport protocol configuration","properties":{"headers":{"items":{"$ref":"#/components/schemas/model.KeyValueInput"},"type":"array","uniqueItems":false},"type":{"example":"stdio","type":"string"},"url":{"example":"https://api.example.com/mcp","type":"string"}},"type":"object"},"v0.Metadata":{"properties":{"count":{"type":"integer"},"nextCursor":{"type":"string"}},"type":"object"},"v0.RegistryExtensions":{"properties":{"isLatest":{"type":"boolean"},"publishedAt":{"format":"date-time","type":"string"},"status":{"$ref":"#/components/schemas/model.Status"},"updatedAt":{"format":"date-time","type":"string"}},"type":"object"},"v0.ResponseMeta":{"properties":{"io.modelcontextprotocol.registry/official":{"$ref":"#/components/schemas/v0.RegistryExtensions"}},"type":"object"},"v0.ServerJSON":{"properties":{"$schema":{"example":"https://static.modelcontextprotocol.io/schemas/2025-10-17/server.schema.json","format":"uri","minLength":1,"type":"string"},"_meta":{"$ref":"#/components/schemas/v0.ServerMeta"},"description":{"example":"MCP server providing weather data and forecasts via OpenWeatherMap API","maxLength":100,"minLength":1,"type":"string"},"icons":{"items":{"$ref":"#/components/schemas/model.Icon"},"type":"array","uniqueItems":false},"name":{"example":"io.github.user/weather","maxLength":200,"minLength":3,"pattern":"^[a-zA-Z0-9.-]+/[a-zA-Z0-9._-]+$","type":"string"},"packages":{"items":{"$ref":"#/components/schemas/model.Package"},"type":"array","uniqueItems":false},"remotes":{"items":{"$ref":"#/components/schemas/model.Transport"},"type":"array","uniqueItems":false},"repository":{"$ref":"#/components/schemas/model.Repository"},"title":{"example":"Weather API","maxLength":100,"minLength":1,"type":"string"},"version":{"example":"1.0.2","type":"string"},"websiteUrl":{"example":"https://modelcontextprotocol.io/examples","format":"uri","type":"string"}},"type":"object"},"v0.ServerListResponse":{"properties":{"metadata":{"$ref":"#/components/schemas/v0.Metadata"},"servers":{"items":{"$ref":"#/components/schemas/v0.ServerResponse"},"type":"array","uniqueItems":false}},"type":"object"},"v0.ServerMeta":{"properties":{"io.modelcontextprotocol.registry/publisher-provided":{"additionalProperties":{},"type":"object"}},"type":"object"},"v0.ServerResponse":{"properties":{"_meta":{"$ref":"#/components/schemas/v0.ResponseMeta"},"server":{"$ref":"#/components/schemas/v0.ServerJSON"}},"type":"object"}}}, "info": {"contact":{"url":"https://github.com/stacklok/toolhive"},"description":"API for accessing MCP server registry data and deployed server information\nThis API provides endpoints to query the MCP (Model Context Protocol) server registry,\nget information about available servers, and check the status of deployed servers.","license":{"name":"Apache 2.0","url":"http://www.apache.org/licenses/LICENSE-2.0.html"},"title":"ToolHive Registry API","version":"0.1"}, "externalDocs": {"description":"","url":""}, - "paths": {"/api/v0/registry/info":{"get":{"deprecated":true,"description":"Get registry metadata including version, last updated time, and total servers","parameters":[{"description":"Response format","in":"query","name":"format","schema":{"default":"toolhive","enum":["toolhive","upstream"],"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object"}}}},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/v0.RegistryInfoResponse"}}},"description":"OK"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/v0.ErrorResponse"}}},"description":"Bad Request"},"501":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/v0.ErrorResponse"}}},"description":"Not Implemented"}},"summary":"Get registry information","tags":["registry"]}},"/api/v0/registry/openapi.yaml":{"get":{"deprecated":true,"description":"Returns the OpenAPI specification for the registry API in YAML format","responses":{"200":{"content":{"application/json":{"schema":{"type":"string"}},"application/x-yaml":{"schema":{"type":"string"}}},"description":"OpenAPI specification in YAML format"}},"summary":"Get OpenAPI specification","tags":["system"]}},"/api/v0/registry/servers":{"get":{"deprecated":true,"description":"Get a list of all available MCP servers in the registry","parameters":[{"description":"Response format","in":"query","name":"format","schema":{"default":"toolhive","enum":["toolhive","upstream"],"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object"}}}},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/v0.ListServersResponse"}}},"description":"OK"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/v0.ErrorResponse"}}},"description":"Bad Request"},"501":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/v0.ErrorResponse"}}},"description":"Not Implemented"}},"summary":"List all servers","tags":["servers"]}},"/api/v0/registry/servers/deployed":{"get":{"deprecated":true,"description":"Get a list of all currently deployed MCP servers","requestBody":{"content":{"application/json":{"schema":{"type":"object"}}}},"responses":{"200":{"content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/service.DeployedServer"},"type":"array"}}},"description":"OK"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/v0.ErrorResponse"}}},"description":"Internal Server Error"}},"summary":"List deployed servers","tags":["deployed-servers"]}},"/api/v0/registry/servers/deployed/{name}":{"get":{"deprecated":true,"description":"Get all deployed MCP servers that match the specified server registry name","parameters":[{"description":"Server registry name","in":"path","name":"name","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object"}}}},"responses":{"200":{"content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/service.DeployedServer"},"type":"array"}}},"description":"OK"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/v0.ErrorResponse"}}},"description":"Bad Request"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/v0.ErrorResponse"}}},"description":"Internal Server Error"}},"summary":"Get deployed servers by registry name","tags":["deployed-servers"]}},"/api/v0/registry/servers/{name}":{"get":{"deprecated":true,"description":"Get detailed information about a specific MCP server","parameters":[{"description":"Server name","in":"path","name":"name","required":true,"schema":{"type":"string"}},{"description":"Response format","in":"query","name":"format","schema":{"default":"toolhive","enum":["toolhive","upstream"],"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object"}}}},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/v0.ServerDetailResponse"}}},"description":"OK"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/v0.ErrorResponse"}}},"description":"Bad Request"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/v0.ErrorResponse"}}},"description":"Not Found"},"501":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/v0.ErrorResponse"}}},"description":"Not Implemented"}},"summary":"Get server by name","tags":["servers"]}},"/health":{"get":{"deprecated":true,"description":"Check if the registry API is healthy","responses":{"200":{"content":{"application/json":{"schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"description":"OK"}},"summary":"Health check","tags":["system"]}},"/readiness":{"get":{"deprecated":true,"description":"Check if the registry API is ready to serve requests","responses":{"200":{"content":{"application/json":{"schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"description":"OK"},"503":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/v0.ErrorResponse"}}},"description":"Service Unavailable"}},"summary":"Readiness check","tags":["system"]}},"/registry/v0.1/publish":{"post":{"description":"Publish a server to the registry","requestBody":{"content":{"application/json":{"schema":{"type":"object"}}}},"responses":{"501":{"content":{"application/json":{"schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"description":"Not implemented"}},"summary":"Publish server","tags":["registry","official"]}},"/registry/v0.1/servers":{"get":{"description":"Get a list of available servers in the registry","requestBody":{"content":{"application/json":{"schema":{"type":"object"}}}},"responses":{"501":{"content":{"application/json":{"schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"description":"Not implemented"}},"summary":"List servers","tags":["registry","official"]}},"/registry/v0.1/servers/{serverName}/versions":{"get":{"description":"Get a list of available versions for a specific server","parameters":[{"description":"Server name","in":"path","name":"serverName","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object"}}}},"responses":{"400":{"content":{"application/json":{"schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"description":"Bad request"},"501":{"content":{"application/json":{"schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"description":"Not implemented"}},"summary":"List server versions","tags":["registry","official"]}},"/registry/v0.1/servers/{serverName}/versions/{version}":{"get":{"description":"Get details for a specific version of a server","parameters":[{"description":"Server name","in":"path","name":"serverName","required":true,"schema":{"type":"string"}},{"description":"Version identifier","in":"path","name":"version","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object"}}}},"responses":{"400":{"content":{"application/json":{"schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"description":"Bad request"},"501":{"content":{"application/json":{"schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"description":"Not implemented"}},"summary":"Get server version","tags":["registry","official"]}},"/v0/publish":{"post":{"deprecated":true,"description":"Publish a new MCP server to the registry or update an existing one","requestBody":{"content":{"application/json":{"schema":{"type":"object"}}}},"responses":{"501":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/v0.ErrorResponse"}}},"description":"Not Implemented"}},"summary":"Publish MCP server (Not Implemented)","tags":["servers"]}},"/version":{"get":{"deprecated":true,"description":"Get version information about the registry API","responses":{"200":{"content":{"application/json":{"schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"description":"OK"}},"summary":"Version information","tags":["system"]}}}, + "paths": {"/api/v0/registry/info":{"get":{"deprecated":true,"description":"Get registry metadata including version, last updated time, and total servers","parameters":[{"description":"Response format","in":"query","name":"format","schema":{"default":"toolhive","enum":["toolhive","upstream"],"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object"}}}},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/internal_api_v0.RegistryInfoResponse"}}},"description":"OK"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/internal_api_v0.ErrorResponse"}}},"description":"Bad Request"},"501":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/internal_api_v0.ErrorResponse"}}},"description":"Not Implemented"}},"summary":"Get registry information","tags":["registry"]}},"/api/v0/registry/openapi.yaml":{"get":{"deprecated":true,"description":"Returns the OpenAPI specification for the registry API in YAML format","responses":{"200":{"content":{"application/json":{"schema":{"type":"string"}},"application/x-yaml":{"schema":{"type":"string"}}},"description":"OpenAPI specification in YAML format"}},"summary":"Get OpenAPI specification","tags":["system"]}},"/api/v0/registry/servers":{"get":{"deprecated":true,"description":"Get a list of all available MCP servers in the registry","parameters":[{"description":"Response format","in":"query","name":"format","schema":{"default":"toolhive","enum":["toolhive","upstream"],"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object"}}}},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/internal_api_v0.ListServersResponse"}}},"description":"OK"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/internal_api_v0.ErrorResponse"}}},"description":"Bad Request"},"501":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/internal_api_v0.ErrorResponse"}}},"description":"Not Implemented"}},"summary":"List all servers","tags":["servers"]}},"/api/v0/registry/servers/deployed":{"get":{"deprecated":true,"description":"Get a list of all currently deployed MCP servers","requestBody":{"content":{"application/json":{"schema":{"type":"object"}}}},"responses":{"200":{"content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/github_com_stacklok_toolhive-registry-server_internal_service.DeployedServer"},"type":"array"}}},"description":"OK"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/internal_api_v0.ErrorResponse"}}},"description":"Internal Server Error"}},"summary":"List deployed servers","tags":["deployed-servers"]}},"/api/v0/registry/servers/deployed/{name}":{"get":{"deprecated":true,"description":"Get all deployed MCP servers that match the specified server registry name","parameters":[{"description":"Server registry name","in":"path","name":"name","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object"}}}},"responses":{"200":{"content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/github_com_stacklok_toolhive-registry-server_internal_service.DeployedServer"},"type":"array"}}},"description":"OK"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/internal_api_v0.ErrorResponse"}}},"description":"Bad Request"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/internal_api_v0.ErrorResponse"}}},"description":"Internal Server Error"}},"summary":"Get deployed servers by registry name","tags":["deployed-servers"]}},"/api/v0/registry/servers/{name}":{"get":{"deprecated":true,"description":"Get detailed information about a specific MCP server","parameters":[{"description":"Server name","in":"path","name":"name","required":true,"schema":{"type":"string"}},{"description":"Response format","in":"query","name":"format","schema":{"default":"toolhive","enum":["toolhive","upstream"],"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object"}}}},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/internal_api_v0.ServerDetailResponse"}}},"description":"OK"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/internal_api_v0.ErrorResponse"}}},"description":"Bad Request"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/internal_api_v0.ErrorResponse"}}},"description":"Not Found"},"501":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/internal_api_v0.ErrorResponse"}}},"description":"Not Implemented"}},"summary":"Get server by name","tags":["servers"]}},"/health":{"get":{"deprecated":true,"description":"Check if the registry API is healthy","responses":{"200":{"content":{"application/json":{"schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"description":"OK"}},"summary":"Health check","tags":["system"]}},"/readiness":{"get":{"deprecated":true,"description":"Check if the registry API is ready to serve requests","responses":{"200":{"content":{"application/json":{"schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"description":"OK"},"503":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/internal_api_v0.ErrorResponse"}}},"description":"Service Unavailable"}},"summary":"Readiness check","tags":["system"]}},"/registry/v0.1/publish":{"post":{"description":"Publish a server to the registry","requestBody":{"content":{"application/json":{"schema":{"type":"object"}}}},"responses":{"501":{"content":{"application/json":{"schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"description":"Not implemented"}},"summary":"Publish server","tags":["registry","official"]}},"/registry/v0.1/servers":{"get":{"description":"Get a list of available servers in the registry","parameters":[{"description":"Pagination cursor for retrieving next set of results","in":"query","name":"cursor","schema":{"type":"string"}},{"description":"Maximum number of items to return","in":"query","name":"limit","schema":{"type":"integer"}},{"description":"Search servers by name (substring match)","in":"query","name":"search","schema":{"type":"string"}},{"description":"Filter by version ('latest' for latest version, or an exact version like '1.2.3')","in":"query","name":"version","schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object"}}}},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/v0.ServerListResponse"}}},"description":"OK"},"400":{"content":{"application/json":{"schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"description":"Bad request"}},"summary":"List servers","tags":["registry","official"]}},"/registry/v0.1/servers/{serverName}/versions":{"get":{"description":"Returns all available versions for a specific MCP server, ordered by publication date (newest first)","parameters":[{"description":"URL-encoded server name (e.g., \\","in":"path","name":"serverName","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object"}}}},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/v0.ServerListResponse"}}},"description":"A list of all versions for the server"},"400":{"content":{"application/json":{"schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"description":"Bad request"},"404":{"content":{"application/json":{"schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"description":"Server not found"}},"summary":"List all versions of an MCP server","tags":["servers"]}},"/registry/v0.1/servers/{serverName}/versions/{version}":{"get":{"description":"Returns detailed information about a specific version of an MCP server. Use the special version `latest` to get the latest version.","parameters":[{"description":"URL-encoded server name (e.g., \\","in":"path","name":"serverName","required":true,"schema":{"type":"string"}},{"description":"URL-encoded version to retrieve (e.g., \\","in":"path","name":"version","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object"}}}},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/v0.ServerResponse"}}},"description":"Detailed server information"},"400":{"content":{"application/json":{"schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"description":"Bad request"},"404":{"content":{"application/json":{"schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"description":"Server or version not found"}},"summary":"Get specific MCP server version","tags":["servers"]}},"/v0/publish":{"post":{"deprecated":true,"description":"Publish a new MCP server to the registry or update an existing one","requestBody":{"content":{"application/json":{"schema":{"type":"object"}}}},"responses":{"501":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/internal_api_v0.ErrorResponse"}}},"description":"Not Implemented"}},"summary":"Publish MCP server (Not Implemented)","tags":["servers"]}},"/version":{"get":{"deprecated":true,"description":"Get version information about the registry API","responses":{"200":{"content":{"application/json":{"schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"description":"OK"}},"summary":"Version information","tags":["system"]}}}, "openapi": "3.1.0", "tags": [ {"description":"Registry metadata and information","name":"registry"}, diff --git a/docs/thv-registry-api/swagger.yaml b/docs/thv-registry-api/swagger.yaml index 9a0cc27..3238c2d 100644 --- a/docs/thv-registry-api/swagger.yaml +++ b/docs/thv-registry-api/swagger.yaml @@ -1,6 +1,6 @@ components: schemas: - service.DeployedServer: + github_com_stacklok_toolhive-registry-server_internal_service.DeployedServer: properties: endpoint_url: type: string @@ -17,7 +17,7 @@ components: transport: type: string type: object - v0.EnvVarDetail: + internal_api_v0.EnvVarDetail: properties: default: type: string @@ -30,22 +30,22 @@ components: secret: type: boolean type: object - v0.ErrorResponse: + internal_api_v0.ErrorResponse: properties: error: type: string type: object - v0.ListServersResponse: + internal_api_v0.ListServersResponse: properties: servers: items: - $ref: '#/components/schemas/v0.ServerSummaryResponse' + $ref: '#/components/schemas/internal_api_v0.ServerSummaryResponse' type: array uniqueItems: false total: type: integer type: object - v0.RegistryInfoResponse: + internal_api_v0.RegistryInfoResponse: properties: last_updated: type: string @@ -56,7 +56,7 @@ components: version: type: string type: object - v0.ServerDetailResponse: + internal_api_v0.ServerDetailResponse: properties: args: items: @@ -67,7 +67,7 @@ components: type: string env_vars: items: - $ref: '#/components/schemas/v0.EnvVarDetail' + $ref: '#/components/schemas/internal_api_v0.EnvVarDetail' type: array uniqueItems: false image: @@ -102,7 +102,7 @@ components: additionalProperties: {} type: object type: object - v0.ServerSummaryResponse: + internal_api_v0.ServerSummaryResponse: properties: description: type: string @@ -117,6 +117,321 @@ components: transport: type: string type: object + model.Argument: + properties: + choices: + items: + type: string + type: array + uniqueItems: false + default: + type: string + description: + type: string + format: + $ref: '#/components/schemas/model.Format' + isRepeated: + type: boolean + isRequired: + type: boolean + isSecret: + type: boolean + name: + example: --port + type: string + placeholder: + type: string + type: + $ref: '#/components/schemas/model.ArgumentType' + value: + type: string + valueHint: + example: file_path + type: string + variables: + additionalProperties: + $ref: '#/components/schemas/model.Input' + type: object + type: object + model.ArgumentType: + example: positional + type: string + x-enum-varnames: + - ArgumentTypePositional + - ArgumentTypeNamed + model.Format: + type: string + x-enum-varnames: + - FormatString + - FormatNumber + - FormatBoolean + - FormatFilePath + model.Icon: + properties: + mimeType: + example: image/png + type: string + sizes: + items: + type: string + type: array + uniqueItems: false + src: + example: https://example.com/icon.png + format: uri + maxLength: 255 + type: string + theme: + type: string + type: object + model.Input: + properties: + choices: + items: + type: string + type: array + uniqueItems: false + default: + type: string + description: + type: string + format: + $ref: '#/components/schemas/model.Format' + isRequired: + type: boolean + isSecret: + type: boolean + placeholder: + type: string + value: + type: string + type: object + model.KeyValueInput: + properties: + choices: + items: + type: string + type: array + uniqueItems: false + default: + type: string + description: + type: string + format: + $ref: '#/components/schemas/model.Format' + isRequired: + type: boolean + isSecret: + type: boolean + name: + example: SOME_VARIABLE + type: string + placeholder: + type: string + value: + type: string + variables: + additionalProperties: + $ref: '#/components/schemas/model.Input' + type: object + type: object + model.Package: + properties: + environmentVariables: + description: EnvironmentVariables are set when running the package + items: + $ref: '#/components/schemas/model.KeyValueInput' + type: array + uniqueItems: false + fileSha256: + description: FileSHA256 is the SHA-256 hash for integrity verification (required + for mcpb, optional for others) + example: fe333e598595000ae021bd27117db32ec69af6987f507ba7a63c90638ff633ce + pattern: ^[a-f0-9]{64}$ + type: string + identifier: + description: |- + Identifier is the package identifier: + - For NPM/PyPI/NuGet: package name or ID + - For OCI: full image reference (e.g., "ghcr.io/owner/repo:v1.0.0") + - For MCPB: direct download URL + example: '@modelcontextprotocol/server-brave-search' + minLength: 1 + type: string + packageArguments: + description: PackageArguments are passed to the package's binary + items: + $ref: '#/components/schemas/model.Argument' + type: array + uniqueItems: false + registryBaseUrl: + description: RegistryBaseURL is the base URL of the package registry (used + by npm, pypi, nuget; not used by oci, mcpb) + example: https://registry.npmjs.org + format: uri + type: string + registryType: + description: RegistryType indicates how to download packages (e.g., "npm", + "pypi", "oci", "nuget", "mcpb") + example: npm + minLength: 1 + type: string + runtimeArguments: + description: RuntimeArguments are passed to the package's runtime command + (e.g., docker, npx) + items: + $ref: '#/components/schemas/model.Argument' + type: array + uniqueItems: false + runtimeHint: + description: RunTimeHint suggests the appropriate runtime for the package + example: npx + type: string + transport: + $ref: '#/components/schemas/model.Transport' + version: + description: Version is the package version (required for npm, pypi, nuget; + optional for mcpb; not used by oci where version is in the identifier) + example: 1.0.2 + minLength: 1 + type: string + type: object + model.Repository: + properties: + id: + example: b94b5f7e-c7c6-d760-2c78-a5e9b8a5b8c9 + type: string + source: + example: github + type: string + subfolder: + example: src/everything + type: string + url: + example: https://github.com/modelcontextprotocol/servers + format: uri + type: string + type: object + model.Status: + type: string + x-enum-varnames: + - StatusActive + - StatusDeprecated + - StatusDeleted + model.Transport: + description: Transport is required and specifies the transport protocol configuration + properties: + headers: + items: + $ref: '#/components/schemas/model.KeyValueInput' + type: array + uniqueItems: false + type: + example: stdio + type: string + url: + example: https://api.example.com/mcp + type: string + type: object + v0.Metadata: + properties: + count: + type: integer + nextCursor: + type: string + type: object + v0.RegistryExtensions: + properties: + isLatest: + type: boolean + publishedAt: + format: date-time + type: string + status: + $ref: '#/components/schemas/model.Status' + updatedAt: + format: date-time + type: string + type: object + v0.ResponseMeta: + properties: + io.modelcontextprotocol.registry/official: + $ref: '#/components/schemas/v0.RegistryExtensions' + type: object + v0.ServerJSON: + properties: + $schema: + example: https://static.modelcontextprotocol.io/schemas/2025-10-17/server.schema.json + format: uri + minLength: 1 + type: string + _meta: + $ref: '#/components/schemas/v0.ServerMeta' + description: + example: MCP server providing weather data and forecasts via OpenWeatherMap + API + maxLength: 100 + minLength: 1 + type: string + icons: + items: + $ref: '#/components/schemas/model.Icon' + type: array + uniqueItems: false + name: + example: io.github.user/weather + maxLength: 200 + minLength: 3 + pattern: ^[a-zA-Z0-9.-]+/[a-zA-Z0-9._-]+$ + type: string + packages: + items: + $ref: '#/components/schemas/model.Package' + type: array + uniqueItems: false + remotes: + items: + $ref: '#/components/schemas/model.Transport' + type: array + uniqueItems: false + repository: + $ref: '#/components/schemas/model.Repository' + title: + example: Weather API + maxLength: 100 + minLength: 1 + type: string + version: + example: 1.0.2 + type: string + websiteUrl: + example: https://modelcontextprotocol.io/examples + format: uri + type: string + type: object + v0.ServerListResponse: + properties: + metadata: + $ref: '#/components/schemas/v0.Metadata' + servers: + items: + $ref: '#/components/schemas/v0.ServerResponse' + type: array + uniqueItems: false + type: object + v0.ServerMeta: + properties: + io.modelcontextprotocol.registry/publisher-provided: + additionalProperties: {} + type: object + type: object + v0.ServerResponse: + properties: + _meta: + $ref: '#/components/schemas/v0.ResponseMeta' + server: + $ref: '#/components/schemas/v0.ServerJSON' + type: object externalDocs: description: "" url: "" @@ -159,19 +474,19 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/v0.RegistryInfoResponse' + $ref: '#/components/schemas/internal_api_v0.RegistryInfoResponse' description: OK "400": content: application/json: schema: - $ref: '#/components/schemas/v0.ErrorResponse' + $ref: '#/components/schemas/internal_api_v0.ErrorResponse' description: Bad Request "501": content: application/json: schema: - $ref: '#/components/schemas/v0.ErrorResponse' + $ref: '#/components/schemas/internal_api_v0.ErrorResponse' description: Not Implemented summary: Get registry information tags: @@ -218,19 +533,19 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/v0.ListServersResponse' + $ref: '#/components/schemas/internal_api_v0.ListServersResponse' description: OK "400": content: application/json: schema: - $ref: '#/components/schemas/v0.ErrorResponse' + $ref: '#/components/schemas/internal_api_v0.ErrorResponse' description: Bad Request "501": content: application/json: schema: - $ref: '#/components/schemas/v0.ErrorResponse' + $ref: '#/components/schemas/internal_api_v0.ErrorResponse' description: Not Implemented summary: List all servers tags: @@ -265,25 +580,25 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/v0.ServerDetailResponse' + $ref: '#/components/schemas/internal_api_v0.ServerDetailResponse' description: OK "400": content: application/json: schema: - $ref: '#/components/schemas/v0.ErrorResponse' + $ref: '#/components/schemas/internal_api_v0.ErrorResponse' description: Bad Request "404": content: application/json: schema: - $ref: '#/components/schemas/v0.ErrorResponse' + $ref: '#/components/schemas/internal_api_v0.ErrorResponse' description: Not Found "501": content: application/json: schema: - $ref: '#/components/schemas/v0.ErrorResponse' + $ref: '#/components/schemas/internal_api_v0.ErrorResponse' description: Not Implemented summary: Get server by name tags: @@ -303,14 +618,14 @@ paths: application/json: schema: items: - $ref: '#/components/schemas/service.DeployedServer' + $ref: '#/components/schemas/github_com_stacklok_toolhive-registry-server_internal_service.DeployedServer' type: array description: OK "500": content: application/json: schema: - $ref: '#/components/schemas/v0.ErrorResponse' + $ref: '#/components/schemas/internal_api_v0.ErrorResponse' description: Internal Server Error summary: List deployed servers tags: @@ -338,20 +653,20 @@ paths: application/json: schema: items: - $ref: '#/components/schemas/service.DeployedServer' + $ref: '#/components/schemas/github_com_stacklok_toolhive-registry-server_internal_service.DeployedServer' type: array description: OK "400": content: application/json: schema: - $ref: '#/components/schemas/v0.ErrorResponse' + $ref: '#/components/schemas/internal_api_v0.ErrorResponse' description: Bad Request "500": content: application/json: schema: - $ref: '#/components/schemas/v0.ErrorResponse' + $ref: '#/components/schemas/internal_api_v0.ErrorResponse' description: Internal Server Error summary: Get deployed servers by registry name tags: @@ -389,7 +704,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/v0.ErrorResponse' + $ref: '#/components/schemas/internal_api_v0.ErrorResponse' description: Service Unavailable summary: Readiness check tags: @@ -418,29 +733,58 @@ paths: /registry/v0.1/servers: get: description: Get a list of available servers in the registry + parameters: + - description: Pagination cursor for retrieving next set of results + in: query + name: cursor + schema: + type: string + - description: Maximum number of items to return + in: query + name: limit + schema: + type: integer + - description: Search servers by name (substring match) + in: query + name: search + schema: + type: string + - description: Filter by version ('latest' for latest version, or an exact version + like '1.2.3') + in: query + name: version + schema: + type: string requestBody: content: application/json: schema: type: object responses: - "501": + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/v0.ServerListResponse' + description: OK + "400": content: application/json: schema: additionalProperties: type: string type: object - description: Not implemented + description: Bad request summary: List servers tags: - registry - official /registry/v0.1/servers/{serverName}/versions: get: - description: Get a list of available versions for a specific server + description: Returns all available versions for a specific MCP server, ordered + by publication date (newest first) parameters: - - description: Server name + - description: URL-encoded server name (e.g., \ in: path name: serverName required: true @@ -452,6 +796,12 @@ paths: schema: type: object responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/v0.ServerListResponse' + description: A list of all versions for the server "400": content: application/json: @@ -460,29 +810,29 @@ paths: type: string type: object description: Bad request - "501": + "404": content: application/json: schema: additionalProperties: type: string type: object - description: Not implemented - summary: List server versions + description: Server not found + summary: List all versions of an MCP server tags: - - registry - - official + - servers /registry/v0.1/servers/{serverName}/versions/{version}: get: - description: Get details for a specific version of a server + description: Returns detailed information about a specific version of an MCP + server. Use the special version `latest` to get the latest version. parameters: - - description: Server name + - description: URL-encoded server name (e.g., \ in: path name: serverName required: true schema: type: string - - description: Version identifier + - description: URL-encoded version to retrieve (e.g., \ in: path name: version required: true @@ -494,6 +844,12 @@ paths: schema: type: object responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/v0.ServerResponse' + description: Detailed server information "400": content: application/json: @@ -502,18 +858,17 @@ paths: type: string type: object description: Bad request - "501": + "404": content: application/json: schema: additionalProperties: type: string type: object - description: Not implemented - summary: Get server version + description: Server or version not found + summary: Get specific MCP server version tags: - - registry - - official + - servers /v0/publish: post: deprecated: true @@ -529,7 +884,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/v0.ErrorResponse' + $ref: '#/components/schemas/internal_api_v0.ErrorResponse' description: Not Implemented summary: Publish MCP server (Not Implemented) tags: diff --git a/go.mod b/go.mod index b28441f..62f399f 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,7 @@ require ( github.com/go-chi/chi/v5 v5.2.3 github.com/go-git/go-billy/v5 v5.6.2 github.com/go-git/go-git/v5 v5.16.3 + github.com/modelcontextprotocol/registry v1.3.8 github.com/onsi/ginkgo/v2 v2.27.2 github.com/onsi/gomega v1.38.2 github.com/spf13/viper v1.21.0 diff --git a/go.sum b/go.sum index 866268a..2c846ee 100644 --- a/go.sum +++ b/go.sum @@ -184,6 +184,8 @@ github.com/mfridman/tparse v0.18.0 h1:wh6dzOKaIwkUGyKgOntDW4liXSo37qg5AXbIhkMV3v github.com/mfridman/tparse v0.18.0/go.mod h1:gEvqZTuCgEhPbYk/2lS3Kcxg1GmTxxU7kTC8DvP0i/A= github.com/moby/spdystream v0.5.0 h1:7r0J1Si3QO/kjRitvSLVVFUjxMEb/YLj6S9FF62JBCU= github.com/moby/spdystream v0.5.0/go.mod h1:xBAYlnt/ay+11ShkdFKNAG7LsyK/tmNBVvVOwrfMgdI= +github.com/modelcontextprotocol/registry v1.3.8 h1:qIqXkbynXZkC8rt+jfgMml31GK+6ArTGLwa0EtXg6LE= +github.com/modelcontextprotocol/registry v1.3.8/go.mod h1:g4/w42V7zZREHAyzorUqL0YcwnDOLe1qD/EUnELelsY= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= diff --git a/internal/api/registry/v01/routes.go b/internal/api/registry/v01/routes.go index 3816988..64600e7 100644 --- a/internal/api/registry/v01/routes.go +++ b/internal/api/registry/v01/routes.go @@ -2,8 +2,11 @@ package v01 import ( "net/http" + "strconv" + "time" "github.com/go-chi/chi/v5" + upstreamv0 "github.com/modelcontextprotocol/registry/pkg/api/v0" "github.com/stacklok/toolhive-registry-server/internal/api/common" "github.com/stacklok/toolhive-registry-server/internal/service" @@ -41,22 +44,76 @@ func Router(svc service.RegistryService) http.Handler { // @Tags registry,official // @Accept json // @Produce json -// @Failure 501 {object} map[string]string "Not implemented" +// @Param cursor query string false "Pagination cursor for retrieving next set of results" +// @Param limit query int false "Maximum number of items to return" +// @Param search query string false "Search servers by name (substring match)" +// @Param updated_since query time false "Filter servers updated since timestamp (RFC3339 datetime)" +// @Param version query string false "Filter by version ('latest' for latest version, or an exact version like '1.2.3')" +// @Success 200 {object} upstreamv0.ServerListResponse +// @Failure 400 {object} map[string]string "Bad request" // @Router /registry/v0.1/servers [get] func (rr *Routes) listServers(w http.ResponseWriter, r *http.Request) { - common.WriteErrorResponse(w, "Listing servers is not supported", http.StatusNotImplemented) + // Parse query parameters + query := r.URL.Query() + + // Parse cursor (optional string) + cursor := query.Get("cursor") + + // Parse limit (optional integer) + var limit *int + if limitStr := query.Get("limit"); limitStr != "" { + limitVal, err := strconv.Atoi(limitStr) + if err != nil { + common.WriteErrorResponse(w, "Invalid limit parameter: must be an integer", http.StatusBadRequest) + return + } + limit = &limitVal + } + + // Parse search (optional string) + search := query.Get("search") + + // Parse updated_since (optional RFC3339 datetime) + var updatedSince *time.Time + if updatedSinceStr := query.Get("updated_since"); updatedSinceStr != "" { + parsedTime, err := time.Parse(time.RFC3339, updatedSinceStr) + if err != nil { + common.WriteErrorResponse(w, "Invalid updated_since parameter: must be RFC3339 format (e.g., 2025-08-07T13:15:04.280Z)", http.StatusBadRequest) + return + } + updatedSince = &parsedTime + } + + // Parse version (optional string) + version := query.Get("version") + + // TODO: Use the parsed parameters in the actual implementation + _ = cursor + _ = limit + _ = search + _ = updatedSince + _ = version + + // Placeholder response - replace with actual implementation + common.WriteJSONResponse(w, upstreamv0.ServerListResponse{ + Servers: []upstreamv0.ServerResponse{}, + Metadata: upstreamv0.Metadata{ + Count: 0, + }, + }, http.StatusOK) } // listVersions handles GET /registry/v0.1/servers/{serverName}/versions // -// @Summary List server versions -// @Description Get a list of available versions for a specific server -// @Tags registry,official +// @Summary List all versions of an MCP server +// @Description Returns all available versions for a specific MCP server, ordered by publication date (newest first) +// @Tags servers // @Accept json // @Produce json -// @Param serverName path string true "Server name" +// @Param serverName path string true "URL-encoded server name (e.g., \"com.example%2Fmy-server\")" +// @Success 200 {object} upstreamv0.ServerListResponse "A list of all versions for the server" // @Failure 400 {object} map[string]string "Bad request" -// @Failure 501 {object} map[string]string "Not implemented" +// @Failure 404 {object} map[string]string "Server not found" // @Router /registry/v0.1/servers/{serverName}/versions [get] func (rr *Routes) listVersions(w http.ResponseWriter, r *http.Request) { serverName := chi.URLParam(r, "serverName") @@ -65,20 +122,27 @@ func (rr *Routes) listVersions(w http.ResponseWriter, r *http.Request) { return } - common.WriteErrorResponse(w, "Listing versions is not supported", http.StatusNotImplemented) + // Return empty version list + common.WriteJSONResponse(w, upstreamv0.ServerListResponse{ + Servers: []upstreamv0.ServerResponse{}, + Metadata: upstreamv0.Metadata{ + Count: 0, + }, + }, http.StatusOK) } // getVersion handles GET /registry/v0.1/servers/{serverName}/versions/{version} // -// @Summary Get server version -// @Description Get details for a specific version of a server -// @Tags registry,official +// @Summary Get specific MCP server version +// @Description Returns detailed information about a specific version of an MCP server. Use the special version `latest` to get the latest version. +// @Tags servers // @Accept json // @Produce json -// @Param serverName path string true "Server name" -// @Param version path string true "Version identifier" +// @Param serverName path string true "URL-encoded server name (e.g., \"com.example%2Fmy-server\")" +// @Param version path string true "URL-encoded version to retrieve (e.g., \"1.0.0\" or \"1.0.0%2B20130313144700\" for versions with build metadata)" +// @Success 200 {object} upstreamv0.ServerResponse "Detailed server information" // @Failure 400 {object} map[string]string "Bad request" -// @Failure 501 {object} map[string]string "Not implemented" +// @Failure 404 {object} map[string]string "Server or version not found" // @Router /registry/v0.1/servers/{serverName}/versions/{version} [get] func (rr *Routes) getVersion(w http.ResponseWriter, r *http.Request) { serverName := chi.URLParam(r, "serverName") @@ -88,7 +152,7 @@ func (rr *Routes) getVersion(w http.ResponseWriter, r *http.Request) { return } - common.WriteErrorResponse(w, "Getting version details is not supported", http.StatusNotImplemented) + common.WriteJSONResponse(w, upstreamv0.ServerResponse{}, http.StatusOK) } // publish handles POST /registry/v0.1/publish diff --git a/internal/api/registry/v01/routes_test.go b/internal/api/registry/v01/routes_test.go new file mode 100644 index 0000000..da964b6 --- /dev/null +++ b/internal/api/registry/v01/routes_test.go @@ -0,0 +1,217 @@ +package v01 + +import ( + "encoding/json" + "net/http" + "net/http/httptest" + "testing" + + upstreamv0 "github.com/modelcontextprotocol/registry/pkg/api/v0" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.uber.org/mock/gomock" + + "github.com/stacklok/toolhive-registry-server/internal/service/mocks" +) + +func TestListServers(t *testing.T) { + t.Parallel() + ctrl := gomock.NewController(t) + t.Cleanup(ctrl.Finish) + + mockSvc := mocks.NewMockRegistryService(ctrl) + router := Router(mockSvc) + + tests := []struct { + name string + path string + wantStatus int + }{ + { + name: "list servers - basic", + path: "/servers", + wantStatus: http.StatusOK, + }, + { + name: "list servers - with cursor", + path: "/servers?cursor=abc123", + wantStatus: http.StatusOK, + }, + { + name: "list servers - with limit", + path: "/servers?limit=10", + wantStatus: http.StatusOK, + }, + { + name: "list servers - with search", + path: "/servers?search=test", + wantStatus: http.StatusOK, + }, + { + name: "list servers - with updated_since", + path: "/servers?updated_since=2025-01-01T00:00:00Z", + wantStatus: http.StatusOK, + }, + { + name: "list servers - with version", + path: "/servers?version=latest", + wantStatus: http.StatusOK, + }, + { + name: "list servers - invalid limit", + path: "/servers?limit=invalid", + wantStatus: http.StatusBadRequest, + }, + { + name: "list servers - invalid updated_since", + path: "/servers?updated_since=invalid", + wantStatus: http.StatusBadRequest, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + req, err := http.NewRequest("GET", tt.path, nil) + require.NoError(t, err) + + rr := httptest.NewRecorder() + router.ServeHTTP(rr, req) + + assert.Equal(t, tt.wantStatus, rr.Code) + + if tt.wantStatus == http.StatusOK { + var response upstreamv0.ServerListResponse + err = json.Unmarshal(rr.Body.Bytes(), &response) + require.NoError(t, err) + assert.NotNil(t, response.Servers) + assert.NotNil(t, response.Metadata) + } + }) + } +} + +func TestListVersions(t *testing.T) { + t.Parallel() + ctrl := gomock.NewController(t) + t.Cleanup(ctrl.Finish) + + mockSvc := mocks.NewMockRegistryService(ctrl) + router := Router(mockSvc) + + tests := []struct { + name string + path string + wantStatus int + }{ + { + name: "list versions - valid server name", + path: "/servers/test-server/versions", + wantStatus: http.StatusOK, + }, + { + name: "list versions - empty server name", + path: "/servers//versions", + wantStatus: http.StatusBadRequest, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + req, err := http.NewRequest("GET", tt.path, nil) + require.NoError(t, err) + + rr := httptest.NewRecorder() + router.ServeHTTP(rr, req) + + assert.Equal(t, tt.wantStatus, rr.Code) + + if tt.wantStatus == http.StatusOK { + var response upstreamv0.ServerListResponse + err = json.Unmarshal(rr.Body.Bytes(), &response) + require.NoError(t, err) + assert.NotNil(t, response.Servers) + assert.NotNil(t, response.Metadata) + } + }) + } +} + +func TestGetVersion(t *testing.T) { + t.Parallel() + ctrl := gomock.NewController(t) + t.Cleanup(ctrl.Finish) + + mockSvc := mocks.NewMockRegistryService(ctrl) + router := Router(mockSvc) + + tests := []struct { + name string + path string + wantStatus int + }{ + { + name: "get version - valid server and version", + path: "/servers/test-server/versions/1.0.0", + wantStatus: http.StatusOK, + }, + { + name: "get version - latest", + path: "/servers/test-server/versions/latest", + wantStatus: http.StatusOK, + }, + { + name: "get version - empty server name", + path: "/servers//versions/1.0.0", + wantStatus: http.StatusBadRequest, + }, + { + name: "get version - empty version", + path: "/servers/test-server/versions/", + wantStatus: http.StatusNotFound, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + req, err := http.NewRequest("GET", tt.path, nil) + require.NoError(t, err) + + rr := httptest.NewRecorder() + router.ServeHTTP(rr, req) + + assert.Equal(t, tt.wantStatus, rr.Code) + + if tt.wantStatus == http.StatusOK { + var response upstreamv0.ServerResponse + err = json.Unmarshal(rr.Body.Bytes(), &response) + require.NoError(t, err) + } + }) + } +} + +func TestPublish(t *testing.T) { + t.Parallel() + ctrl := gomock.NewController(t) + t.Cleanup(ctrl.Finish) + + mockSvc := mocks.NewMockRegistryService(ctrl) + router := Router(mockSvc) + + req, err := http.NewRequest("POST", "/publish", nil) + require.NoError(t, err) + + rr := httptest.NewRecorder() + router.ServeHTTP(rr, req) + + assert.Equal(t, http.StatusNotImplemented, rr.Code) + + var response map[string]string + err = json.Unmarshal(rr.Body.Bytes(), &response) + require.NoError(t, err) + assert.Contains(t, response, "error") + assert.Equal(t, "Publishing is not supported", response["error"]) +}