Skip to content

Commit 36b1e47

Browse files
authored
Merge pull request #24 from datum-cloud/feat/networkingapis
feat: http routes, gateways, and traffic protection policies
2 parents a08cfe9 + 1dbf0f1 commit 36b1e47

File tree

2 files changed

+210
-2
lines changed

2 files changed

+210
-2
lines changed

README.md

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010

1111
# datum-mcp
1212

13-
An MCP server for Datum Cloud with OAuth 2.1 (PKCE) auth, macOS Keychain token storage, and tools for listing/operating on organizations, projects, domains, HTTP proxies, and CRD schemas.
13+
An MCP server for Datum Cloud with OAuth 2.1 (PKCE) auth, macOS Keychain token storage, and tools for listing/operating on organizations, projects, domains, HTTP proxies, HTTP routes, gateways, traffic protection policies, and CRD schemas.
1414

1515
## Installation
1616

@@ -153,6 +153,19 @@ All tools accept JSON inputs and return both structured content and a pretty-pri
153153
- httpproxies
154154
- Same shape and behavior as `domains` (namespaced list/get/create/update; delete by name).
155155

156+
- httproutes
157+
- Same shape and behavior as `domains` (namespaced list/get/create/update; delete by name).
158+
- Targets Gateway API HTTPRoute resources.
159+
160+
- gateways
161+
- Same shape and behavior as `domains` (namespaced list/get/create/update; delete by name).
162+
- Targets Gateway API Gateway resources.
163+
164+
- trafficprotectionpolicies
165+
- Same shape and behavior as `domains` (namespaced list/get/create/update; delete by name).
166+
- Policies are intended to target either `Gateway` or `HTTPRoute` resources.
167+
- Group/kind: `networking.datumapis.com` / `TrafficProtectionPolicy`.
168+
156169
- apis (CRDs list/describe via upstream OpenAPI/`kubectl explain` logic)
157170
- **Actions**: `list` | `get`
158171
- **Input**:
@@ -167,5 +180,5 @@ All tools accept JSON inputs and return both structured content and a pretty-pri
167180
2. `organizations` → set active org
168181
3. `projects` → list for an org
169182
4. `projects` → set active project
170-
5. Use `domains` / `httpproxies` for CRUD, or `apis` to inspect CRD schemas
183+
5. Use `domains` / `httpproxies` / `httproutes` / `gateways` / `trafficprotectionpolicies` for CRUD, or `apis` to inspect CRD schemas
171184

internal/server/server.go

Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -396,6 +396,198 @@ func toolHTTPProxies(ctx context.Context, _ *mcp.CallToolRequest, in RoutedInput
396396
}
397397
}
398398

