MCP Server for Thunder #579
Replies: 4 comments 7 replies
-
|
+1 Lets go ahead with a feature proposal.. @shashimalcse @thiva-k also interested on this. |
Beta Was this translation helpful? Give feedback.
-
|
i was thinking exposing |
Beta Was this translation helpful? Give feedback.
-
|
Based on the recent improvements, one of the following approaches can be considered for the MCP server implementation: Directory Structure 1. Route Registration: // init.go - MCP service initialization
func Initialize(mux *http.ServeMux, ...) {
handler := newMCPHandler(...) // Create handler with dependencies
mux.HandleFunc("POST /mcp", handler.HandleMCPRequest)
}2. JSON-RPC Method Dispatch: // handler.go - Main MCP handler receives all requests
func (h *mcpHandler) HandleMCPRequest(w http.ResponseWriter, r *http.Request) {
var req JSONRPCRequest
json.NewDecoder(r.Body).Decode(&req)
// Dispatch based on JSON-RPC method
switch req.Method {
case "tools/list":
h.handleToolsList(w, r, req)
case "tools/call":
h.handleToolCall(w, r, req) // Routes to specific tool
case "resources/list":
h.handleResourcesList(w, r, req)
// ... other MCP methods
}
}3. Tool Resolution: // handler.go - MCP handler with dependencies (type varies by approach)
type mcpHandler struct {
registry *ToolRegistry
dependencies interface{} // Approach-specific: services, handlers, or HTTP client
}
// Tool call handler
func (h *mcpHandler) handleToolCall(w http.ResponseWriter, r *http.Request, req JSONRPCRequest) {
toolName := req.Params.Name // e.g., "create_application"
// Look up tool in registry
tool := h.registry.GetTool(toolName)
if tool == nil {
// Return JSON-RPC error: tool not found
return
}
// Execute tool with context, arguments, and dependencies
// Dependencies type varies by approach
response, err := tool.Execute(ctx, req.Params.Arguments, h.dependencies)
// ... return JSON-RPC response
}4. Tool Registry & Execution: // tools/registry.go - Generic tool executor interface
type ToolExecutor interface {
Execute(ctx context.Context, args map[string]interface{}, deps interface{}) (*ToolCallResponse, error)
}
type ToolRegistry struct {
tools map[string]ToolExecutor
}
func (r *ToolRegistry) RegisterTool(name string, executor ToolExecutor) {
r.tools[name] = executor
}
func (r *ToolRegistry) GetTool(name string) ToolExecutor {
return r.tools[name]
}
// Tool adapters wrap tool functions to implement ToolExecutor
// Each approach defines adapters that extract the right dependenciesThere are three feasible approaches on how we are going to provide the actual tool functionality/implementation: Approach 1: Direct Service Layer CallsMCP tools call Thunder's service layer interfaces directly. Example: // tools/application_tools.go
// Tool function receives only the dependency it needs (appService)
func createApplication(ctx context.Context, args map[string]interface{}, appService ApplicationServiceInterface) (*ToolCallResponse, error) {
appDTO := &model.ApplicationDTO{
Name: args["name"].(string),
// ... other fields
}
createdApp, svcErr := appService.CreateApplication(appDTO)
if svcErr != nil {
return nil, svcErr
}
return &ToolCallResponse{
Content: []ToolCallContent{
{Type: "text", Text: fmt.Sprintf("Created application: %s", createdApp.ID)},
},
}, nil
}Pros:
Cons:
Approach 2: Wrap Existing HTTP HandlersMCP tools call Thunder's existing HTTP handlers, reusing all the handler logic. Example: // tools/application_tools.go
func createApplication(ctx context.Context, args map[string]interface{}, appHandler *applicationHandler) (*ToolCallResponse, error) {
// Build HTTP request
body := map[string]interface{}{"name": args["name"]}
reqBody, _ := json.Marshal(body)
req := httptest.NewRequest("POST", "/api/v1/applications", bytes.NewReader(reqBody))
req.Header.Set("Content-Type", "application/json")
// Copy auth context from MCP request context to request if needed
// Invoke existing handler
w := httptest.NewRecorder()
appHandler.HandleApplicationPostRequest(w, req)
// Parse response
var response model.ApplicationDTO
json.Unmarshal(w.Body.Bytes(), &response)
return &ToolCallResponse{
Content: []ToolCallContent{
{Type: "text", Text: fmt.Sprintf("Created application: %s", response.ID)},
},
}, nil
}Pros:
Cons:
Approach 3: REST API WrapperMCP tools make HTTP calls to Thunder's REST APIs, treating it like an external service. External tools also can be used for this transformation if needed. Example: // tools/application_tools.go
func createApplication(ctx context.Context, args map[string]interface{}, client *http.Client) (*ToolCallResponse, error) {
body := map[string]interface{}{"name": args["name"]}
reqBody, _ := json.Marshal(body)
// Validate and handle token
req, _ := http.NewRequest("POST", "https://localhost:8090/api/v1/applications", bytes.NewReader(reqBody))
req.Header.Set("Authorization", "Bearer "+token)
req.Header.Set("Content-Type", "application/json")
resp, err := client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
var response model.ApplicationDTO
json.NewDecoder(resp.Body).Decode(&response)
return &ToolCallResponse{
Content: []ToolCallContent{
{Type: "text", Text: fmt.Sprintf("Created application: %s", response.ID)},
},
}, nil
}Pros:
Cons:
Thunder's security middleware already handles auth, so MCP endpoints get secured automatically and this can be also improved to handle MCP specific validations better. Thunder can already act as the OAuth 2.0 authorization server for MCP clients (https://github.com/asgardeo/thunder/blob/main/docs/guides/standards-based/oauth2-oidc/features/mcp-server-securing.md). Clients register via Given Thunder's architecture and the discussion to expose MCP server functionalities only for lower environments, please let know which approach aligns best with the requirements? |
Beta Was this translation helpful? Give feedback.
-
|
We may not need a separate runtime as of now, since we have clear and reusable service/handler layer separation in Thunder. Also it would be better in terms of UX and deployment perspective to have a single runtime. We could also provide a config to enable the MCP server only if needed within this runtime. Approach 1 may require duplication of some pre/post-processing logic that are present in HTTP handlers (e.g. pagination, DTO conversions, sanitizing, secret masking, etc.). Approach 2 reduces this redundancy since we can pass-through the MCP tool args as an HTTP req object to the existing HTTP handler and let it handle it, reusing the existing logic. But may have the overhead of HTTP object conversions and is a less cleaner approach. |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
-
It would be great we add a mcp server for thunder. The MCP server should be able to do below things mainly
Beta Was this translation helpful? Give feedback.
All reactions