From d39e7057eb581139877ecfadedf92399fb9e428c Mon Sep 17 00:00:00 2001 From: "codeglide[bot]" <223743026+codeglide[bot]@users.noreply.github.com> Date: Thu, 21 Aug 2025 08:47:57 +0000 Subject: [PATCH 1/2] feat: Updated MCP server with latest generated code --- MCP/README.md | 208 ++++++++++++++++++ MCP/config/config.go | 48 ++++ MCP/go.mod | 17 ++ MCP/go.sum | 39 ++++ MCP/main.go | 145 ++++++++++++ MCP/models/models.go | 17 ++ MCP/registry.go | 15 ++ MCP/tools/openapi/get_openapi.go | 64 ++++++ .../get_openapi_typejsonyaml.go | 64 ++++++ 9 files changed, 617 insertions(+) create mode 100644 MCP/README.md create mode 100644 MCP/config/config.go create mode 100644 MCP/go.mod create mode 100644 MCP/go.sum create mode 100644 MCP/main.go create mode 100644 MCP/models/models.go create mode 100644 MCP/registry.go create mode 100644 MCP/tools/openapi/get_openapi.go create mode 100644 MCP/tools/openapi_type_json_yaml/get_openapi_typejsonyaml.go diff --git a/MCP/README.md b/MCP/README.md new file mode 100644 index 0000000000..c9a25c10fe --- /dev/null +++ b/MCP/README.md @@ -0,0 +1,208 @@ +# Input API MCP Server + +This MCP (Model Content Protocol) server provides access to Input API API functionality through HTTP, HTTPS, and STDIO transport modes. + +## Features + +- transport mode support (HTTP and STDIO) +- Dynamic configuration through HTTP headers +- Automatic tool generation from API documentation + +## Building the Project + +1. Ensure you have Go 1.24.6 or later installed +2. Clone the repository +3. Build the project: + +```bash +go build -o mcp-server +``` + +## Running the Server + +The server can run in three modes based on the **TRANSPORT** environment variable: + +### HTTP Mode + +To run in HTTP mode, set the transport environment variable to "http" or "HTTP": + +```bash +export TRANSPORT="http" # or "HTTP" or "HTTPS" +export PORT="8181" # required +``` + +Run the server: +```bash +./mcp-server +``` + +#### Required Environment Variables for HTTP Mode: +- `TRANSPORT`: Set to "HTTP" **(Required)** +- `PORT`: Server port **(Required)** + +#### Configuration through HTTP Headers: +In HTTP mode, API configuration is provided via HTTP headers for each request: +- `API_BASE_URL`: **(Required)** Base URL for the API +- `BEARER_TOKEN`: Bearer token for authentication +- `API_KEY`: API key for authentication +- `BASIC_AUTH`: Basic authentication credentials + +Cursor mcp.json settings: + +{ + "mcpServers": { + "your-mcp-server-http": { + "url": "http://:/mcp", + "headers": { + "API_BASE_URL": "https://your-api-base-url", + "BEARER_TOKEN": "your-bearer-token" + } + } + } +} + +The server will start on the configured port with the following endpoints: +- `/mcp`: HTTP endpoint for MCP communication (requires API_BASE_URL header) +- `/`: Health check endpoint + +**Note**: At least one authentication header (BEARER_TOKEN, API_KEY, or BASIC_AUTH) should be provided unless the API explicitly doesn't require authentication. + +### HTTPS Mode + +To run in HTTPS mode, set the transport environment variable to "https" or "HTTPS": + +```bash +export TRANSPORT="https" # or "HTTPS" +export PORT="8443" # required +export CERT_FILE="./certs/cert.pem" # required +export KEY_FILE="./certs/key.pem" # required +``` + +Run the server: +```bash +./mcp-server +``` + +#### Required Environment Variables for HTTPS Mode: +- `TRANSPORT`: Set to "HTTPS" **(Required)** +- `PORT`: Server port **(Required)** +- `CERT_FILE`: Path to SSL certificate file **(Required)** +- `KEY_FILE`: Path to SSL private key file **(Required)** + +#### Configuration through HTTP Headers: +In HTTPS mode, API configuration is provided via HTTP headers for each request: +- `API_BASE_URL`: **(Required)** Base URL for the API +- `BEARER_TOKEN`: Bearer token for authentication +- `API_KEY`: API key for authentication +- `BASIC_AUTH`: Basic authentication credentials + +Cursor mcp.json settings: + +{ + "mcpServers": { + "your-mcp-server-https": { + "url": "https://:/mcp", + "headers": { + "API_BASE_URL": "https://your-api-base-url", + "BEARER_TOKEN": "your-bearer-token" + } + } + } +} + +The server will start on the configured port with the following endpoints: +- `/mcp`: HTTPS endpoint for MCP communication (requires API_BASE_URL header) +- `/`: Health check endpoint + +**Note**: At least one authentication header (BEARER_TOKEN, API_KEY, or BASIC_AUTH) should be provided unless the API explicitly doesn't require authentication. + +``` + +### STDIO Mode + +To run in STDIO mode, either set the transport environment variable to "stdio" or leave it unset (default): + +```bash +export TRANSPORT="stdio" # or leave unset for default +export API_BASE_URL="https://your-api-base-url" +export BEARER_TOKEN="your-bearer-token" +``` + +Run the server: +```bash +./mcp-server +``` + +#### Required Environment Variables for STDIO Mode: +- `TRANSPORT`: Set to "stdio" or leave unset (default) +- `API_BASE_URL`: Base URL for the API **(Required)** +- `BEARER_TOKEN`: Bearer token for authentication +- `API_KEY`: API key for authentication +- `BASIC_AUTH`: Basic authentication credentials + +**Note**: At least one authentication environment variable (BEARER_TOKEN, API_KEY, or BASIC_AUTH) should be provided unless the API explicitly doesn't require authentication. + +Cursor mcp.json settings: + +{ + "mcpServers": { + "your-mcp-server-stdio": { + "command": "/", + "env": { + "API_BASE_URL": "", + "BEARER_TOKEN": "" + } + } + } +} + +## Environment Variable Case Sensitivity + +The server supports both uppercase and lowercase transport environment variables: +- `TRANSPORT` (uppercase) - checked first +- `transport` (lowercase) - fallback if uppercase not set + +Valid values: "http", "HTTP", "https", "HTTPS", "stdio", or unset (defaults to STDIO) + +## Authentication + +### HTTP Mode +Authentication is provided through HTTP headers on each request: +- `BEARER_TOKEN`: Bearer token +- `API_KEY`: API key +- `BASIC_AUTH`: Basic authentication + +### STDIO Mode +Authentication is provided through environment variables: +- `BEARER_TOKEN`: Bearer token +- `API_KEY`: API key +- `BASIC_AUTH`: Basic authentication + +## Health Check + +When running in HTTP mode, you can check server health at the root endpoint (`/`). +Expected response: `{"status":"ok"}` + +## Transport Modes Summary + +### HTTP Mode (TRANSPORT=http or TRANSPORT=HTTP) +- Uses streamable HTTP server +- Configuration provided via HTTP headers for each request +- Requires API_BASE_URL header for each request +- Endpoint: `/mcp` +- Port configured via PORT environment variable (defaults to 8080) + +### HTTPS Mode (TRANSPORT=https or TRANSPORT=HTTPS) +- Uses streamable HTTPS server with SSL/TLS encryption +- Configuration provided via HTTP headers for each request +- Requires API_BASE_URL header for each request +- Endpoint: `/mcp` +- Port configured via PORT environment variable (defaults to 8443) +- **Requires SSL certificate and private key files (CERT_FILE and KEY_FILE)** + +### STDIO Mode (TRANSPORT=stdio or unset) +- Uses standard input/output for communication +- Configuration through environment variables only +- Requires API_BASE_URL environment variable +- Suitable for command-line usage + diff --git a/MCP/config/config.go b/MCP/config/config.go new file mode 100644 index 0000000000..d8fc8131a8 --- /dev/null +++ b/MCP/config/config.go @@ -0,0 +1,48 @@ +package config + +import ( + "fmt" + "os" +) + +type APIConfig struct { + BaseURL string + BearerToken string // For OAuth2/Bearer authentication + APIKey string // For API key authentication + BasicAuth string // For basic authentication + Port string // For server port configuration +} + +func LoadAPIConfig() (*APIConfig, error) { + // Check port environment variable (both uppercase and lowercase) + port := os.Getenv("PORT") + if port == "" { + port = os.Getenv("port") + } + + baseURL := os.Getenv("API_BASE_URL") + + // Check transport environment variable (both uppercase and lowercase) + transport := os.Getenv("TRANSPORT") + if transport == "" { + transport = os.Getenv("transport") + } + + // For STDIO mode (transport is not "http"/"HTTP"/"https"/"HTTPS"), API_BASE_URL is required from environment + if transport != "http" && transport != "HTTP" && transport != "https" && transport != "HTTPS" && baseURL == "" { + return nil, fmt.Errorf("API_BASE_URL environment variable not set") + } + + // For HTTP/HTTPS mode (transport is "http"/"HTTP"/"https"/"HTTPS"), API_BASE_URL comes from headers + // so we don't require it from environment variables + + return &APIConfig{ + BaseURL: baseURL, + BearerToken: os.Getenv("BEARER_TOKEN"), + APIKey: os.Getenv("API_KEY"), + BasicAuth: os.Getenv("BASIC_AUTH"), + Port: port, + }, nil +} + + diff --git a/MCP/go.mod b/MCP/go.mod new file mode 100644 index 0000000000..0b1ce3f738 --- /dev/null +++ b/MCP/go.mod @@ -0,0 +1,17 @@ +module github.com/input-api/mcp-server + +go 1.24.4 + +require github.com/mark3labs/mcp-go v0.38.0 + +require ( + github.com/bahlo/generic-list-go v0.2.0 // indirect + github.com/buger/jsonparser v1.1.1 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/invopop/jsonschema v0.13.0 // indirect + github.com/mailru/easyjson v0.7.7 // indirect + github.com/spf13/cast v1.7.1 // indirect + github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect + github.com/yosida95/uritemplate/v3 v3.0.2 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/MCP/go.sum b/MCP/go.sum new file mode 100644 index 0000000000..05498e4d40 --- /dev/null +++ b/MCP/go.sum @@ -0,0 +1,39 @@ +github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk= +github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg= +github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs= +github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= +github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/invopop/jsonschema v0.13.0 h1:KvpoAJWEjR3uD9Kbm2HWJmqsEaHt8lBUpd0qHcIi21E= +github.com/invopop/jsonschema v0.13.0/go.mod h1:ffZ5Km5SWWRAIN6wbDXItl95euhFz2uON45H2qjYt+0= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mark3labs/mcp-go v0.38.0 h1:E5tmJiIXkhwlV0pLAwAT0O5ZjUZSISE/2Jxg+6vpq4I= +github.com/mark3labs/mcp-go v0.38.0/go.mod h1:T7tUa2jO6MavG+3P25Oy/jR7iCeJPHImCZHRymCn39g= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= +github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y= +github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc= +github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw= +github.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zIM+UJPGz4= +github.com/yosida95/uritemplate/v3 v3.0.2/go.mod h1:ILOh0sOhIJR3+L/8afwt/kE++YT040gmv5BQTMR2HP4= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/MCP/main.go b/MCP/main.go new file mode 100644 index 0000000000..94f7337bae --- /dev/null +++ b/MCP/main.go @@ -0,0 +1,145 @@ +package main + +import ( + "context" + "log" + "net" + "net/http" + "os" + "os/signal" + "syscall" + "time" + + "github.com/mark3labs/mcp-go/server" + "github.com/input-api/mcp-server/config" +) + +func main() { + cfg, err := config.LoadAPIConfig() + if err != nil { + log.Fatalf("Failed to load config: %v", err) + } + + // Check transport environment variable (both uppercase and lowercase) + transport := os.Getenv("TRANSPORT") + if transport == "" { + transport = os.Getenv("transport") + } + sigChan := make(chan os.Signal, 1) + signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM) + + // HTTP/HTTPS Mode - if transport is "http", "HTTP", "https", or "HTTPS" + if transport == "http" || transport == "HTTP" || transport == "https" || transport == "HTTPS" { + port := cfg.Port + if port == "" { + log.Fatalf("PORT environment variable is required for HTTP/HTTPS mode. Please set PORT environment variable.") + } + + // Determine if HTTPS mode and normalize transport + isHTTPS := transport == "https" || transport == "HTTPS" + if isHTTPS { + transport = "HTTPS" + } else { + transport = "HTTP" + } + + log.Printf("Running in %s mode on port %s", transport, port) + + mux := http.NewServeMux() + mux.HandleFunc("/mcp", func(w http.ResponseWriter, r *http.Request) { + // Read headers for dynamic config + apiCfg := &config.APIConfig{ + BaseURL: r.Header.Get("API_BASE_URL"), + BearerToken: r.Header.Get("BEARER_TOKEN"), + APIKey: r.Header.Get("API_KEY"), + BasicAuth: r.Header.Get("BASIC_AUTH"), + } + + if apiCfg.BaseURL == "" { + http.Error(w, "Missing API_BASE_URL header", http.StatusBadRequest) + return + } + + log.Printf("Incoming HTTP request - BaseURL: %s", apiCfg.BaseURL) + + // Create MCP server for this request + mcpSrv := createMCPServer(apiCfg, transport) + handler := server.NewStreamableHTTPServer(mcpSrv, server.WithHTTPContextFunc( + func(ctx context.Context, req *http.Request) context.Context { + return context.WithValue(ctx, "apiConfig", apiCfg) + }, + )) + + handler.ServeHTTP(w, r) + }) + + mux.HandleFunc("/", func(w http.ResponseWriter, _ *http.Request) { + w.Header().Set("Content-Type", "application/json") + w.Write([]byte(`{"status":"ok"}`)) + }) + + addr := net.JoinHostPort("0.0.0.0", port) + httpServer := &http.Server{Addr: addr, Handler: mux} + + go func() { + // Check if HTTPS mode + if isHTTPS { + certFile := os.Getenv("CERT_FILE") + keyFile := os.Getenv("KEY_FILE") + + if certFile == "" || keyFile == "" { + log.Fatalf("CERT_FILE and KEY_FILE environment variables are required for HTTPS mode") + } + + log.Printf("Starting HTTPS server on %s", addr) + if err := httpServer.ListenAndServeTLS(certFile, keyFile); err != http.ErrServerClosed { + log.Fatalf("HTTPS server error: %v", err) + } + } else { + log.Printf("Starting HTTP server on %s", addr) + if err := httpServer.ListenAndServe(); err != http.ErrServerClosed { + log.Fatalf("HTTP server error: %v", err) + } + } + }() + + <-sigChan + log.Println("Shutdown signal received") + + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + if err := httpServer.Shutdown(ctx); err != nil { + log.Printf("Shutdown error: %v", err) + } else { + log.Println("HTTP server shutdown complete") + } + return + } + + // STDIO Mode - default when no transport or transport is "stdio" + log.Println("Running in STDIO mode") + mcp := createMCPServer(cfg, "STDIO") + go func() { + if err := server.ServeStdio(mcp); err != nil { + log.Fatalf("STDIO error: %v", err) + } + }() + <-sigChan + log.Println("Received shutdown signal. Exiting STDIO mode.") +} + +func createMCPServer(cfg *config.APIConfig, mode string) *server.MCPServer { + mcp := server.NewMCPServer("Input API", "1.0.0", + server.WithToolCapabilities(true), + server.WithRecovery(), + ) + + tools := GetAll(cfg) + log.Printf("Loaded %d tools for %s mode", len(tools), mode) + + for _, tool := range tools { + mcp.AddTool(tool.Definition, tool.Handler) + } + + return mcp +} \ No newline at end of file diff --git a/MCP/models/models.go b/MCP/models/models.go new file mode 100644 index 0000000000..e9794d00df --- /dev/null +++ b/MCP/models/models.go @@ -0,0 +1,17 @@ +package models + +import ( + "context" + "github.com/mark3labs/mcp-go/mcp" +) + +type Tool struct { + Definition mcp.Tool + Handler func(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) +} + +// Error represents the Error schema from the OpenAPI specification +type Error struct { + Code int `json:"code,omitempty"` + Message string `json:"message,omitempty"` +} diff --git a/MCP/registry.go b/MCP/registry.go new file mode 100644 index 0000000000..752d39d3f7 --- /dev/null +++ b/MCP/registry.go @@ -0,0 +1,15 @@ +package main + +import ( + "github.com/input-api/mcp-server/config" + "github.com/input-api/mcp-server/models" + tools_openapi "github.com/input-api/mcp-server/tools/openapi" + tools_openapi_type_json_yaml "github.com/input-api/mcp-server/tools/openapi_type_json_yaml" +) + +func GetAll(cfg *config.APIConfig) []models.Tool { + return []models.Tool{ + tools_openapi.CreateGet_openapiTool(cfg), + tools_openapi_type_json_yaml.CreateGet_openapi_typejsonyamlTool(cfg), + } +} diff --git a/MCP/tools/openapi/get_openapi.go b/MCP/tools/openapi/get_openapi.go new file mode 100644 index 0000000000..a2f19a1635 --- /dev/null +++ b/MCP/tools/openapi/get_openapi.go @@ -0,0 +1,64 @@ +package tools + +import ( + "context" + "encoding/json" + "fmt" + "io" + "net/http" + + "github.com/input-api/mcp-server/config" + "github.com/input-api/mcp-server/models" + "github.com/mark3labs/mcp-go/mcp" +) + +func Get_openapiHandler(cfg *config.APIConfig) func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { + return func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { + url := fmt.Sprintf("%s/openapi", cfg.BaseURL) + req, err := http.NewRequest("GET", url, nil) + if err != nil { + return mcp.NewToolResultErrorFromErr("Failed to create request", err), nil + } + // No authentication required for this endpoint + req.Header.Set("Accept", "application/json") + + resp, err := http.DefaultClient.Do(req) + if err != nil { + return mcp.NewToolResultErrorFromErr("Request failed", err), nil + } + defer resp.Body.Close() + + body, err := io.ReadAll(resp.Body) + if err != nil { + return mcp.NewToolResultErrorFromErr("Failed to read response body", err), nil + } + + if resp.StatusCode >= 400 { + return mcp.NewToolResultError(fmt.Sprintf("API error: %s", body)), nil + } + // Use properly typed response + var result map[string]interface{} + if err := json.Unmarshal(body, &result); err != nil { + // Fallback to raw text if unmarshaling fails + return mcp.NewToolResultText(string(body)), nil + } + + prettyJSON, err := json.MarshalIndent(result, "", " ") + if err != nil { + return mcp.NewToolResultErrorFromErr("Failed to format JSON", err), nil + } + + return mcp.NewToolResultText(string(prettyJSON)), nil + } +} + +func CreateGet_openapiTool(cfg *config.APIConfig) models.Tool { + tool := mcp.NewTool("get_openapi", + mcp.WithDescription("Get openapi"), + ) + + return models.Tool{ + Definition: tool, + Handler: Get_openapiHandler(cfg), + } +} diff --git a/MCP/tools/openapi_type_json_yaml/get_openapi_typejsonyaml.go b/MCP/tools/openapi_type_json_yaml/get_openapi_typejsonyaml.go new file mode 100644 index 0000000000..f99bfdb585 --- /dev/null +++ b/MCP/tools/openapi_type_json_yaml/get_openapi_typejsonyaml.go @@ -0,0 +1,64 @@ +package tools + +import ( + "context" + "encoding/json" + "fmt" + "io" + "net/http" + + "github.com/input-api/mcp-server/config" + "github.com/input-api/mcp-server/models" + "github.com/mark3labs/mcp-go/mcp" +) + +func Get_openapi_typejsonyamlHandler(cfg *config.APIConfig) func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { + return func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { + url := fmt.Sprintf("%s/openapi.%s", cfg.BaseURL) + req, err := http.NewRequest("GET", url, nil) + if err != nil { + return mcp.NewToolResultErrorFromErr("Failed to create request", err), nil + } + // No authentication required for this endpoint + req.Header.Set("Accept", "application/json") + + resp, err := http.DefaultClient.Do(req) + if err != nil { + return mcp.NewToolResultErrorFromErr("Request failed", err), nil + } + defer resp.Body.Close() + + body, err := io.ReadAll(resp.Body) + if err != nil { + return mcp.NewToolResultErrorFromErr("Failed to read response body", err), nil + } + + if resp.StatusCode >= 400 { + return mcp.NewToolResultError(fmt.Sprintf("API error: %s", body)), nil + } + // Use properly typed response + var result map[string]interface{} + if err := json.Unmarshal(body, &result); err != nil { + // Fallback to raw text if unmarshaling fails + return mcp.NewToolResultText(string(body)), nil + } + + prettyJSON, err := json.MarshalIndent(result, "", " ") + if err != nil { + return mcp.NewToolResultErrorFromErr("Failed to format JSON", err), nil + } + + return mcp.NewToolResultText(string(prettyJSON)), nil + } +} + +func CreateGet_openapi_typejsonyamlTool(cfg *config.APIConfig) models.Tool { + tool := mcp.NewTool("get_openapi.type:json|yaml", + mcp.WithDescription("Get openapi.{type:json|yaml}"), + ) + + return models.Tool{ + Definition: tool, + Handler: Get_openapi_typejsonyamlHandler(cfg), + } +} From 2354f5a4143b884523072a22975e5529cd3d7079 Mon Sep 17 00:00:00 2001 From: "codeglide[bot]" <223743026+codeglide[bot]@users.noreply.github.com> Date: Thu, 21 Aug 2025 11:04:12 +0000 Subject: [PATCH 2/2] feat: Updated MCP server with latest generated code --- MCP/models/models.go | 67 +++++++++++++++++++++++- MCP/registry.go | 11 ++++ MCP/tools/api/get.go | 64 ++++++++++++++++++++++ MCP/tools/auth/head_basic_auth.go | 64 ++++++++++++++++++++++ MCP/tools/auth/head_bearer_token.go | 64 ++++++++++++++++++++++ MCP/tools/authentication/head_api_key.go | 64 ++++++++++++++++++++++ MCP/tools/config/head_api_base_url.go | 64 ++++++++++++++++++++++ MCP/tools/mcp/get_mcp.go | 64 ++++++++++++++++++++++ 8 files changed, 461 insertions(+), 1 deletion(-) create mode 100644 MCP/tools/api/get.go create mode 100644 MCP/tools/auth/head_basic_auth.go create mode 100644 MCP/tools/auth/head_bearer_token.go create mode 100644 MCP/tools/authentication/head_api_key.go create mode 100644 MCP/tools/config/head_api_base_url.go create mode 100644 MCP/tools/mcp/get_mcp.go diff --git a/MCP/models/models.go b/MCP/models/models.go index e9794d00df..e70c5bab52 100644 --- a/MCP/models/models.go +++ b/MCP/models/models.go @@ -12,6 +12,71 @@ type Tool struct { // Error represents the Error schema from the OpenAPI specification type Error struct { - Code int `json:"code,omitempty"` Message string `json:"message,omitempty"` + Code int `json:"code,omitempty"` +} + +// ErrorResponse represents the ErrorResponse schema from the OpenAPI specification +type ErrorResponse struct { + Timestamp string `json:"timestamp,omitempty"` // Error timestamp + ErrorField string `json:"error,omitempty"` // Technical error details + Message string `json:"message,omitempty"` // Error message +} + +// APIResponse represents the APIResponse schema from the OpenAPI specification +type APIResponse struct { + Data map[string]interface{} `json:"data,omitempty"` // Response data + Message string `json:"message,omitempty"` // Response message + Success bool `json:"success,omitempty"` // Operation success status +} + +// CustomerResponse represents the CustomerResponse schema from the OpenAPI specification +type CustomerResponse struct { + Message string `json:"message,omitempty"` // Response message + Success bool `json:"success,omitempty"` // Operation success status + Customer Customer `json:"customer,omitempty"` +} + +// Success represents the Success schema from the OpenAPI specification +type Success struct { + Data map[string]interface{} `json:"data,omitempty"` // Response data + Message string `json:"message,omitempty"` // Success message + Success bool `json:"success,omitempty"` // Operation success status +} + +// Broker represents the Broker schema from the OpenAPI specification +type Broker struct { + Name string `json:"name,omitempty"` // Broker name + Status string `json:"status,omitempty"` // Broker status + Endpoint string `json:"endpoint,omitempty"` // Broker endpoint URL + Id string `json:"id,omitempty"` // Broker unique identifier +} + +// Artifact represents the Artifact schema from the OpenAPI specification +type Artifact struct { + Customerid string `json:"customerId,omitempty"` // Customer ID associated with the artifact + Id string `json:"id,omitempty"` // Artifact unique identifier + Name string `json:"name,omitempty"` // Artifact name + Size int `json:"size,omitempty"` // Artifact size in bytes + TypeField string `json:"type,omitempty"` // Artifact type + Updatedat string `json:"updatedAt,omitempty"` // Last update timestamp + Createdat string `json:"createdAt,omitempty"` // Creation timestamp +} + +// Customer represents the Customer schema from the OpenAPI specification +type Customer struct { + Createdat string `json:"createdAt,omitempty"` // Creation timestamp + Email string `json:"email,omitempty"` // Customer email address + Id string `json:"id,omitempty"` // Customer unique identifier + Name string `json:"name,omitempty"` // Customer name +} + +// BrokerInfo represents the BrokerInfo schema from the OpenAPI specification +type BrokerInfo struct { + Id string `json:"id,omitempty"` // Broker unique identifier + Lasthealthcheck string `json:"lastHealthCheck,omitempty"` // Last health check timestamp + Name string `json:"name,omitempty"` // Broker name + Status string `json:"status,omitempty"` // Broker status + TypeField string `json:"type,omitempty"` // Broker type + Endpoint string `json:"endpoint,omitempty"` // Broker endpoint URL } diff --git a/MCP/registry.go b/MCP/registry.go index 752d39d3f7..a6b0d2f9eb 100644 --- a/MCP/registry.go +++ b/MCP/registry.go @@ -3,13 +3,24 @@ package main import ( "github.com/input-api/mcp-server/config" "github.com/input-api/mcp-server/models" + tools_auth "github.com/input-api/mcp-server/tools/auth" + tools_api "github.com/input-api/mcp-server/tools/api" + tools_mcp "github.com/input-api/mcp-server/tools/mcp" tools_openapi "github.com/input-api/mcp-server/tools/openapi" tools_openapi_type_json_yaml "github.com/input-api/mcp-server/tools/openapi_type_json_yaml" + tools_config "github.com/input-api/mcp-server/tools/config" + tools_authentication "github.com/input-api/mcp-server/tools/authentication" ) func GetAll(cfg *config.APIConfig) []models.Tool { return []models.Tool{ + tools_auth.CreateHead_basic_authTool(cfg), + tools_auth.CreateHead_bearer_tokenTool(cfg), + tools_api.CreateGetTool(cfg), + tools_mcp.CreateGet_mcpTool(cfg), tools_openapi.CreateGet_openapiTool(cfg), tools_openapi_type_json_yaml.CreateGet_openapi_typejsonyamlTool(cfg), + tools_config.CreateHead_api_base_urlTool(cfg), + tools_authentication.CreateHead_api_keyTool(cfg), } } diff --git a/MCP/tools/api/get.go b/MCP/tools/api/get.go new file mode 100644 index 0000000000..b39ca41a5d --- /dev/null +++ b/MCP/tools/api/get.go @@ -0,0 +1,64 @@ +package tools + +import ( + "context" + "encoding/json" + "fmt" + "io" + "net/http" + + "github.com/input-api/mcp-server/config" + "github.com/input-api/mcp-server/models" + "github.com/mark3labs/mcp-go/mcp" +) + +func GetHandler(cfg *config.APIConfig) func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { + return func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { + url := fmt.Sprintf("%s/", cfg.BaseURL) + req, err := http.NewRequest("GET", url, nil) + if err != nil { + return mcp.NewToolResultErrorFromErr("Failed to create request", err), nil + } + // No authentication required for this endpoint + req.Header.Set("Accept", "application/json") + + resp, err := http.DefaultClient.Do(req) + if err != nil { + return mcp.NewToolResultErrorFromErr("Request failed", err), nil + } + defer resp.Body.Close() + + body, err := io.ReadAll(resp.Body) + if err != nil { + return mcp.NewToolResultErrorFromErr("Failed to read response body", err), nil + } + + if resp.StatusCode >= 400 { + return mcp.NewToolResultError(fmt.Sprintf("API error: %s", body)), nil + } + // Use properly typed response + var result map[string]interface{} + if err := json.Unmarshal(body, &result); err != nil { + // Fallback to raw text if unmarshaling fails + return mcp.NewToolResultText(string(body)), nil + } + + prettyJSON, err := json.MarshalIndent(result, "", " ") + if err != nil { + return mcp.NewToolResultErrorFromErr("Failed to format JSON", err), nil + } + + return mcp.NewToolResultText(string(prettyJSON)), nil + } +} + +func CreateGetTool(cfg *config.APIConfig) models.Tool { + tool := mcp.NewTool("get_", + mcp.WithDescription("Get api"), + ) + + return models.Tool{ + Definition: tool, + Handler: GetHandler(cfg), + } +} diff --git a/MCP/tools/auth/head_basic_auth.go b/MCP/tools/auth/head_basic_auth.go new file mode 100644 index 0000000000..0b315f7f80 --- /dev/null +++ b/MCP/tools/auth/head_basic_auth.go @@ -0,0 +1,64 @@ +package tools + +import ( + "context" + "encoding/json" + "fmt" + "io" + "net/http" + + "github.com/input-api/mcp-server/config" + "github.com/input-api/mcp-server/models" + "github.com/mark3labs/mcp-go/mcp" +) + +func Head_basic_authHandler(cfg *config.APIConfig) func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { + return func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { + url := fmt.Sprintf("%sBASIC_AUTH", cfg.BaseURL) + req, err := http.NewRequest("HEAD", url, nil) + if err != nil { + return mcp.NewToolResultErrorFromErr("Failed to create request", err), nil + } + // No authentication required for this endpoint + req.Header.Set("Accept", "application/json") + + resp, err := http.DefaultClient.Do(req) + if err != nil { + return mcp.NewToolResultErrorFromErr("Request failed", err), nil + } + defer resp.Body.Close() + + body, err := io.ReadAll(resp.Body) + if err != nil { + return mcp.NewToolResultErrorFromErr("Failed to read response body", err), nil + } + + if resp.StatusCode >= 400 { + return mcp.NewToolResultError(fmt.Sprintf("API error: %s", body)), nil + } + // Use properly typed response + var result string + if err := json.Unmarshal(body, &result); err != nil { + // Fallback to raw text if unmarshaling fails + return mcp.NewToolResultText(string(body)), nil + } + + prettyJSON, err := json.MarshalIndent(result, "", " ") + if err != nil { + return mcp.NewToolResultErrorFromErr("Failed to format JSON", err), nil + } + + return mcp.NewToolResultText(string(prettyJSON)), nil + } +} + +func CreateHead_basic_authTool(cfg *config.APIConfig) models.Tool { + tool := mcp.NewTool("head_BASIC_AUTH", + mcp.WithDescription("Get Basic Auth Header"), + ) + + return models.Tool{ + Definition: tool, + Handler: Head_basic_authHandler(cfg), + } +} diff --git a/MCP/tools/auth/head_bearer_token.go b/MCP/tools/auth/head_bearer_token.go new file mode 100644 index 0000000000..3cac5d947a --- /dev/null +++ b/MCP/tools/auth/head_bearer_token.go @@ -0,0 +1,64 @@ +package tools + +import ( + "context" + "encoding/json" + "fmt" + "io" + "net/http" + + "github.com/input-api/mcp-server/config" + "github.com/input-api/mcp-server/models" + "github.com/mark3labs/mcp-go/mcp" +) + +func Head_bearer_tokenHandler(cfg *config.APIConfig) func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { + return func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { + url := fmt.Sprintf("%sBEARER_TOKEN", cfg.BaseURL) + req, err := http.NewRequest("HEAD", url, nil) + if err != nil { + return mcp.NewToolResultErrorFromErr("Failed to create request", err), nil + } + // No authentication required for this endpoint + req.Header.Set("Accept", "application/json") + + resp, err := http.DefaultClient.Do(req) + if err != nil { + return mcp.NewToolResultErrorFromErr("Request failed", err), nil + } + defer resp.Body.Close() + + body, err := io.ReadAll(resp.Body) + if err != nil { + return mcp.NewToolResultErrorFromErr("Failed to read response body", err), nil + } + + if resp.StatusCode >= 400 { + return mcp.NewToolResultError(fmt.Sprintf("API error: %s", body)), nil + } + // Use properly typed response + var result string + if err := json.Unmarshal(body, &result); err != nil { + // Fallback to raw text if unmarshaling fails + return mcp.NewToolResultText(string(body)), nil + } + + prettyJSON, err := json.MarshalIndent(result, "", " ") + if err != nil { + return mcp.NewToolResultErrorFromErr("Failed to format JSON", err), nil + } + + return mcp.NewToolResultText(string(prettyJSON)), nil + } +} + +func CreateHead_bearer_tokenTool(cfg *config.APIConfig) models.Tool { + tool := mcp.NewTool("head_BEARER_TOKEN", + mcp.WithDescription("Get Bearer Token"), + ) + + return models.Tool{ + Definition: tool, + Handler: Head_bearer_tokenHandler(cfg), + } +} diff --git a/MCP/tools/authentication/head_api_key.go b/MCP/tools/authentication/head_api_key.go new file mode 100644 index 0000000000..7c19fc2143 --- /dev/null +++ b/MCP/tools/authentication/head_api_key.go @@ -0,0 +1,64 @@ +package tools + +import ( + "context" + "encoding/json" + "fmt" + "io" + "net/http" + + "github.com/input-api/mcp-server/config" + "github.com/input-api/mcp-server/models" + "github.com/mark3labs/mcp-go/mcp" +) + +func Head_api_keyHandler(cfg *config.APIConfig) func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { + return func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { + url := fmt.Sprintf("%sAPI_KEY", cfg.BaseURL) + req, err := http.NewRequest("HEAD", url, nil) + if err != nil { + return mcp.NewToolResultErrorFromErr("Failed to create request", err), nil + } + // No authentication required for this endpoint + req.Header.Set("Accept", "application/json") + + resp, err := http.DefaultClient.Do(req) + if err != nil { + return mcp.NewToolResultErrorFromErr("Request failed", err), nil + } + defer resp.Body.Close() + + body, err := io.ReadAll(resp.Body) + if err != nil { + return mcp.NewToolResultErrorFromErr("Failed to read response body", err), nil + } + + if resp.StatusCode >= 400 { + return mcp.NewToolResultError(fmt.Sprintf("API error: %s", body)), nil + } + // Use properly typed response + var result string + if err := json.Unmarshal(body, &result); err != nil { + // Fallback to raw text if unmarshaling fails + return mcp.NewToolResultText(string(body)), nil + } + + prettyJSON, err := json.MarshalIndent(result, "", " ") + if err != nil { + return mcp.NewToolResultErrorFromErr("Failed to format JSON", err), nil + } + + return mcp.NewToolResultText(string(prettyJSON)), nil + } +} + +func CreateHead_api_keyTool(cfg *config.APIConfig) models.Tool { + tool := mcp.NewTool("head_API_KEY", + mcp.WithDescription("Get API Key from Request Header"), + ) + + return models.Tool{ + Definition: tool, + Handler: Head_api_keyHandler(cfg), + } +} diff --git a/MCP/tools/config/head_api_base_url.go b/MCP/tools/config/head_api_base_url.go new file mode 100644 index 0000000000..c9613bc353 --- /dev/null +++ b/MCP/tools/config/head_api_base_url.go @@ -0,0 +1,64 @@ +package tools + +import ( + "context" + "encoding/json" + "fmt" + "io" + "net/http" + + "github.com/input-api/mcp-server/config" + "github.com/input-api/mcp-server/models" + "github.com/mark3labs/mcp-go/mcp" +) + +func Head_api_base_urlHandler(cfg *config.APIConfig) func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { + return func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { + url := fmt.Sprintf("%sAPI_BASE_URL", cfg.BaseURL) + req, err := http.NewRequest("HEAD", url, nil) + if err != nil { + return mcp.NewToolResultErrorFromErr("Failed to create request", err), nil + } + // No authentication required for this endpoint + req.Header.Set("Accept", "application/json") + + resp, err := http.DefaultClient.Do(req) + if err != nil { + return mcp.NewToolResultErrorFromErr("Request failed", err), nil + } + defer resp.Body.Close() + + body, err := io.ReadAll(resp.Body) + if err != nil { + return mcp.NewToolResultErrorFromErr("Failed to read response body", err), nil + } + + if resp.StatusCode >= 400 { + return mcp.NewToolResultError(fmt.Sprintf("API error: %s", body)), nil + } + // Use properly typed response + var result string + if err := json.Unmarshal(body, &result); err != nil { + // Fallback to raw text if unmarshaling fails + return mcp.NewToolResultText(string(body)), nil + } + + prettyJSON, err := json.MarshalIndent(result, "", " ") + if err != nil { + return mcp.NewToolResultErrorFromErr("Failed to format JSON", err), nil + } + + return mcp.NewToolResultText(string(prettyJSON)), nil + } +} + +func CreateHead_api_base_urlTool(cfg *config.APIConfig) models.Tool { + tool := mcp.NewTool("head_API_BASE_URL", + mcp.WithDescription("Get API Base URL"), + ) + + return models.Tool{ + Definition: tool, + Handler: Head_api_base_urlHandler(cfg), + } +} diff --git a/MCP/tools/mcp/get_mcp.go b/MCP/tools/mcp/get_mcp.go new file mode 100644 index 0000000000..3881b9588d --- /dev/null +++ b/MCP/tools/mcp/get_mcp.go @@ -0,0 +1,64 @@ +package tools + +import ( + "context" + "encoding/json" + "fmt" + "io" + "net/http" + + "github.com/input-api/mcp-server/config" + "github.com/input-api/mcp-server/models" + "github.com/mark3labs/mcp-go/mcp" +) + +func Get_mcpHandler(cfg *config.APIConfig) func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { + return func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { + url := fmt.Sprintf("%s/mcp", cfg.BaseURL) + req, err := http.NewRequest("GET", url, nil) + if err != nil { + return mcp.NewToolResultErrorFromErr("Failed to create request", err), nil + } + // No authentication required for this endpoint + req.Header.Set("Accept", "application/json") + + resp, err := http.DefaultClient.Do(req) + if err != nil { + return mcp.NewToolResultErrorFromErr("Request failed", err), nil + } + defer resp.Body.Close() + + body, err := io.ReadAll(resp.Body) + if err != nil { + return mcp.NewToolResultErrorFromErr("Failed to read response body", err), nil + } + + if resp.StatusCode >= 400 { + return mcp.NewToolResultError(fmt.Sprintf("API error: %s", body)), nil + } + // Use properly typed response + var result map[string]interface{} + if err := json.Unmarshal(body, &result); err != nil { + // Fallback to raw text if unmarshaling fails + return mcp.NewToolResultText(string(body)), nil + } + + prettyJSON, err := json.MarshalIndent(result, "", " ") + if err != nil { + return mcp.NewToolResultErrorFromErr("Failed to format JSON", err), nil + } + + return mcp.NewToolResultText(string(prettyJSON)), nil + } +} + +func CreateGet_mcpTool(cfg *config.APIConfig) models.Tool { + tool := mcp.NewTool("get_mcp", + mcp.WithDescription("Get mcp"), + ) + + return models.Tool{ + Definition: tool, + Handler: Get_mcpHandler(cfg), + } +}