399+
// HTTP Routes tool supports: list|get|create|update|delete
400+
// Group/Kind follows Gateway API: gateway.networking.k8s.io/HTTPRoute
401+
func toolHTTPRoutes(ctx context.Context, _ *mcp.CallToolRequest, in RoutedInput) (*mcp.CallToolResult, any, error) {
402+
_, err := auth.EnsureAuth(ctx)
403+
if err != nil {
404+
return &mcp.CallToolResult{IsError: true}, nil, err
405+
}
406+
p, err := resolveProjectName(in.Project)
407+
if err != nil {
408+
return &mcp.CallToolResult{IsError: true}, nil, err
409+
}
410+
cli, err := api.NewProjectControlPlaneClient(ctx, p)
411+
if err != nil {
412+
return &mcp.CallToolResult{IsError: true}, nil, err
413+
}
414+
switch strings.ToLower(string(in.Action)) {
415+
case string(ActionList):
416+
list, err := api.FetchList(ctx, cli, "gateway.networking.k8s.io", "HTTPRoute", "default")
417+
if err != nil {
418+
return &mcp.CallToolResult{IsError: true}, nil, err
419+
}
420+
b, _ := json.MarshalIndent(list, "", " ")
421+
return &mcp.CallToolResult{Content: []mcp.Content{&mcp.TextContent{Text: string(b)}}}, list, nil
422+
case string(ActionGet):
423+
if in.ID == "" {
424+
return &mcp.CallToolResult{IsError: true}, nil, fmt.Errorf("invalid params: id is required")
425+
}
426+
obj, err := api.FetchObject(ctx, cli, "gateway.networking.k8s.io", "HTTPRoute", "default", in.ID)
427+
if err != nil {
428+
return &mcp.CallToolResult{IsError: true}, nil, err
429+
}
430+
b, _ := json.MarshalIndent(obj, "", " ")
431+
return &mcp.CallToolResult{Content: []mcp.Content{&mcp.TextContent{Text: string(b)}}}, obj, nil
432+
case string(ActionCreate):
433+
obj, err := api.CreateObject(ctx, cli, "gateway.networking.k8s.io", "HTTPRoute", "default", in.Body)
434+
if err != nil {
435+
return &mcp.CallToolResult{IsError: true}, nil, err
436+
}
437+
b, _ := json.MarshalIndent(obj, "", " ")
438+
return &mcp.CallToolResult{Content: []mcp.Content{&mcp.TextContent{Text: string(b)}}}, obj, nil
439+
case string(ActionUpdate):
440+
if in.ID == "" {
441+
return &mcp.CallToolResult{IsError: true}, nil, fmt.Errorf("invalid params: id is required")
442+
}
443+
obj, err := api.UpdateObjectSpec(ctx, cli, "gateway.networking.k8s.io", "HTTPRoute", "default", in.ID, in.Body)
444+
if err != nil {
445+
return &mcp.CallToolResult{IsError: true}, nil, err
446+
}
447+
b, _ := json.MarshalIndent(obj, "", " ")
448+
return &mcp.CallToolResult{Content: []mcp.Content{&mcp.TextContent{Text: string(b)}}}, obj, nil
449+
case string(ActionDelete):
450+
if in.ID == "" {
451+
return &mcp.CallToolResult{IsError: true}, nil, fmt.Errorf("invalid params: id is required")
452+
}
453+
if err := api.DeleteObject(ctx, cli, "gateway.networking.k8s.io", "HTTPRoute", "default", in.ID); err != nil {
454+
return &mcp.CallToolResult{IsError: true}, nil, err
455+
}
456+
return &mcp.CallToolResult{Content: []mcp.Content{&mcp.TextContent{Text: "deleted"}}}, map[string]string{"deleted": in.ID}, nil
457+
default:
458+
return &mcp.CallToolResult{IsError: true}, nil, fmt.Errorf("unsupported httproutes action: %s", in.Action)
459+
}
460+
}
461+
462+
// Gateways tool supports: list|get|create|update|delete
463+
// Group/Kind follows Gateway API: gateway.networking.k8s.io/Gateway
464+
func toolGateways(ctx context.Context, _ *mcp.CallToolRequest, in RoutedInput) (*mcp.CallToolResult, any, error) {
465+
_, err := auth.EnsureAuth(ctx)
466+
if err != nil {
467+
return &mcp.CallToolResult{IsError: true}, nil, err
468+
}
469+
p, err := resolveProjectName(in.Project)
470+
if err != nil {
471+
return &mcp.CallToolResult{IsError: true}, nil, err
472+
}
473+
cli, err := api.NewProjectControlPlaneClient(ctx, p)
474+
if err != nil {
475+
return &mcp.CallToolResult{IsError: true}, nil, err
476+
}
477+
switch strings.ToLower(string(in.Action)) {
478+
case string(ActionList):
479+
list, err := api.FetchList(ctx, cli, "gateway.networking.k8s.io", "Gateway", "default")
480+
if err != nil {
481+
return &mcp.CallToolResult{IsError: true}, nil, err
482+
}
483+
b, _ := json.MarshalIndent(list, "", " ")
484+
return &mcp.CallToolResult{Content: []mcp.Content{&mcp.TextContent{Text: string(b)}}}, list, nil
485+
case string(ActionGet):
486+
if in.ID == "" {
487+
return &mcp.CallToolResult{IsError: true}, nil, fmt.Errorf("invalid params: id is required")
488+
}
489+
obj, err := api.FetchObject(ctx, cli, "gateway.networking.k8s.io", "Gateway", "default", in.ID)
490+
if err != nil {
491+
return &mcp.CallToolResult{IsError: true}, nil, err
492+
}
493+
b, _ := json.MarshalIndent(obj, "", " ")
494+
return &mcp.CallToolResult{Content: []mcp.Content{&mcp.TextContent{Text: string(b)}}}, obj, nil
495+
case string(ActionCreate):
496+
obj, err := api.CreateObject(ctx, cli, "gateway.networking.k8s.io", "Gateway", "default", in.Body)
497+
if err != nil {
498+
return &mcp.CallToolResult{IsError: true}, nil, err
499+
}
500+
b, _ := json.MarshalIndent(obj, "", " ")
501+
return &mcp.CallToolResult{Content: []mcp.Content{&mcp.TextContent{Text: string(b)}}}, obj, nil
502+
case string(ActionUpdate):
503+
if in.ID == "" {
504+
return &mcp.CallToolResult{IsError: true}, nil, fmt.Errorf("invalid params: id is required")
505+
}
506+
obj, err := api.UpdateObjectSpec(ctx, cli, "gateway.networking.k8s.io", "Gateway", "default", in.ID, in.Body)
507+
if err != nil {
508+
return &mcp.CallToolResult{IsError: true}, nil, err
509+
}
510+
b, _ := json.MarshalIndent(obj, "", " ")
511+
return &mcp.CallToolResult{Content: []mcp.Content{&mcp.TextContent{Text: string(b)}}}, obj, nil
512+
case string(ActionDelete):
513+
if in.ID == "" {
514+
return &mcp.CallToolResult{IsError: true}, nil, fmt.Errorf("invalid params: id is required")
515+
}
516+
if err := api.DeleteObject(ctx, cli, "gateway.networking.k8s.io", "Gateway", "default", in.ID); err != nil {
517+
return &mcp.CallToolResult{IsError: true}, nil, err
518+
}
519+
return &mcp.CallToolResult{Content: []mcp.Content{&mcp.TextContent{Text: "deleted"}}}, map[string]string{"deleted": in.ID}, nil
520+
default:
521+
return &mcp.CallToolResult{IsError: true}, nil, fmt.Errorf("unsupported gateways action: %s", in.Action)
522+
}
523+
}
524+
525+
// TrafficProtectionPolicies tool supports: list|get|create|update|delete
526+
// Group is assumed to be networking.datumapis.com with kind TrafficProtectionPolicy.
527+
// Policies are intended to be attached to Gateways or HTTPRoutes.
528+
func toolTrafficProtectionPolicies(ctx context.Context, _ *mcp.CallToolRequest, in RoutedInput) (*mcp.CallToolResult, any, error) {
529+
_, err := auth.EnsureAuth(ctx)
530+
if err != nil {
531+
return &mcp.CallToolResult{IsError: true}, nil, err
532+
}
533+
p, err := resolveProjectName(in.Project)
534+
if err != nil {
535+
return &mcp.CallToolResult{IsError: true}, nil, err
536+
}
537+
cli, err := api.NewProjectControlPlaneClient(ctx, p)
538+
if err != nil {
539+
return &mcp.CallToolResult{IsError: true}, nil, err
540+
}
541+
const group = "networking.datumapis.com"
542+
const kind = "TrafficProtectionPolicy"
543+
switch strings.ToLower(string(in.Action)) {
544+
case string(ActionList):
545+
list, err := api.FetchList(ctx, cli, group, kind, "default")
546+
if err != nil {
547+
return &mcp.CallToolResult{IsError: true}, nil, err
548+
}
549+
b, _ := json.MarshalIndent(list, "", " ")
550+
return &mcp.CallToolResult{Content: []mcp.Content{&mcp.TextContent{Text: string(b)}}}, list, nil
551+
case string(ActionGet):
552+
if in.ID == "" {
553+
return &mcp.CallToolResult{IsError: true}, nil, fmt.Errorf("invalid params: id is required")
554+
}
555+
obj, err := api.FetchObject(ctx, cli, group, kind, "default", in.ID)
556+
if err != nil {
557+
return &mcp.CallToolResult{IsError: true}, nil, err
558+
}
559+
b, _ := json.MarshalIndent(obj, "", " ")
560+
return &mcp.CallToolResult{Content: []mcp.Content{&mcp.TextContent{Text: string(b)}}}, obj, nil
561+
case string(ActionCreate):
562+
obj, err := api.CreateObject(ctx, cli, group, kind, "default", in.Body)
563+
if err != nil {
564+
return &mcp.CallToolResult{IsError: true}, nil, err
565+
}
566+
b, _ := json.MarshalIndent(obj, "", " ")
567+
return &mcp.CallToolResult{Content: []mcp.Content{&mcp.TextContent{Text: string(b)}}}, obj, nil
568+
case string(ActionUpdate):
569+
if in.ID == "" {
570+
return &mcp.CallToolResult{IsError: true}, nil, fmt.Errorf("invalid params: id is required")
571+
}
572+
obj, err := api.UpdateObjectSpec(ctx, cli, group, kind, "default", in.ID, in.Body)
573+
if err != nil {
574+
return &mcp.CallToolResult{IsError: true}, nil, err
575+
}
576+
b, _ := json.MarshalIndent(obj, "", " ")
577+
return &mcp.CallToolResult{Content: []mcp.Content{&mcp.TextContent{Text: string(b)}}}, obj, nil
578+
case string(ActionDelete):
579+
if in.ID == "" {
580+
return &mcp.CallToolResult{IsError: true}, nil, fmt.Errorf("invalid params: id is required")
581+
}
582+
if err := api.DeleteObject(ctx, cli, group, kind, "default", in.ID); err != nil {
583+
return &mcp.CallToolResult{IsError: true}, nil, err
584+
}
585+
return &mcp.CallToolResult{Content: []mcp.Content{&mcp.TextContent{Text: "deleted"}}}, map[string]string{"deleted": in.ID}, nil
586+
default:
587+
return &mcp.CallToolResult{IsError: true}, nil, fmt.Errorf("unsupported trafficprotectionpolicies action: %s", in.Action)
588+
}
589+
}
590+
399591
// APIs tool: list/get CRDs under the project control-plane.
400592
// - {"action":"list","project":"proj"}
401593
// - {"action":"get","project":"proj","name":"domains"}
@@ -446,6 +638,9 @@ func NewMCPServer() *mcp.Server {
446638
mcp.AddTool(s, &mcp.Tool{Name: "users", Description: "List users under the active org or 'org'. Actions: list."}, toolUsers)
447639
mcp.AddTool(s, &mcp.Tool{Name: "domains", Description: "CRUD for domains. Actions: list|get|create|update|delete. Fields: project (optional), id (for get/update/delete), body (for create/update)."}, toolDomains)
448640
mcp.AddTool(s, &mcp.Tool{Name: "httpproxies", Description: "CRUD for HTTP proxies. Actions: list|get|create|update|delete. Fields: project (optional), id (for get/update/delete), body (for create/update)."}, toolHTTPProxies)
641+
mcp.AddTool(s, &mcp.Tool{Name: "httproutes", Description: "CRUD for HTTP routes. Actions: list|get|create|update|delete. Fields: project (optional), id (for get/update/delete), body (for create/update)."}, toolHTTPRoutes)
642+
mcp.AddTool(s, &mcp.Tool{Name: "gateways", Description: "CRUD for Gateways. Actions: list|get|create|update|delete. Fields: project (optional), id (for get/update/delete), body (for create/update)."}, toolGateways)
643+
mcp.AddTool(s, &mcp.Tool{Name: "trafficprotectionpolicies", Description: "CRUD for TrafficProtectionPolicies. Actions: list|get|create|update|delete. Fields: project (optional), id (for get/update/delete), body (for create/update). Policies apply to Gateways or HTTPRoutes."}, toolTrafficProtectionPolicies)
449644
mcp.AddTool(s, &mcp.Tool{Name: "apis", Description: "List/get CRDs under the current project. Actions: list|get. Fields: project (optional), name (for get)."}, toolAPIs)
450645
return s
451646
}

0 commit comments

Comments
 (0)