Skip to content

Commit af6ec21

Browse files
committed
Fix REST HTTP API design
1 parent ada3249 commit af6ec21

File tree

2 files changed

+39
-90
lines changed

2 files changed

+39
-90
lines changed

internal/api/resources.go

Lines changed: 21 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -107,16 +107,10 @@ type ServerResourceTemplatesRequest struct {
107107
Cursor string `doc:"Pagination cursor" query:"cursor"`
108108
}
109109

110-
// ServerResourceReadRequest represents the incoming API request for reading a resource.
111-
type ServerResourceReadRequest struct {
112-
Name string `doc:"Name of the server" path:"name"`
113-
Body ReadResourceBody `doc:"Resource read parameters"`
114-
}
115-
116-
// ReadResourceBody contains parameters for reading a resource.
117-
type ReadResourceBody struct {
118-
URI string `doc:"URI of the resource to read" json:"uri"`
119-
Arguments map[string]any `doc:"Optional arguments for the resource" json:"arguments,omitempty"`
110+
// ServerResourceContentRequest represents the incoming API request for getting resource content.
111+
type ServerResourceContentRequest struct {
112+
Name string `doc:"Name of the server" path:"name"`
113+
URI string `doc:"URI of the resource" query:"uri"`
120114
}
121115

122116
// ResourcesResponse represents the wrapped API response for Resources.
@@ -129,8 +123,8 @@ type ResourceTemplatesResponse struct {
129123
Body ResourceTemplates
130124
}
131125

132-
// ReadResourceResponse represents the wrapped API response for reading a resource.
133-
type ReadResourceResponse struct {
126+
// ResourceContentResponse represents the wrapped API response for getting resource content.
127+
type ResourceContentResponse struct {
134128
Body []ResourceContent
135129
}
136130

@@ -313,12 +307,12 @@ func handleServerResourceTemplates(
313307
return resp, nil
314308
}
315309

316-
// handleServerResourceRead reads a specific resource from a server.
317-
func handleServerResourceRead(
310+
// handleServerResourceContent gets the content of a specific resource from a server.
311+
func handleServerResourceContent(
318312
accessor contracts.MCPClientAccessor,
319313
name string,
320-
body ReadResourceBody,
321-
) (*ReadResourceResponse, error) {
314+
uri string,
315+
) (*ResourceContentResponse, error) {
322316
mcpClient, clientOk := accessor.Client(name)
323317
if !clientOk {
324318
return nil, fmt.Errorf("%w: %s", errors.ErrServerNotFound, name)
@@ -329,8 +323,7 @@ func handleServerResourceRead(
329323

330324
result, err := mcpClient.ReadResource(ctx, mcp.ReadResourceRequest{
331325
Params: mcp.ReadResourceParams{
332-
URI: body.URI,
333-
Arguments: body.Arguments,
326+
URI: uri,
334327
},
335328
})
336329
if err != nil {
@@ -340,10 +333,10 @@ func handleServerResourceRead(
340333
if strings.Contains(err.Error(), methodNotFoundMessage) {
341334
return nil, fmt.Errorf("%w: %s", errors.ErrResourcesNotImplemented, name)
342335
}
343-
return nil, fmt.Errorf("%w: %s: %s: %w", errors.ErrResourceReadFailed, name, body.URI, err)
336+
return nil, fmt.Errorf("%w: %s: %s: %w", errors.ErrResourceReadFailed, name, uri, err)
344337
}
345338
if result == nil {
346-
return nil, fmt.Errorf("%w: %s: %s: no result", errors.ErrResourceReadFailed, name, body.URI)
339+
return nil, fmt.Errorf("%w: %s: %s: no result", errors.ErrResourceReadFailed, name, uri)
347340
}
348341

349342
contents := make([]ResourceContent, 0, len(result.Contents))
@@ -382,7 +375,7 @@ func handleServerResourceRead(
382375
}
383376
}
384377

385-
resp := &ReadResourceResponse{}
378+
resp := &ResourceContentResponse{}
386379
resp.Body = contents
387380

388381
return resp, nil
@@ -423,14 +416,15 @@ func RegisterResourceRoutes(serversAPI huma.API, accessor contracts.MCPClientAcc
423416
huma.Register(
424417
serversAPI,
425418
huma.Operation{
426-
OperationID: "readResource",
427-
Method: "POST",
428-
Path: "/{name}/resources/read",
429-
Summary: "Read a resource from a server",
419+
OperationID: "getResourceContent",
420+
Method: "GET",
421+
Path: "/{name}/resources/content",
422+
Summary: "Get resource content from a server",
423+
Description: "Retrieves the content of a resource by URI",
430424
Tags: tags,
431425
},
432-
func(ctx context.Context, input *ServerResourceReadRequest) (*ReadResourceResponse, error) {
433-
return handleServerResourceRead(accessor, input.Name, input.Body)
426+
func(ctx context.Context, input *ServerResourceContentRequest) (*ResourceContentResponse, error) {
427+
return handleServerResourceContent(accessor, input.Name, input.URI)
434428
},
435429
)
436430
}

internal/api/resources_test.go

Lines changed: 18 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,7 @@ func TestAPI_HandleServerResourceTemplates_ServerNotFound(t *testing.T) {
196196
require.True(t, errors.Is(err, internalerrors.ErrServerNotFound))
197197
}
198198

199-
func TestAPI_HandleServerResourceRead_TextContent(t *testing.T) {
199+
func TestAPI_HandleServerResourceContent_TextContent(t *testing.T) {
200200
t.Parallel()
201201

202202
mockClient := &mockMCPClient{
@@ -214,11 +214,9 @@ func TestAPI_HandleServerResourceRead_TextContent(t *testing.T) {
214214
accessor := newMockMCPClientAccessor()
215215
accessor.Add("test-server", mockClient, []string{})
216216

217-
body := ReadResourceBody{
218-
URI: "file:///test.txt",
219-
}
217+
uri := "file:///test.txt"
220218

221-
result, err := handleServerResourceRead(accessor, "test-server", body)
219+
result, err := handleServerResourceContent(accessor, "test-server", uri)
222220

223221
require.NoError(t, err)
224222
require.NotNil(t, result)
@@ -228,7 +226,7 @@ func TestAPI_HandleServerResourceRead_TextContent(t *testing.T) {
228226
require.Empty(t, result.Body[0].Blob)
229227
}
230228

231-
func TestAPI_HandleServerResourceRead_BlobContent(t *testing.T) {
229+
func TestAPI_HandleServerResourceContent_BlobContent(t *testing.T) {
232230
t.Parallel()
233231

234232
mockClient := &mockMCPClient{
@@ -246,11 +244,9 @@ func TestAPI_HandleServerResourceRead_BlobContent(t *testing.T) {
246244
accessor := newMockMCPClientAccessor()
247245
accessor.Add("test-server", mockClient, []string{})
248246

249-
body := ReadResourceBody{
250-
URI: "file:///image.png",
251-
}
247+
uri := "file:///image.png"
252248

253-
result, err := handleServerResourceRead(accessor, "test-server", body)
249+
result, err := handleServerResourceContent(accessor, "test-server", uri)
254250

255251
require.NoError(t, err)
256252
require.NotNil(t, result)
@@ -260,7 +256,7 @@ func TestAPI_HandleServerResourceRead_BlobContent(t *testing.T) {
260256
require.Empty(t, result.Body[0].Text)
261257
}
262258

263-
func TestAPI_HandleServerResourceRead_MultipleContents(t *testing.T) {
259+
func TestAPI_HandleServerResourceContent_MultipleContents(t *testing.T) {
264260
t.Parallel()
265261

266262
mockClient := &mockMCPClient{
@@ -283,11 +279,9 @@ func TestAPI_HandleServerResourceRead_MultipleContents(t *testing.T) {
283279
accessor := newMockMCPClientAccessor()
284280
accessor.Add("test-server", mockClient, []string{})
285281

286-
body := ReadResourceBody{
287-
URI: "file:///multi",
288-
}
282+
uri := "file:///multi"
289283

290-
result, err := handleServerResourceRead(accessor, "test-server", body)
284+
result, err := handleServerResourceContent(accessor, "test-server", uri)
291285

292286
require.NoError(t, err)
293287
require.NotNil(t, result)
@@ -296,56 +290,21 @@ func TestAPI_HandleServerResourceRead_MultipleContents(t *testing.T) {
296290
require.Equal(t, "YmluYXJ5", result.Body[1].Blob)
297291
}
298292

299-
func TestAPI_HandleServerResourceRead_WithArguments(t *testing.T) {
293+
func TestAPI_HandleServerResourceContent_ServerNotFound(t *testing.T) {
300294
t.Parallel()
301295

302-
mockClient := &mockMCPClient{
303-
readResourceResult: &mcp.ReadResourceResult{
304-
Contents: []mcp.ResourceContents{
305-
mcp.TextResourceContents{
306-
URI: "api:///data",
307-
Text: "Parameterized data",
308-
},
309-
},
310-
},
311-
}
312-
313296
accessor := newMockMCPClientAccessor()
314-
accessor.Add("test-server", mockClient, []string{})
315297

316-
body := ReadResourceBody{
317-
URI: "api:///data",
318-
Arguments: map[string]any{
319-
"format": "json",
320-
"limit": 10,
321-
},
322-
}
298+
uri := "file:///test.txt"
323299

324-
result, err := handleServerResourceRead(accessor, "test-server", body)
325-
326-
require.NoError(t, err)
327-
require.NotNil(t, result)
328-
require.Len(t, result.Body, 1)
329-
require.Equal(t, "Parameterized data", result.Body[0].Text)
330-
}
331-
332-
func TestAPI_HandleServerResourceRead_ServerNotFound(t *testing.T) {
333-
t.Parallel()
334-
335-
accessor := newMockMCPClientAccessor()
336-
337-
body := ReadResourceBody{
338-
URI: "file:///test.txt",
339-
}
340-
341-
result, err := handleServerResourceRead(accessor, "nonexistent-server", body)
300+
result, err := handleServerResourceContent(accessor, "nonexistent-server", uri)
342301

343302
require.Error(t, err)
344303
require.Nil(t, result)
345304
require.True(t, errors.Is(err, internalerrors.ErrServerNotFound))
346305
}
347306

348-
func TestAPI_HandleServerResourceRead_ReadError(t *testing.T) {
307+
func TestAPI_HandleServerResourceContent_ReadError(t *testing.T) {
349308
t.Parallel()
350309

351310
mockClient := &mockMCPClient{
@@ -355,18 +314,16 @@ func TestAPI_HandleServerResourceRead_ReadError(t *testing.T) {
355314
accessor := newMockMCPClientAccessor()
356315
accessor.Add("test-server", mockClient, []string{})
357316

358-
body := ReadResourceBody{
359-
URI: "file:///nonexistent.txt",
360-
}
317+
uri := "file:///nonexistent.txt"
361318

362-
result, err := handleServerResourceRead(accessor, "test-server", body)
319+
result, err := handleServerResourceContent(accessor, "test-server", uri)
363320

364321
require.Error(t, err)
365322
require.Nil(t, result)
366323
require.True(t, errors.Is(err, internalerrors.ErrResourceReadFailed))
367324
}
368325

369-
func TestAPI_HandleServerResourceRead_NilResult(t *testing.T) {
326+
func TestAPI_HandleServerResourceContent_NilResult(t *testing.T) {
370327
t.Parallel()
371328

372329
mockClient := &mockMCPClient{
@@ -376,11 +333,9 @@ func TestAPI_HandleServerResourceRead_NilResult(t *testing.T) {
376333
accessor := newMockMCPClientAccessor()
377334
accessor.Add("test-server", mockClient, []string{})
378335

379-
body := ReadResourceBody{
380-
URI: "file:///test.txt",
381-
}
336+
uri := "file:///test.txt"
382337

383-
result, err := handleServerResourceRead(accessor, "test-server", body)
338+
result, err := handleServerResourceContent(accessor, "test-server", uri)
384339

385340
require.Error(t, err)
386341
require.Nil(t, result)

0 commit comments

Comments
 (0)