Skip to content

Commit ac59341

Browse files
eleftheriasrdimitrov
authored andcommitted
Add endpoints for listing and registering clients
Ref #612
1 parent 5d9a734 commit ac59341

File tree

7 files changed

+349
-6
lines changed

7 files changed

+349
-6
lines changed

docs/server/docs.go

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/server/swagger.json

Lines changed: 2 additions & 2 deletions
Large diffs are not rendered by default.

docs/server/swagger.yaml

Lines changed: 80 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
components:
22
schemas:
3+
client.Client:
4+
properties:
5+
name:
6+
$ref: '#/components/schemas/client.MCPClient'
7+
type: object
38
client.MCPClient:
4-
description: ClientType is the type of MCP client
59
type: string
610
x-enum-varnames:
711
- RooCode
@@ -13,7 +17,15 @@ components:
1317
client.MCPClientStatus:
1418
properties:
1519
client_type:
16-
$ref: '#/components/schemas/client.MCPClient'
20+
description: ClientType is the type of MCP client
21+
type: string
22+
x-enum-varnames:
23+
- RooCode
24+
- Cline
25+
- Cursor
26+
- VSCodeInsider
27+
- VSCode
28+
- ClaudeCode
1729
installed:
1830
description: Installed indicates whether the client is installed on the
1931
system
@@ -247,6 +259,32 @@ components:
247259
type: array
248260
uniqueItems: false
249261
type: object
262+
v1.createClientRequest:
263+
properties:
264+
name:
265+
description: Name is the type of the client to register.
266+
type: string
267+
x-enum-varnames:
268+
- RooCode
269+
- Cline
270+
- Cursor
271+
- VSCodeInsider
272+
- VSCode
273+
- ClaudeCode
274+
type: object
275+
v1.createClientResponse:
276+
properties:
277+
name:
278+
description: Name is the type of the client that was registered.
279+
type: string
280+
x-enum-varnames:
281+
- RooCode
282+
- Cline
283+
- Cursor
284+
- VSCodeInsider
285+
- VSCode
286+
- ClaudeCode
287+
type: object
250288
v1.createRequest:
251289
description: Request to create a new workload
252290
properties:
@@ -465,6 +503,46 @@ paths:
465503
summary: Get OpenAPI specification
466504
tags:
467505
- system
506+
/api/v1beta/clients:
507+
get:
508+
description: List all registered clients in ToolHive
509+
responses:
510+
"200":
511+
content:
512+
application/json:
513+
schema:
514+
items:
515+
$ref: '#/components/schemas/client.Client'
516+
type: array
517+
description: OK
518+
summary: List all clients
519+
tags:
520+
- clients
521+
post:
522+
description: Register a new client with ToolHive
523+
requestBody:
524+
content:
525+
application/json:
526+
schema:
527+
$ref: '#/components/schemas/v1.createClientRequest'
528+
description: Client to register
529+
required: true
530+
responses:
531+
"200":
532+
content:
533+
application/json:
534+
schema:
535+
$ref: '#/components/schemas/v1.createClientResponse'
536+
description: OK
537+
"400":
538+
content:
539+
application/json:
540+
schema:
541+
type: string
542+
description: Invalid request
543+
summary: Register a new client
544+
tags:
545+
- clients
468546
/api/v1beta/discovery/clients:
469547
get:
470548
description: List all clients compatible with ToolHive and their status

