Skip to content

Commit 0f72676

Browse files
committed
Add endpoints for listing and registering clients
Ref #612
1 parent 9f52ce7 commit 0f72676

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
@@ -290,6 +302,32 @@ components:
290302
type: array
291303
uniqueItems: false
292304
type: object
305+
v1.createClientRequest:
306+
properties:
307+
name:
308+
description: Name is the type of the client to register.
309+
type: string
310+
x-enum-varnames:
311+
- RooCode
312+
- Cline
313+
- Cursor
314+
- VSCodeInsider
315+
- VSCode
316+
- ClaudeCode
317+
type: object
318+
v1.createClientResponse:
319+
properties:
320+
name:
321+
description: Name is the type of the client that was registered.
322+
type: string
323+
x-enum-varnames:
324+
- RooCode
325+
- Cline
326+
- Cursor
327+
- VSCodeInsider
328+
- VSCode
329+
- ClaudeCode
330+
type: object
293331
v1.createRequest:
294332
description: Request to create a new server
295333
properties:
@@ -464,6 +502,46 @@ paths:
464502
summary: Get OpenAPI specification
465503
tags:
466504
- system
505+
/api/v1beta/clients:
506+
get:
507+
description: List all registered clients in ToolHive
508+
responses:
509+
"200":
510+
content:
511+
application/json:
512+
schema:
513+
items:
514+
$ref: '#/components/schemas/client.Client'
515+
type: array
516+
description: OK
517+
summary: List all clients
518+
tags:
519+
- clients
520+
post:
521+
description: Register a new client with ToolHive
522+
requestBody:
523+
content:
524+
application/json:
525+
schema:
526+
$ref: '#/components/schemas/v1.createClientRequest'
527+
description: Client to register
528+
required: true
529+
responses:
530+
"200":
531+
content:
532+
application/json:
533+
schema:
534+
$ref: '#/components/schemas/v1.createClientResponse'
535+
description: OK
536+
"400":
537+
content:
538+
application/json:
539+
schema:
540+
type: string
541+
description: Invalid request
542+
summary: Register a new client
543+
tags:
544+
- clients
467545
/api/v1beta/discovery/clients:
468546
get:
469547
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/lifecycle"
3233
"github.com/stacklok/toolhive/pkg/logger"
@@ -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/servers": v1.ServerRouter(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)