mcp-netbird
by aantti
Verified
package mcpnetbird
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"log"
"net/http"
"os"
"github.com/mark3labs/mcp-go/server"
)
const (
defaultNetbirdHost = "api.netbird.io"
defaultNetbirdURL = "https://" + defaultNetbirdHost
netbirdAPIPath = "/api"
netbirdHostEnvVar = "NETBIRD_HOST"
netbirdAPIEnvVar = "NETBIRD_API_TOKEN"
)
// NetbirdClient provides methods to interact with the Netbird API
type NetbirdClient struct {
baseURL string
client *http.Client
}
// NewNetbirdClient creates a new NetbirdClient with the given API key
func NewNetbirdClient() *NetbirdClient {
host := os.Getenv(netbirdHostEnvVar)
if host == "" {
host = defaultNetbirdHost
}
baseURL := "https://" + host + netbirdAPIPath
return &NetbirdClient{
baseURL: baseURL,
client: &http.Client{},
}
}
// do performs an HTTP request to the Netbird API
func (c *NetbirdClient) do(ctx context.Context, method, path string, body, v any) error {
token := NetbirdAPIKeyFromContext(ctx)
if token == "" {
return fmt.Errorf("netbird API token not found in context")
}
var bodyReader io.Reader
if body != nil {
bodyBytes, err := json.Marshal(body)
if err != nil {
return fmt.Errorf("marshaling request body: %w", err)
}
bodyReader = bytes.NewReader(bodyBytes)
}
req, err := http.NewRequestWithContext(ctx, method, c.baseURL+path, bodyReader)
if err != nil {
return fmt.Errorf("creating request: %w", err)
}
req.Header.Set("Authorization", "Bearer "+token)
if body != nil {
req.Header.Set("Content-Type", "application/json")
}
resp, err := c.client.Do(req)
if err != nil {
return fmt.Errorf("making request: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
body, _ := io.ReadAll(resp.Body)
return fmt.Errorf("unexpected status code: %d, body: %s", resp.StatusCode, string(body))
}
if v != nil {
if err := json.NewDecoder(resp.Body).Decode(v); err != nil {
return fmt.Errorf("decoding response: %w", err)
}
}
return nil
}
// Get performs a GET request to the Netbird API
func (c *NetbirdClient) Get(ctx context.Context, path string, v any) error {
return c.do(ctx, http.MethodGet, path, nil, v)
}
// Put performs a PUT request to the Netbird API
func (c *NetbirdClient) Put(ctx context.Context, path string, body, v any) error {
return c.do(ctx, http.MethodPut, path, body, v)
}
type netbirdAPIKeyKey struct{}
// ExtractNetbirdInfoFromEnv is a StdioContextFunc that extracts Netbird configuration
// from environment variables and injects it into the context.
var ExtractNetbirdInfoFromEnv server.StdioContextFunc = func(ctx context.Context) context.Context {
apiKey := os.Getenv(netbirdAPIEnvVar)
if apiKey == "" {
log.Printf("Warning: %s environment variable not found", netbirdAPIEnvVar)
} else {
log.Printf("Found %s environment variable", netbirdAPIEnvVar)
}
return WithNetbirdAPIKey(ctx, apiKey)
}
// ExtractNetbirdInfoFromEnvSSE is an SSEContextFunc that extracts Netbird configuration
// from environment variables and injects it into the context.
var ExtractNetbirdInfoFromEnvSSE server.SSEContextFunc = func(ctx context.Context, req *http.Request) context.Context {
apiKey := os.Getenv(netbirdAPIEnvVar)
if apiKey == "" {
log.Printf("SSE MODE - Warning: %s environment variable not found", netbirdAPIEnvVar)
} else {
log.Printf("SSE MODE - Found %s environment variable with length %d", netbirdAPIEnvVar, len(apiKey))
}
return WithNetbirdAPIKey(ctx, apiKey)
}
// WithNetbirdAPIKey adds the Netbird API key to the context.
func WithNetbirdAPIKey(ctx context.Context, apiKey string) context.Context {
return context.WithValue(ctx, netbirdAPIKeyKey{}, apiKey)
}
// NetbirdAPIKeyFromContext extracts the Netbird API key from the context.
func NetbirdAPIKeyFromContext(ctx context.Context) string {
if v := ctx.Value(netbirdAPIKeyKey{}); v != nil {
return v.(string)
}
return ""
}
// ComposeStdioContextFuncs composes multiple StdioContextFuncs into a single one.
func ComposeStdioContextFuncs(funcs ...server.StdioContextFunc) server.StdioContextFunc {
return func(ctx context.Context) context.Context {
for _, f := range funcs {
ctx = f(ctx)
}
return ctx
}
}
// ComposeSSEContextFuncs composes multiple SSEContextFuncs into a single one.
func ComposeSSEContextFuncs(funcs ...server.SSEContextFunc) server.SSEContextFunc {
return func(ctx context.Context, req *http.Request) context.Context {
for _, f := range funcs {
ctx = f(ctx, req)
}
return ctx
}
}
// ComposedStdioContextFunc is a StdioContextFunc that comprises all predefined StdioContextFuncs.
var ComposedStdioContextFunc = ComposeStdioContextFuncs(
ExtractNetbirdInfoFromEnv,
)
// ComposedSSEContextFunc is an SSEContextFunc that comprises all predefined SSEContextFuncs.
var ComposedSSEContextFunc = ComposeSSEContextFuncs(
ExtractNetbirdInfoFromEnvSSE,
)