pkg/api/server.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import (
2727

2828
v1 "github.com/stacklok/toolhive/pkg/api/v1"
2929
"github.com/stacklok/toolhive/pkg/auth"
30+
"github.com/stacklok/toolhive/pkg/client"
3031
"github.com/stacklok/toolhive/pkg/container"
3132
"github.com/stacklok/toolhive/pkg/logger"
3233
"github.com/stacklok/toolhive/pkg/workloads"
@@ -113,12 +114,17 @@ func Serve(
113114
return fmt.Errorf("failed to create container runtime: %v", err)
114115
}
115116

117+
clientManager, err := client.NewManager(ctx)
118+
if err != nil {
119+
return fmt.Errorf("failed to create client manager: %v", err)
120+
}
116121
routers := map[string]http.Handler{
117122
"/health": v1.HealthcheckRouter(),
118123
"/api/v1beta/version": v1.VersionRouter(),
119124
"/api/v1beta/workloads": v1.WorkloadRouter(manager, rt, debugMode),
120125
"/api/v1beta/registry": v1.RegistryRouter(),
121126
"/api/v1beta/discovery": v1.DiscoveryRouter(),
127+
"/api/v1beta/clients": v1.ClientRouter(clientManager),
122128
}
123129

124130
// Only mount docs router if enabled

pkg/api/v1/clients.go

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
package v1
2+
3+
import (
4+
"encoding/json"
5+
"net/http"
6+
7+
"github.com/go-chi/chi/v5"
8+
9+
"github.com/stacklok/toolhive/pkg/client"
10+
"github.com/stacklok/toolhive/pkg/logger"
11+
)
12+
13+
// ClientRoutes defines the routes for the client API.
14+
type ClientRoutes struct {
15+
manager client.Manager
16+
}
17+
18+
// ClientRouter creates a new router for the client API.
19+
func ClientRouter(
20+
manager client.Manager,
21+
) http.Handler {
22+
routes := ClientRoutes{
23+
manager: manager,
24+
}
25+
26+
r := chi.NewRouter()
27+
r.Get("/", routes.listClients)
28+
r.Post("/", routes.registerClient)
29+
return r
30+
}
31+
32+
// listClients
33+
//
34+
// @Summary List all clients
35+
// @Description List all registered clients in ToolHive
36+
// @Tags clients
37+
// @Produce json
38+
// @Success 200 {array} client.Client
39+
// @Router /api/v1beta/clients [get]
40+
func (c *ClientRoutes) listClients(w http.ResponseWriter, _ *http.Request) {
41+
clients, err := c.manager.ListClients()
42+
if err != nil {
43+
logger.Errorf("Failed to list clients: %v", err)
44+
http.Error(w, "Failed to list clients", http.StatusInternalServerError)
45+
return
46+
}
47+
48+
err = json.NewEncoder(w).Encode(clients)
49+
if err != nil {
50+
http.Error(w, "Failed to encode client list", http.StatusInternalServerError)
51+
return
52+
}
53+
}
54+
55+
// registerClient
56+
//
57+
// @Summary Register a new client
58+
// @Description Register a new client with ToolHive
59+
// @Tags clients
60+
// @Accept json
61+
// @Produce json
62+
// @Param client body createClientRequest true "Client to register"
63+
// @Success 200 {object} createClientResponse
64+
// @Failure 400 {string} string "Invalid request"
65+
// @Router /api/v1beta/clients [post]
66+
func (c *ClientRoutes) registerClient(w http.ResponseWriter, r *http.Request) {
67+
var newClient createClientRequest
68+
err := json.NewDecoder(r.Body).Decode(&newClient)
69+
if err != nil {
70+
logger.Errorf("Failed to decode request body: %v", err)
71+
http.Error(w, "Invalid request body", http.StatusBadRequest)
72+
return
73+
}
74+
75+
err = c.manager.RegisterClient(r.Context(), client.Client{
76+
Name: newClient.Name,
77+
})
78+
if err != nil {
79+
logger.Errorf("Failed to register client: %v", err)
80+
http.Error(w, "Failed to register client", http.StatusInternalServerError)
81+
return
82+
}
83+
84+
resp := createClientResponse(newClient)
85+
if err = json.NewEncoder(w).Encode(resp); err != nil {
86+
http.Error(w, "Failed to marshal server details", http.StatusInternalServerError)
87+
return
88+
}
89+
}
90+
91+
type createClientRequest struct {
92+
// Name is the type of the client to register.
93+
Name client.MCPClient `json:"name"`
94+
}
95+
96+
type createClientResponse struct {
97+
// Name is the type of the client that was registered.
98+
Name client.MCPClient `json:"name"`
99+
}

pkg/client/config.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,22 @@ type MCPServerConfig struct {
149149
URL string `json:"url,omitempty"`
150150
}
151151

152+
// FindClientConfig returns the client configuration file for a given client type.
153+
func FindClientConfig(clientType MCPClient) (*ConfigFile, error) {
154+
configFiles, err := FindClientConfigs()
155+
if err != nil {
156+
return nil, fmt.Errorf("failed to fetch client configurations: %w", err)
157+
}
158+
159+
for _, cf := range configFiles {
160+
if cf.ClientType == clientType {
161+
return &cf, nil
162+
}
163+
}
164+
165+
return nil, fmt.Errorf("client configuration for %s not found", clientType)
166+
}
167+
152168
// FindClientConfigs searches for client configuration files in standard locations
153169
func FindClientConfigs() ([]ConfigFile, error) {
154170
// Start by assuming all clients are enabled

0 commit comments

Comments
 (0)