Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions cmd/mcp-netbird/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,18 @@ func newServer() *server.MCPServer {
tools.AddNetbirdGroupTools(s)
tools.AddNetbirdPolicyTools(s)
tools.AddNetbirdNetworkTools(s)
tools.AddNetbirdNetworkResourceTools(s)
tools.AddNetbirdNetworkRouterTools(s)
tools.AddNetbirdPostureCheckTools(s)
tools.AddNetbirdPortAllocationTools(s)
tools.AddNetbirdNameserverTools(s)
tools.AddNetbirdDNSSettingsTools(s)
tools.AddNetbirdSetupKeyTools(s)
tools.AddNetbirdUserTools(s)
tools.AddNetbirdAccountTools(s)
tools.AddNetbirdRouteTools(s)
tools.AddNetbirdEventTools(s)
tools.AddNetbirdTokenTools(s)
return s
}

Expand Down
19 changes: 17 additions & 2 deletions mcpnetbird.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,12 +84,12 @@ func (c *NetbirdClient) do(ctx context.Context, method, path string, body, v any
}
defer resp.Body.Close()

if resp.StatusCode != http.StatusOK {
if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusNoContent {
body, _ := io.ReadAll(resp.Body)
return fmt.Errorf("unexpected status code: %d, body: %s", resp.StatusCode, string(body))
}

if v != nil {
if v != nil && resp.StatusCode != http.StatusNoContent {
if err := json.NewDecoder(resp.Body).Decode(v); err != nil {
return fmt.Errorf("decoding response: %w", err)
}
Expand All @@ -108,6 +108,21 @@ func (c *NetbirdClient) Put(ctx context.Context, path string, body, v any) error
return c.do(ctx, http.MethodPut, path, body, v)
}

// Post performs a POST request to the Netbird API
func (c *NetbirdClient) Post(ctx context.Context, path string, body, v any) error {
return c.do(ctx, http.MethodPost, path, body, v)
}

// Delete performs a DELETE request to the Netbird API
func (c *NetbirdClient) Delete(ctx context.Context, path string) error {
return c.do(ctx, http.MethodDelete, path, nil, nil)
}

// Patch performs a PATCH request to the Netbird API
func (c *NetbirdClient) Patch(ctx context.Context, path string, body, v any) error {
return c.do(ctx, http.MethodPatch, path, body, v)
}

type netbirdAPIKeyKey struct{}

// ExtractNetbirdInfoFromEnv is a StdioContextFunc that extracts Netbird configuration
Expand Down
88 changes: 88 additions & 0 deletions tools/accounts.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
package tools

import (
"context"
"fmt"

mcpnetbird "github.com/aantti/mcp-netbird"
"github.com/mark3labs/mcp-go/server"
)

type NetbirdSettings struct {
PeerLoginExpiration int `json:"peer_login_expiration"`
PeerLoginExpirationEnabled bool `json:"peer_login_expiration_enabled"`
GroupsPropagationEnabled bool `json:"groups_propagation_enabled"`
JWTGroupsEnabled bool `json:"jwt_groups_enabled"`
JWTGroupsClaimName string `json:"jwt_groups_claim_name"`
JWTAllowGroups []string `json:"jwt_allow_groups"`
RegularUserViewBlocked bool `json:"regular_users_view_blocked"`
}

type NetbirdAccount struct {
ID string `json:"id"`
Settings NetbirdSettings `json:"settings"`
}

func accountClient() *mcpnetbird.NetbirdClient {
if mcpnetbird.TestNetbirdClient != nil {
return mcpnetbird.TestNetbirdClient
}
return mcpnetbird.NewNetbirdClient()
}

// list_netbird_accounts

type ListNetbirdAccountsParams struct{}

func listNetbirdAccounts(ctx context.Context, args ListNetbirdAccountsParams) ([]NetbirdAccount, error) {
var accounts []NetbirdAccount
if err := accountClient().Get(ctx, "/accounts", &accounts); err != nil {
return nil, err
}
return accounts, nil
}

var ListNetbirdAccounts = mcpnetbird.MustTool(
"list_netbird_accounts",
"List all Netbird accounts",
listNetbirdAccounts,
)

// update_netbird_account

type UpdateNetbirdAccountSettingsParams struct {
PeerLoginExpiration int `json:"peer_login_expiration,omitempty" jsonschema:"description=Peer login expiration time in seconds"`
PeerLoginExpirationEnabled bool `json:"peer_login_expiration_enabled,omitempty" jsonschema:"description=Whether peer login expiration is enabled"`
GroupsPropagationEnabled bool `json:"groups_propagation_enabled,omitempty" jsonschema:"description=Whether group propagation is enabled"`
JWTGroupsEnabled bool `json:"jwt_groups_enabled,omitempty" jsonschema:"description=Whether JWT groups are enabled"`
JWTGroupsClaimName string `json:"jwt_groups_claim_name,omitempty" jsonschema:"description=JWT claim name for groups"`
JWTAllowGroups []string `json:"jwt_allow_groups,omitempty" jsonschema:"description=List of JWT groups allowed"`
RegularUserViewBlocked bool `json:"regular_users_view_blocked,omitempty" jsonschema:"description=Whether regular users view is blocked"`
}

type UpdateNetbirdAccountParams struct {
AccountID string `json:"accountId" jsonschema:"description=The ID of the account to update"`
Settings UpdateNetbirdAccountSettingsParams `json:"settings" jsonschema:"description=Account settings to update"`
}

func updateNetbirdAccount(ctx context.Context, args UpdateNetbirdAccountParams) (*NetbirdAccount, error) {
body := map[string]any{
"settings": args.Settings,
}
var account NetbirdAccount
if err := accountClient().Put(ctx, fmt.Sprintf("/accounts/%s", args.AccountID), body, &account); err != nil {
return nil, err
}
return &account, nil
}

var UpdateNetbirdAccount = mcpnetbird.MustTool(
"update_netbird_account",
"Update a Netbird account's settings",
updateNetbirdAccount,
)

func AddNetbirdAccountTools(mcp *server.MCPServer) {
ListNetbirdAccounts.Register(mcp)
UpdateNetbirdAccount.Register(mcp)
}
65 changes: 65 additions & 0 deletions tools/dns_settings.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package tools

import (
"context"

mcpnetbird "github.com/aantti/mcp-netbird"
"github.com/mark3labs/mcp-go/server"
)

type NetbirdDNSSettings struct {
DisabledManagementGroups []string `json:"disabled_management_groups"`
}

func dnsSettingsClient() *mcpnetbird.NetbirdClient {
if mcpnetbird.TestNetbirdClient != nil {
return mcpnetbird.TestNetbirdClient
}
return mcpnetbird.NewNetbirdClient()
}

// get_netbird_dns_settings

type GetNetbirdDNSSettingsParams struct{}

func getNetbirdDNSSettings(ctx context.Context, args GetNetbirdDNSSettingsParams) (*NetbirdDNSSettings, error) {
var settings NetbirdDNSSettings
if err := dnsSettingsClient().Get(ctx, "/dns/settings", &settings); err != nil {
return nil, err
}
return &settings, nil
}

var GetNetbirdDNSSettings = mcpnetbird.MustTool(
"get_netbird_dns_settings",
"Get Netbird DNS settings",
getNetbirdDNSSettings,
)

// update_netbird_dns_settings

type UpdateNetbirdDNSSettingsParams struct {
DisabledManagementGroups []string `json:"disabled_management_groups,omitempty" jsonschema:"description=List of group IDs with disabled DNS management"`
}

func updateNetbirdDNSSettings(ctx context.Context, args UpdateNetbirdDNSSettingsParams) (*NetbirdDNSSettings, error) {
body := map[string]any{
"disabled_management_groups": args.DisabledManagementGroups,
}
var settings NetbirdDNSSettings
if err := dnsSettingsClient().Put(ctx, "/dns/settings", body, &settings); err != nil {
return nil, err
}
return &settings, nil
}

var UpdateNetbirdDNSSettings = mcpnetbird.MustTool(
"update_netbird_dns_settings",
"Update Netbird DNS settings",
updateNetbirdDNSSettings,
)

func AddNetbirdDNSSettingsTools(mcp *server.MCPServer) {
GetNetbirdDNSSettings.Register(mcp)
UpdateNetbirdDNSSettings.Register(mcp)
}
66 changes: 66 additions & 0 deletions tools/events.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package tools

import (
"context"
"fmt"
"time"

mcpnetbird "github.com/aantti/mcp-netbird"
"github.com/mark3labs/mcp-go/server"
)

type NetbirdEvent struct {
ID string `json:"id"`
Timestamp time.Time `json:"timestamp"`
Activity string `json:"activity"`
InitiatorID string `json:"initiator_id"`
TargetID string `json:"target_id"`
Meta any `json:"meta"`
}

func eventClient() *mcpnetbird.NetbirdClient {
if mcpnetbird.TestNetbirdClient != nil {
return mcpnetbird.TestNetbirdClient
}
return mcpnetbird.NewNetbirdClient()
}

// list_netbird_events

type ListNetbirdEventsParams struct {
Page int `json:"page,omitempty" jsonschema:"description=Page number (1-based)"`
Limit int `json:"limit,omitempty" jsonschema:"description=Number of events per page"`
}

func listNetbirdEvents(ctx context.Context, args ListNetbirdEventsParams) ([]NetbirdEvent, error) {
path := "/events/audit"
query := ""
if args.Page > 0 {
query += fmt.Sprintf("page=%d", args.Page)
}
if args.Limit > 0 {
if query != "" {
query += "&"
}
query += fmt.Sprintf("limit=%d", args.Limit)
}
if query != "" {
path += "?" + query
}

var events []NetbirdEvent
if err := eventClient().Get(ctx, path, &events); err != nil {
return nil, err
}
return events, nil
}

var ListNetbirdEvents = mcpnetbird.MustTool(
"list_netbird_events",
"List Netbird audit events",
listNetbirdEvents,
)

func AddNetbirdEventTools(mcp *server.MCPServer) {
ListNetbirdEvents.Register(mcp)
}
Loading