diff --git a/database/queries/registry.sql b/database/queries/registry.sql index 88f2ecf..dc8b4aa 100644 --- a/database/queries/registry.sql +++ b/database/queries/registry.sql @@ -25,6 +25,15 @@ SELECT id, FROM registry WHERE id = sqlc.arg(id); +-- name: GetRegistryByName :one +SELECT id, + name, + reg_type, + created_at, + updated_at + FROM registry + WHERE name = sqlc.arg(name); + -- name: InsertRegistry :one INSERT INTO registry ( name, diff --git a/database/queries/servers.sql b/database/queries/servers.sql index 76575e7..48164bf 100644 --- a/database/queries/servers.sql +++ b/database/queries/servers.sql @@ -237,3 +237,9 @@ INSERT INTO mcp_server_icon ( ) ON CONFLICT (server_id, source_uri, mime_type, theme) DO UPDATE SET theme = sqlc.arg(theme); + +-- name: DeleteServerVersion :exec +DELETE FROM mcp_server +WHERE reg_id = sqlc.arg(reg_id) + AND name = sqlc.arg(name) + AND version = sqlc.arg(version); diff --git a/docs/thv-registry-api/docs.go b/docs/thv-registry-api/docs.go index 1f938a6..24bd0d9 100644 --- a/docs/thv-registry-api/docs.go +++ b/docs/thv-registry-api/docs.go @@ -9,7 +9,7 @@ const docTemplate = `{ "components": {"schemas":{"internal_api_v0.ErrorResponse":{"properties":{"error":{"type":"string"}},"type":"object"},"internal_api_v0.RegistryInfoResponse":{"properties":{"last_updated":{"type":"string"},"source":{"type":"string"},"total_servers":{"type":"integer"},"version":{"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/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"]}},"/extension/v0/registries":{"get":{"description":"List all registries","requestBody":{"content":{"application/json":{"schema":{"type":"object"}}}},"responses":{"501":{"content":{"application/json":{"schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"description":"Not implemented"}},"summary":"List registries","tags":["extension"]}},"/extension/v0/registries/{registryName}":{"delete":{"description":"Delete a registry","parameters":[{"description":"Registry Name","in":"path","name":"registryName","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":"Delete registry","tags":["extension"]},"get":{"description":"Get a registry by name","parameters":[{"description":"Registry Name","in":"path","name":"registryName","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 registry","tags":["extension"]},"put":{"description":"Create or update a registry","parameters":[{"description":"Registry Name","in":"path","name":"registryName","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":"Create or update registry","tags":["extension"]}},"/extension/v0/registries/{registryName}/servers/{serverName}/versions/{version}":{"delete":{"description":"Delete a server from the registry","parameters":[{"description":"Registry Name","in":"path","name":"registryName","required":true,"schema":{"type":"string"}},{"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":{"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":"Delete server","tags":["extension"]},"put":{"description":"Create or update a server in the registry","parameters":[{"description":"Registry Name","in":"path","name":"registryName","required":true,"schema":{"type":"string"}},{"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":{"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":"Create or update server","tags":["extension"]}},"/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":["registry","official"]}},"/registry/v0.1/servers/{serverName}/versions/{version}":{"get":{"description":"Returns detailed information about a specific version of an MCP server.\nUse 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":["registry","official"]}},"/registry/{registryName}/v0.1/publish":{"post":{"description":"Publish a server to the registry","parameters":[{"description":"Registry name","in":"path","name":"registryName","required":true,"schema":{"type":"string"}}],"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/{registryName}/v0.1/servers":{"get":{"description":"Get a list of available servers in the registry","parameters":[{"description":"Registry name","in":"path","name":"registryName","required":true,"schema":{"type":"string"}},{"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/{registryName}/v0.1/servers/{serverName}/versions":{"get":{"description":"Returns all available versions for a specific MCP server, ordered by publication date (newest first)","parameters":[{"description":"Registry name","in":"path","name":"registryName","required":true,"schema":{"type":"string"}},{"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":["registry","official"]}},"/registry/{registryName}/v0.1/servers/{serverName}/versions/{version}":{"get":{"description":"Returns detailed information about a specific version of an MCP server.\nUse the special version ` + "`" + `latest` + "`" + ` to get the latest version.","parameters":[{"description":"Registry name","in":"path","name":"registryName","required":true,"schema":{"type":"string"}},{"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":["registry","official"]}},"/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"]}},"/extension/v0/registries":{"get":{"description":"List all registries","requestBody":{"content":{"application/json":{"schema":{"type":"object"}}}},"responses":{"501":{"content":{"application/json":{"schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"description":"Not implemented"}},"summary":"List registries","tags":["extension"]}},"/extension/v0/registries/{registryName}":{"delete":{"description":"Delete a registry","parameters":[{"description":"Registry Name","in":"path","name":"registryName","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":"Delete registry","tags":["extension"]},"get":{"description":"Get a registry by name","parameters":[{"description":"Registry Name","in":"path","name":"registryName","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 registry","tags":["extension"]},"put":{"description":"Create or update a registry","parameters":[{"description":"Registry Name","in":"path","name":"registryName","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":"Create or update registry","tags":["extension"]}},"/extension/v0/registries/{registryName}/servers/{serverName}/versions/{version}":{"put":{"description":"Create or update a server in the registry","parameters":[{"description":"Registry Name","in":"path","name":"registryName","required":true,"schema":{"type":"string"}},{"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":{"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":"Create or update server","tags":["extension"]}},"/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":["registry","official"]}},"/registry/v0.1/servers/{serverName}/versions/{version}":{"get":{"description":"Returns detailed information about a specific version of an MCP server.\nUse 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":["registry","official"]}},"/registry/{registryName}/v0.1/publish":{"post":{"description":"Publish a server to the registry","parameters":[{"description":"Registry name","in":"path","name":"registryName","required":true,"schema":{"type":"string"}}],"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/{registryName}/v0.1/servers":{"get":{"description":"Get a list of available servers in the registry","parameters":[{"description":"Registry name","in":"path","name":"registryName","required":true,"schema":{"type":"string"}},{"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/{registryName}/v0.1/servers/{serverName}/versions":{"get":{"description":"Returns all available versions for a specific MCP server, ordered by publication date (newest first)","parameters":[{"description":"Registry name","in":"path","name":"registryName","required":true,"schema":{"type":"string"}},{"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":["registry","official"]}},"/registry/{registryName}/v0.1/servers/{serverName}/versions/{version}":{"get":{"description":"Returns detailed information about a specific version of an MCP server.\nUse the special version ` + "`" + `latest` + "`" + ` to get the latest version.","parameters":[{"description":"Registry name","in":"path","name":"registryName","required":true,"schema":{"type":"string"}},{"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":["registry","official"]}},"/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"]}},"/{registryName}/v0.1/servers/{serverName}/versions/{version}":{"delete":{"description":"Delete a server version from a specific managed registry","parameters":[{"description":"Registry name","in":"path","name":"registryName","required":true,"schema":{"type":"string"}},{"description":"Server name (URL-encoded)","in":"path","name":"serverName","required":true,"schema":{"type":"string"}},{"description":"Version (URL-encoded)","in":"path","name":"version","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object"}}}},"responses":{"204":{"description":"No content"},"400":{"content":{"application/json":{"schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"description":"Bad request"},"403":{"content":{"application/json":{"schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"description":"Not a managed registry"},"404":{"content":{"application/json":{"schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"description":"Server version not found"},"500":{"content":{"application/json":{"schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"description":"Internal server error"}},"summary":"Delete server version from specific registry","tags":["registry","official"]}}}, "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 7318a27..f792b15 100644 --- a/docs/thv-registry-api/swagger.json +++ b/docs/thv-registry-api/swagger.json @@ -2,7 +2,7 @@ "components": {"schemas":{"internal_api_v0.ErrorResponse":{"properties":{"error":{"type":"string"}},"type":"object"},"internal_api_v0.RegistryInfoResponse":{"properties":{"last_updated":{"type":"string"},"source":{"type":"string"},"total_servers":{"type":"integer"},"version":{"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/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"]}},"/extension/v0/registries":{"get":{"description":"List all registries","requestBody":{"content":{"application/json":{"schema":{"type":"object"}}}},"responses":{"501":{"content":{"application/json":{"schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"description":"Not implemented"}},"summary":"List registries","tags":["extension"]}},"/extension/v0/registries/{registryName}":{"delete":{"description":"Delete a registry","parameters":[{"description":"Registry Name","in":"path","name":"registryName","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":"Delete registry","tags":["extension"]},"get":{"description":"Get a registry by name","parameters":[{"description":"Registry Name","in":"path","name":"registryName","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 registry","tags":["extension"]},"put":{"description":"Create or update a registry","parameters":[{"description":"Registry Name","in":"path","name":"registryName","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":"Create or update registry","tags":["extension"]}},"/extension/v0/registries/{registryName}/servers/{serverName}/versions/{version}":{"delete":{"description":"Delete a server from the registry","parameters":[{"description":"Registry Name","in":"path","name":"registryName","required":true,"schema":{"type":"string"}},{"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":{"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":"Delete server","tags":["extension"]},"put":{"description":"Create or update a server in the registry","parameters":[{"description":"Registry Name","in":"path","name":"registryName","required":true,"schema":{"type":"string"}},{"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":{"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":"Create or update server","tags":["extension"]}},"/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":["registry","official"]}},"/registry/v0.1/servers/{serverName}/versions/{version}":{"get":{"description":"Returns detailed information about a specific version of an MCP server.\nUse 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":["registry","official"]}},"/registry/{registryName}/v0.1/publish":{"post":{"description":"Publish a server to the registry","parameters":[{"description":"Registry name","in":"path","name":"registryName","required":true,"schema":{"type":"string"}}],"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/{registryName}/v0.1/servers":{"get":{"description":"Get a list of available servers in the registry","parameters":[{"description":"Registry name","in":"path","name":"registryName","required":true,"schema":{"type":"string"}},{"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/{registryName}/v0.1/servers/{serverName}/versions":{"get":{"description":"Returns all available versions for a specific MCP server, ordered by publication date (newest first)","parameters":[{"description":"Registry name","in":"path","name":"registryName","required":true,"schema":{"type":"string"}},{"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":["registry","official"]}},"/registry/{registryName}/v0.1/servers/{serverName}/versions/{version}":{"get":{"description":"Returns detailed information about a specific version of an MCP server.\nUse the special version `latest` to get the latest version.","parameters":[{"description":"Registry name","in":"path","name":"registryName","required":true,"schema":{"type":"string"}},{"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":["registry","official"]}},"/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"]}},"/extension/v0/registries":{"get":{"description":"List all registries","requestBody":{"content":{"application/json":{"schema":{"type":"object"}}}},"responses":{"501":{"content":{"application/json":{"schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"description":"Not implemented"}},"summary":"List registries","tags":["extension"]}},"/extension/v0/registries/{registryName}":{"delete":{"description":"Delete a registry","parameters":[{"description":"Registry Name","in":"path","name":"registryName","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":"Delete registry","tags":["extension"]},"get":{"description":"Get a registry by name","parameters":[{"description":"Registry Name","in":"path","name":"registryName","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 registry","tags":["extension"]},"put":{"description":"Create or update a registry","parameters":[{"description":"Registry Name","in":"path","name":"registryName","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":"Create or update registry","tags":["extension"]}},"/extension/v0/registries/{registryName}/servers/{serverName}/versions/{version}":{"put":{"description":"Create or update a server in the registry","parameters":[{"description":"Registry Name","in":"path","name":"registryName","required":true,"schema":{"type":"string"}},{"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":{"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":"Create or update server","tags":["extension"]}},"/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":["registry","official"]}},"/registry/v0.1/servers/{serverName}/versions/{version}":{"get":{"description":"Returns detailed information about a specific version of an MCP server.\nUse 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":["registry","official"]}},"/registry/{registryName}/v0.1/publish":{"post":{"description":"Publish a server to the registry","parameters":[{"description":"Registry name","in":"path","name":"registryName","required":true,"schema":{"type":"string"}}],"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/{registryName}/v0.1/servers":{"get":{"description":"Get a list of available servers in the registry","parameters":[{"description":"Registry name","in":"path","name":"registryName","required":true,"schema":{"type":"string"}},{"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/{registryName}/v0.1/servers/{serverName}/versions":{"get":{"description":"Returns all available versions for a specific MCP server, ordered by publication date (newest first)","parameters":[{"description":"Registry name","in":"path","name":"registryName","required":true,"schema":{"type":"string"}},{"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":["registry","official"]}},"/registry/{registryName}/v0.1/servers/{serverName}/versions/{version}":{"get":{"description":"Returns detailed information about a specific version of an MCP server.\nUse the special version `latest` to get the latest version.","parameters":[{"description":"Registry name","in":"path","name":"registryName","required":true,"schema":{"type":"string"}},{"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":["registry","official"]}},"/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"]}},"/{registryName}/v0.1/servers/{serverName}/versions/{version}":{"delete":{"description":"Delete a server version from a specific managed registry","parameters":[{"description":"Registry name","in":"path","name":"registryName","required":true,"schema":{"type":"string"}},{"description":"Server name (URL-encoded)","in":"path","name":"serverName","required":true,"schema":{"type":"string"}},{"description":"Version (URL-encoded)","in":"path","name":"version","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object"}}}},"responses":{"204":{"description":"No content"},"400":{"content":{"application/json":{"schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"description":"Bad request"},"403":{"content":{"application/json":{"schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"description":"Not a managed registry"},"404":{"content":{"application/json":{"schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"description":"Server version not found"},"500":{"content":{"application/json":{"schema":{"additionalProperties":{"type":"string"},"type":"object"}}},"description":"Internal server error"}},"summary":"Delete server version from specific registry","tags":["registry","official"]}}}, "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 ae4dd17..855ebdb 100644 --- a/docs/thv-registry-api/swagger.yaml +++ b/docs/thv-registry-api/swagger.yaml @@ -348,6 +348,72 @@ info: version: "0.1" openapi: 3.1.0 paths: + /{registryName}/v0.1/servers/{serverName}/versions/{version}: + delete: + description: Delete a server version from a specific managed registry + parameters: + - description: Registry name + in: path + name: registryName + required: true + schema: + type: string + - description: Server name (URL-encoded) + in: path + name: serverName + required: true + schema: + type: string + - description: Version (URL-encoded) + in: path + name: version + required: true + schema: + type: string + requestBody: + content: + application/json: + schema: + type: object + responses: + "204": + description: No content + "400": + content: + application/json: + schema: + additionalProperties: + type: string + type: object + description: Bad request + "403": + content: + application/json: + schema: + additionalProperties: + type: string + type: object + description: Not a managed registry + "404": + content: + application/json: + schema: + additionalProperties: + type: string + type: object + description: Server version not found + "500": + content: + application/json: + schema: + additionalProperties: + type: string + type: object + description: Internal server error + summary: Delete server version from specific registry + tags: + - registry + - official /api/v0/registry/info: get: deprecated: true @@ -532,52 +598,6 @@ paths: tags: - extension /extension/v0/registries/{registryName}/servers/{serverName}/versions/{version}: - delete: - description: Delete a server from the registry - parameters: - - description: Registry Name - in: path - name: registryName - required: true - schema: - type: string - - 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: - "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: Delete server - tags: - - extension put: description: Create or update a server in the registry parameters: diff --git a/internal/api/extension/v0/routes.go b/internal/api/extension/v0/routes.go index 6e71693..1f6c772 100644 --- a/internal/api/extension/v0/routes.go +++ b/internal/api/extension/v0/routes.go @@ -37,7 +37,6 @@ func Router(svc service.RegistryService) http.Handler { r.Route("/registries/{registryName}/servers/{serverName}", func(r chi.Router) { r.Put("/versions/{version}", routes.upsertVersion) - r.Delete("/versions/{version}", routes.deleteVersion) }) return r @@ -152,37 +151,3 @@ func (*Routes) upsertVersion(w http.ResponseWriter, r *http.Request) { common.WriteErrorResponse(w, "Creating or updating servers is not supported", http.StatusNotImplemented) } - -// deleteVersion handles DELETE /extension/v0/registries/{registryName}/servers/{serverName}/versions/{version} -// -// @Summary Delete server -// @Description Delete a server from the registry -// @Tags extension -// @Accept json -// @Produce json -// @Param registryName path string true "Registry Name" -// @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\")" -// @Failure 400 {object} map[string]string "Bad request" -// @Failure 501 {object} map[string]string "Not implemented" -// @Router /extension/v0/registries/{registryName}/servers/{serverName}/versions/{version} [delete] -func (*Routes) deleteVersion(w http.ResponseWriter, r *http.Request) { - registryName := chi.URLParam(r, "registryName") - serverName := chi.URLParam(r, "serverName") - version := chi.URLParam(r, "version") - - if strings.TrimSpace(registryName) == "" { - common.WriteErrorResponse(w, "Registry name is required", http.StatusBadRequest) - return - } - if strings.TrimSpace(serverName) == "" { - common.WriteErrorResponse(w, "Server ID is required", http.StatusBadRequest) - return - } - if strings.TrimSpace(version) == "" { - common.WriteErrorResponse(w, "Version is required", http.StatusBadRequest) - return - } - - common.WriteErrorResponse(w, "Deleting servers is not supported", http.StatusNotImplemented) -} diff --git a/internal/api/extension/v0/routes_test.go b/internal/api/extension/v0/routes_test.go index 96ce61e..7b2c4c2 100644 --- a/internal/api/extension/v0/routes_test.go +++ b/internal/api/extension/v0/routes_test.go @@ -99,92 +99,6 @@ func TestUpsertVersion(t *testing.T) { } } -func TestDeleteVersion(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 - method string - wantStatus int - wantError string - }{ - { - name: "delete version - valid ID", - path: "/registries/foo/servers/test-server-123/versions/1.0.0", - method: "DELETE", - wantStatus: http.StatusNotImplemented, - }, - { - name: "delete version - empty registry name", - path: "/registries/%20/servers/test-server-123/versions/1.0.0", - method: "DELETE", - wantStatus: http.StatusBadRequest, - wantError: "Registry name is required", - }, - { - name: "delete version - empty server name", - path: "/registries/foo/servers/%20/versions/1.0.0", - method: "DELETE", - wantStatus: http.StatusBadRequest, - wantError: "Server ID is required", - }, - { - name: "delete version - empty version", - path: "/registries/foo/servers/test-server-123/versions/%20", - method: "DELETE", - wantStatus: http.StatusBadRequest, - wantError: "Version is required", - }, - { - name: "delete version - with special characters", - path: "/registries/foo/servers/test-server-123/versions/1.0%2F0.0", - method: "DELETE", - wantStatus: http.StatusNotImplemented, - }, - { - name: "delete version - no id", - path: "/registries/foo/servers/test-server-123/versions/", - method: "DELETE", - wantStatus: http.StatusNotFound, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - t.Parallel() - req, err := http.NewRequest(tt.method, 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.StatusNotImplemented { - var response map[string]string - err = json.Unmarshal(rr.Body.Bytes(), &response) - require.NoError(t, err) - assert.Contains(t, response, "error") - assert.Equal(t, "Deleting servers is not supported", response["error"]) - } - - if tt.wantStatus == http.StatusBadRequest && tt.wantError != "" { - var response map[string]string - err = json.Unmarshal(rr.Body.Bytes(), &response) - require.NoError(t, err) - assert.Contains(t, response, "error") - assert.Equal(t, tt.wantError, response["error"]) - } - }) - } -} - func TestListRegistries(t *testing.T) { t.Parallel() ctrl := gomock.NewController(t) diff --git a/internal/api/registry/v01/routes.go b/internal/api/registry/v01/routes.go index e77b56c..cf0a36e 100644 --- a/internal/api/registry/v01/routes.go +++ b/internal/api/registry/v01/routes.go @@ -2,6 +2,7 @@ package v01 import ( + "errors" "net/http" "strconv" "strings" @@ -43,6 +44,7 @@ func Router(svc service.RegistryService) http.Handler { r.Route("/{registryName}/v0.1/servers/{serverName}", func(r chi.Router) { r.Get("/versions", routes.listVersionsWithRegistryName) r.Get("/versions/{version}", routes.getVersionWithRegistryName) + r.Delete("/versions/{version}", routes.deleteVersionWithRegistryName) }) r.Post("/{registryName}/v0.1/publish", routes.publishWithRegistryName) @@ -328,6 +330,70 @@ func (routes *Routes) getVersionWithRegistryName(w http.ResponseWriter, r *http. routes.handleGetVersion(w, r, registryName) } +// deleteVersionWithRegistryName handles DELETE /{registryName}/v0.1/servers/{serverName}/versions/{version} +// +// @Summary Delete server version from specific registry +// @Description Delete a server version from a specific managed registry +// @Tags registry,official +// @Accept json +// @Produce json +// @Param registryName path string true "Registry name" +// @Param serverName path string true "Server name (URL-encoded)" +// @Param version path string true "Version (URL-encoded)" +// @Success 204 "No content" +// @Failure 400 {object} map[string]string "Bad request" +// @Failure 403 {object} map[string]string "Not a managed registry" +// @Failure 404 {object} map[string]string "Server version not found" +// @Failure 500 {object} map[string]string "Internal server error" +// @Router /{registryName}/v0.1/servers/{serverName}/versions/{version} [delete] +func (routes *Routes) deleteVersionWithRegistryName(w http.ResponseWriter, r *http.Request) { + // Extract URL parameters + registryName := chi.URLParam(r, "registryName") + serverName := chi.URLParam(r, "serverName") + version := chi.URLParam(r, "version") + + if strings.TrimSpace(registryName) == "" { + common.WriteErrorResponse(w, "Registry name is required", http.StatusBadRequest) + return + } + + if strings.TrimSpace(serverName) == "" { + common.WriteErrorResponse(w, "Server name is required", http.StatusBadRequest) + return + } + + if strings.TrimSpace(version) == "" { + common.WriteErrorResponse(w, "Version is required", http.StatusBadRequest) + return + } + + // Call service layer + err := routes.service.DeleteServerVersion( + r.Context(), + service.WithRegistryName[service.DeleteServerVersionOptions](registryName), + service.WithName[service.DeleteServerVersionOptions](serverName), + service.WithVersion[service.DeleteServerVersionOptions](version), + ) + + if err != nil { + // Check for specific error types + if errors.Is(err, service.ErrRegistryNotFound) || errors.Is(err, service.ErrServerNotFound) { + common.WriteErrorResponse(w, err.Error(), http.StatusNotFound) + return + } + if errors.Is(err, service.ErrNotManagedRegistry) { + common.WriteErrorResponse(w, err.Error(), http.StatusForbidden) + return + } + // All other errors + common.WriteErrorResponse(w, "Failed to delete server version", http.StatusInternalServerError) + return + } + + // Success - return 204 No Content + w.WriteHeader(http.StatusNoContent) +} + // handlePublish is a shared helper that handles publishing with an optional registry name. func (*Routes) handlePublish(w http.ResponseWriter, r *http.Request, registryName string) { // TODO: Use registryName in the actual implementation diff --git a/internal/db/sqlc/querier.go b/internal/db/sqlc/querier.go index 6a87d8d..13291f6 100644 --- a/internal/db/sqlc/querier.go +++ b/internal/db/sqlc/querier.go @@ -11,7 +11,9 @@ import ( ) type Querier interface { + DeleteServerVersion(ctx context.Context, arg DeleteServerVersionParams) error GetRegistry(ctx context.Context, id uuid.UUID) (Registry, error) + GetRegistryByName(ctx context.Context, name string) (Registry, error) GetRegistrySync(ctx context.Context, id uuid.UUID) (RegistrySync, error) GetServerVersion(ctx context.Context, arg GetServerVersionParams) (GetServerVersionRow, error) InsertRegistry(ctx context.Context, arg InsertRegistryParams) (uuid.UUID, error) diff --git a/internal/db/sqlc/registry.sql.go b/internal/db/sqlc/registry.sql.go index 8197da2..280afe6 100644 --- a/internal/db/sqlc/registry.sql.go +++ b/internal/db/sqlc/registry.sql.go @@ -35,6 +35,29 @@ func (q *Queries) GetRegistry(ctx context.Context, id uuid.UUID) (Registry, erro return i, err } +const getRegistryByName = `-- name: GetRegistryByName :one +SELECT id, + name, + reg_type, + created_at, + updated_at + FROM registry + WHERE name = $1 +` + +func (q *Queries) GetRegistryByName(ctx context.Context, name string) (Registry, error) { + row := q.db.QueryRow(ctx, getRegistryByName, name) + var i Registry + err := row.Scan( + &i.ID, + &i.Name, + &i.RegType, + &i.CreatedAt, + &i.UpdatedAt, + ) + return i, err +} + const insertRegistry = `-- name: InsertRegistry :one INSERT INTO registry ( name, diff --git a/internal/db/sqlc/servers.sql.go b/internal/db/sqlc/servers.sql.go index 0254c4c..8c7bac9 100644 --- a/internal/db/sqlc/servers.sql.go +++ b/internal/db/sqlc/servers.sql.go @@ -12,6 +12,24 @@ import ( "github.com/google/uuid" ) +const deleteServerVersion = `-- name: DeleteServerVersion :exec +DELETE FROM mcp_server +WHERE reg_id = $1 + AND name = $2 + AND version = $3 +` + +type DeleteServerVersionParams struct { + RegID uuid.UUID `json:"reg_id"` + Name string `json:"name"` + Version string `json:"version"` +} + +func (q *Queries) DeleteServerVersion(ctx context.Context, arg DeleteServerVersionParams) error { + _, err := q.db.Exec(ctx, deleteServerVersion, arg.RegID, arg.Name, arg.Version) + return err +} + const getServerVersion = `-- name: GetServerVersion :one SELECT r.reg_type as registry_type, s.id, diff --git a/internal/service/db/impl.go b/internal/service/db/impl.go index a12cea3..3f59f5c 100644 --- a/internal/service/db/impl.go +++ b/internal/service/db/impl.go @@ -224,6 +224,70 @@ func (s *dbService) GetServerVersion( return res[0], nil } +// DeleteServerVersion removes a server version from a managed registry +func (s *dbService) DeleteServerVersion( + ctx context.Context, + opts ...service.Option[service.DeleteServerVersionOptions], +) error { + // 1. Parse options + options := &service.DeleteServerVersionOptions{} + for _, opt := range opts { + if err := opt(options); err != nil { + return fmt.Errorf("invalid option: %w", err) + } + } + + // 2. Begin transaction + tx, err := s.pool.BeginTx(ctx, pgx.TxOptions{ + IsoLevel: pgx.Serializable, + AccessMode: pgx.ReadWrite, + }) + if err != nil { + return fmt.Errorf("failed to begin transaction: %w", err) + } + defer func() { + err := tx.Rollback(ctx) + if err != nil && !errors.Is(err, pgx.ErrTxClosed) { + // TODO: log the rollback error (add proper logging) + _ = err + } + }() + + querier := sqlc.New(tx) + + // 3. Validate registry exists and get registry info + registry, err := querier.GetRegistryByName(ctx, options.RegistryName) + if err != nil { + if errors.Is(err, pgx.ErrNoRows) { + return fmt.Errorf("%w: %s", service.ErrRegistryNotFound, options.RegistryName) + } + return fmt.Errorf("failed to get registry: %w", err) + } + + // 4. Validate registry is LOCAL (managed) type + if registry.RegType != sqlc.RegistryTypeLOCAL { + return fmt.Errorf("%w: registry %s has type %s", + service.ErrNotManagedRegistry, options.RegistryName, registry.RegType) + } + + // 5. Delete the server version + err = querier.DeleteServerVersion(ctx, sqlc.DeleteServerVersionParams{ + RegID: registry.ID, + Name: options.ServerName, + Version: options.Version, + }) + if err != nil { + return fmt.Errorf("failed to delete server version: %w", err) + } + + // 6. Commit transaction + if err := tx.Commit(ctx); err != nil { + return fmt.Errorf("failed to commit transaction: %w", err) + } + + return nil +} + // querierFunction is a function that uses the given querier object to run the // main extraction. As of the time of this writing, its main use is accessing // the `mcp_server` table in a type-agnostic way. This is to overcome a diff --git a/internal/service/inmemory/impl.go b/internal/service/inmemory/impl.go index 1a47764..79e6e20 100644 --- a/internal/service/inmemory/impl.go +++ b/internal/service/inmemory/impl.go @@ -197,6 +197,14 @@ func (s *regSvc) GetServerVersion( return nil, service.ErrServerNotFound } +// DeleteServerVersion implements RegistryService.DeleteServerVersion +func (*regSvc) DeleteServerVersion( + _ context.Context, + _ ...service.Option[service.DeleteServerVersionOptions], +) error { + return service.ErrNotImplemented +} + // getServerByNameWithName returns a server by name with name properly populated func (s *regSvc) getServerByName(name string) (*upstreamv0.ServerJSON, error) { // Check container servers first diff --git a/internal/service/mocks/mock_service.go b/internal/service/mocks/mock_service.go index 84bf744..a71f29b 100644 --- a/internal/service/mocks/mock_service.go +++ b/internal/service/mocks/mock_service.go @@ -57,6 +57,25 @@ func (mr *MockRegistryServiceMockRecorder) CheckReadiness(ctx any) *gomock.Call return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CheckReadiness", reflect.TypeOf((*MockRegistryService)(nil).CheckReadiness), ctx) } +// DeleteServerVersion mocks base method. +func (m *MockRegistryService) DeleteServerVersion(ctx context.Context, opts ...service.Option[service.DeleteServerVersionOptions]) error { + m.ctrl.T.Helper() + varargs := []any{ctx} + for _, a := range opts { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "DeleteServerVersion", varargs...) + ret0, _ := ret[0].(error) + return ret0 +} + +// DeleteServerVersion indicates an expected call of DeleteServerVersion. +func (mr *MockRegistryServiceMockRecorder) DeleteServerVersion(ctx any, opts ...any) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]any{ctx}, opts...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteServerVersion", reflect.TypeOf((*MockRegistryService)(nil).DeleteServerVersion), varargs...) +} + // GetRegistry mocks base method. func (m *MockRegistryService) GetRegistry(ctx context.Context) (*registry.UpstreamRegistry, string, error) { m.ctrl.T.Helper() diff --git a/internal/service/service.go b/internal/service/service.go index 51c3005..db0b680 100644 --- a/internal/service/service.go +++ b/internal/service/service.go @@ -16,6 +16,10 @@ var ( ErrServerNotFound = errors.New("server not found") // ErrNotImplemented is returned when a feature is not implemented ErrNotImplemented = errors.New("not implemented") + // ErrRegistryNotFound is returned when a registry is not found + ErrRegistryNotFound = errors.New("registry not found") + // ErrNotManagedRegistry is returned when attempting write operations on a non-managed registry + ErrNotManagedRegistry = errors.New("registry is not managed") ) //go:generate mockgen -destination=mocks/mock_service.go -package=mocks -source=service.go Service @@ -36,10 +40,16 @@ type RegistryService interface { // GetServer returns a specific server by name GetServerVersion(ctx context.Context, opts ...Option[GetServerVersionOptions]) (*upstreamv0.ServerJSON, error) + + // DeleteServerVersion removes a server version from a managed registry + DeleteServerVersion(ctx context.Context, opts ...Option[DeleteServerVersionOptions]) error } -// Option is a function that sets an option for the ListServersOptions, ListServerVersionsOptions, or GetServerVersionOptions -type Option[T ListServersOptions | ListServerVersionsOptions | GetServerVersionOptions] func(*T) error +// Option is a function that sets an option for the ListServersOptions, ListServerVersionsOptions, +// GetServerVersionOptions, or DeleteServerVersionOptions +type Option[ + T ListServersOptions | ListServerVersionsOptions | GetServerVersionOptions | DeleteServerVersionOptions, +] func(*T) error // ListServersOptions is the options for the ListServers operation type ListServersOptions struct { @@ -67,6 +77,13 @@ type GetServerVersionOptions struct { Version string } +// DeleteServerVersionOptions is the options for the DeleteServerVersion operation +type DeleteServerVersionOptions struct { + RegistryName string + ServerName string + Version string +} + // WithCursor sets the cursor for the ListServers operation func WithCursor(cursor string) Option[ListServersOptions] { return func(o *ListServersOptions) error { @@ -100,8 +117,9 @@ func WithUpdatedSince(updatedSince time.Time) Option[ListServersOptions] { } } -// WithRegistryName sets the registry name for the ListServers, ListServerVersions, or GetServerVersion operation -func WithRegistryName[T ListServersOptions | ListServerVersionsOptions | GetServerVersionOptions]( +// WithRegistryName sets the registry name for the ListServers, ListServerVersions, +// GetServerVersion, or DeleteServerVersion operation +func WithRegistryName[T ListServersOptions | ListServerVersionsOptions | GetServerVersionOptions | DeleteServerVersionOptions]( registryName string, ) Option[T] { return func(o *T) error { @@ -115,6 +133,8 @@ func WithRegistryName[T ListServersOptions | ListServerVersionsOptions | GetServ o.RegistryName = ®istryName case *GetServerVersionOptions: o.RegistryName = ®istryName + case *DeleteServerVersionOptions: + o.RegistryName = registryName default: return fmt.Errorf("invalid option type: %T", o) } @@ -144,8 +164,9 @@ func WithPrev(prev time.Time) Option[ListServerVersionsOptions] { } } -// WithVersion sets the version for the ListServers or GetServerVersion operation -func WithVersion[T ListServersOptions | GetServerVersionOptions](version string) Option[T] { +// WithVersion sets the version for the ListServers, GetServerVersion, +// or DeleteServerVersion operation +func WithVersion[T ListServersOptions | GetServerVersionOptions | DeleteServerVersionOptions](version string) Option[T] { return func(o *T) error { if version == "" { return fmt.Errorf("invalid version: %s", version) @@ -156,6 +177,8 @@ func WithVersion[T ListServersOptions | GetServerVersionOptions](version string) o.Version = version case *GetServerVersionOptions: o.Version = version + case *DeleteServerVersionOptions: + o.Version = version default: return fmt.Errorf("invalid option type: %T", o) } @@ -164,8 +187,9 @@ func WithVersion[T ListServersOptions | GetServerVersionOptions](version string) } } -// WithName sets the name for the ListServerVersions or GetServerVersion operation -func WithName[T ListServerVersionsOptions | GetServerVersionOptions](name string) Option[T] { +// WithName sets the name for the ListServerVersions, GetServerVersion, +// or DeleteServerVersion operation +func WithName[T ListServerVersionsOptions | GetServerVersionOptions | DeleteServerVersionOptions](name string) Option[T] { return func(o *T) error { if name == "" { return fmt.Errorf("invalid name: %s", name) @@ -176,6 +200,8 @@ func WithName[T ListServerVersionsOptions | GetServerVersionOptions](name string o.Name = name case *GetServerVersionOptions: o.Name = name + case *DeleteServerVersionOptions: + o.ServerName = name default: return fmt.Errorf("invalid option type: %T", o) }