#
tokens: 2885/50000 4/4 files
lines: off (toggle) GitHub
raw markdown copy
# Directory Structure

```
├── .gitignore
├── claude_desktop_config.json
├── cmd
│   └── main.go
├── go.mod
├── go.sum
├── Makefile
└── README.md
```

# Files

--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------

```
santa-mcp

```

--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------

```markdown
# santa-mcp

A PoC MCP Server for [Santa](https://github.com/northpolesec/santa). 

##  What is this?

> [!CAUTION]
> This is intended solely as a demonstration and is not production-ready. It is not an officially supported product of North Pole Security.

This is a Proof of Concept [Model Context Procotol server](https://modelcontextprotocol.io/introduction) for [santactl](https://northpole.dev/binaries/santactl.html). 

It allows you to connect and drive Santa via an LLM that implements an MCP
client.

[![Example session With Claude Desktop](https://img.youtube.com/vi/Q_bHdz3wFzQ/0.jpg)](https://youtu.be/Q_bHdz3wFzQ)

* [Session with Claude](https://claude.ai/share/9425ecd9-6dfb-40c7-adbd-6478ec857d4a)

## Quickstart

* Install [Claude Desktop](https://claude.ai/download) if you don't already have it.
* Run `make`
* Copy the `santa-mcp` binary somewhere on your system
* Edit the `claude_desktop_config.json` to point to the path from the previous
 step
* Copy the `claude_desktop_config.json` file to your `~/Library
  * `cp claude_desktop_config.json ~/Library/Application\ Support/Claude/`
* Open Claude desktop you should see 4 tools
 * santactl_fileinfo
 * santactl_status
 * santactl_sync
 * santactl_version

* Ask it questions about Santa e.g. `Why is Santa blocking osascript?`

## Dependencies

This depends on [MCP Golang](https://mcpgolang.com/).

```

--------------------------------------------------------------------------------
/claude_desktop_config.json:
--------------------------------------------------------------------------------

```json
{
  "mcpServers": {
    "santa-mcp": {
      "command": "/Users/peterm/bin/santa-mcp",
      "args": []
    }
  }
}

```

--------------------------------------------------------------------------------
/cmd/main.go:
--------------------------------------------------------------------------------

```go
// This is an MCP server for santactl.
package main

import (
	"fmt"
	"log"
	"os/exec"

	mcp_golang "github.com/metoro-io/mcp-golang"
	"github.com/metoro-io/mcp-golang/transport/stdio"
)

// EmptySantaCtlArgs is an empty struct used for commands that do not require
// any arguments.
type EmptySantaCtlArgs struct {
}

// runSantactlCommand runs the santactl command with the specified arguments.
func runSantactlCommand(asRoot bool, asJson bool, command string, args []string) (*mcp_golang.ToolResponse, error) {
	// Create a command with no arguments
	commandAndArgs := []string{}
	if asRoot {
		commandAndArgs = append(commandAndArgs, "sudo")
	}
	commandAndArgs = append(commandAndArgs, "/usr/local/bin/santactl")
	commandAndArgs = append(commandAndArgs, command)

	if asJson {
		commandAndArgs = append(commandAndArgs, "--json")
	}

	commandAndArgs = append(commandAndArgs, args...)

	cmd := exec.Command(commandAndArgs[0], commandAndArgs[1:]...)

	// Run the command and capture the output
	output, err := cmd.Output()
	if cmd == nil {
		msg := fmt.Sprintf("Error with %s: ", command)
		return mcp_golang.NewToolResponse(
			mcp_golang.NewTextContent(msg + err.Error())), err
	}

	return mcp_golang.NewToolResponse(
		mcp_golang.NewTextContent(string(output))), nil
}

// santaVersion retrieves the version information for all of the santa
// components. It is a helper running for the santactl version command.
func santaVersion(args EmptySantaCtlArgs) (*mcp_golang.ToolResponse, error) {
	return runSantactlCommand(false, true, "version", nil)
}

// santaStatus retrieves the status information for the Santa daemon.
// This includes information about the rule database, watch items, and more.
func santaStatus(args EmptySantaCtlArgs) (*mcp_golang.ToolResponse, error) {
	return runSantactlCommand(false, false, "status", nil)
}

// SantaSyncArgs is the argument structure for the santaSyncRules function.
// It includes a flag for clean sync which will remove all existing rules.
type SantaSyncArgs struct {
	CleanSync bool `json:"clean_sync"`
}

// santaSyncRules forces a sync of the Santa rules database from a sync server
func santaSyncRules(args SantaSyncArgs) (*mcp_golang.ToolResponse, error) {
	if args.CleanSync {
		return runSantactlCommand(true, false, "sync", []string{"--clean"})
	}

	return runSantactlCommand(true, false, "sync", nil)
}

// santaMetrics retrieves the metrics information for the Santa daemon. It is
// a helper function for running santactl metrics.
func santaMetrics(args EmptySantaCtlArgs) (*mcp_golang.ToolResponse, error) {
	// Run the santactl command to get metrics information
	return runSantactlCommand(false, false, "metrics", nil)
}

type SantaFileinfoArgs struct {
	// Absolute path to the file on disk
	FilePath         string `json:"file_path"` // Absolute path to the file on disk
	ShowEntitlements bool   `json:"show_entitlements"`
}

// santaFileinfo retrieves information about a file using the santactl fileinfo command
func santaFileinfo(args SantaFileinfoArgs) (*mcp_golang.ToolResponse, error) {
	// Run the santactl command to get file information
	return runSantactlCommand(false, false, "fileinfo", []string{args.FilePath})
}

// Prompts below.
func santaFileInfoPrompt(args EmptySantaCtlArgs) (*mcp_golang.PromptResponse, error) {
	// Prompt for the file path
	prompt := `santactl fileinfo       

The details provided will be the same ones Santa uses to make a decision
about executables. This includes SHA-256, SHA-1, code signing information and
the type of file.
Usage: santactl fileinfo [options] [file-paths]
    --recursive (-r): Search directories recursively.
                      Incompatible with --bundleinfo.
    --json: Output in JSON format.
    --key: Search and return this one piece of information.
           You may specify multiple keys by repeating this flag.
           Valid Keys:
                       "Path"
                       "SHA-256"
                       "SHA-1"
                       "Bundle Name"
                       "Bundle Version"
                       "Bundle Version Str"
                       "Download Referrer URL"
                       "Download URL"
                       "Download Timestamp"
                       "Download Agent"
                       "Team ID"
                       "Signing ID"
                       "CDHash"
                       "Type"
                       "Page Zero"
                       "Code-signed"
                       "Rule"
                       "Entitlements"
                       "Signing Chain"
                       "Universal Signing Chain"

           Valid keys when using --cert-index:
                       "SHA-256"
                       "SHA-1"
                       "Common Name"
                       "Organization"
                       "Organizational Unit"
                       "Valid From"
                       "Valid Until"

    --cert-index: Supply an integer corresponding to a certificate of the
                  signing chain to show info only for that certificate.
                     0 up to n for the leaf certificate up to the root
                    -1 down to -n-1 for the root certificate down to the leaf
                  Incompatible with --bundleinfo.
    --filter: Use predicates of the form 'key=regex' to filter out which files
              are displayed. Valid keys are the same as for --key. Value is a
              case-insensitive regular expression which must match anywhere in
              the keyed property value for the file's info to be displayed.
              You may specify multiple filters by repeating this flag.
              If multiple filters are specified, any match will display the
              file.
    --filter-inclusive: If multiple filters are specified, they must all match
                        for the file to be displayed.
    --entitlements: If the file has entitlements, will also display them
    --bundleinfo: If the file is part of a bundle, will also display bundle
                  hash information and hashes of all bundle executables.
                  Incompatible with --recursive and --cert-index.

Examples: santactl fileinfo --cert-index 1 --key SHA-256 --json /usr/bin/yes
          santactl fileinfo --key SHA-256 --json /usr/bin/yes
          santactl fileinfo /usr/bin/yes /bin/*
          santactl fileinfo /usr/bin -r --key Path --key SHA-256 --key Rule
          santactl fileinfo /usr/bin/* --filter Type=Script --filter Path=zip`

	return mcp_golang.NewPromptResponse("santactl_fileinfo_prompt",
		mcp_golang.NewPromptMessage(mcp_golang.NewTextContent(prompt),
			mcp_golang.RoleUser)), nil
}

func santaSubCommandPrompt(args EmptySantaCtlArgs) (*mcp_golang.PromptResponse, error) {
	// Prompt for the santactl subcommand
	santactlPrompt := `santactl has the following sub commands

		Usage: santactl:
	  fileinfo - Prints information about a file.
	   metrics - Show Santa metric information.
	  printlog - Prints the contents of Santa protobuf log files as JSON.
		  rule - Manually add/remove/check rules.
		status - Show Santa status information.
		  sync - Synchronizes Santa with a configured server.
	   version - Show Santa component versions.
	   `

	return mcp_golang.NewPromptResponse("santactl subcommands",
		mcp_golang.NewPromptMessage(mcp_golang.NewTextContent(santactlPrompt),
			mcp_golang.RoleAssistant)), nil
}

func santaStatusCommandPrompt(args EmptySantaCtlArgs) (*mcp_golang.PromptResponse, error) {
	// Prompt for the status command
	statusPrompt := `santactl status produces a set of key value pairs
	describing how santa operates. The database section is for execution rules
	and the watch items related to file access authorization rules.

	Do not conflate the watch items and the database items keep all statistics
	separate.

	Additionally if santa is not configured to use a sync service then local
	admin users may add rules using the santactl rule command. This is also true
	if santa does not have any static rules.
	`

	return mcp_golang.NewPromptResponse("santactl_status_prompt",
		mcp_golang.NewPromptMessage(mcp_golang.NewTextContent(statusPrompt),
			mcp_golang.RoleAssistant)), nil
}

func main() {
	done := make(chan struct{})

	server := mcp_golang.NewServer(stdio.NewStdioServerTransport())

	err := server.RegisterTool("santactl_version",
		"List santa daemon versions", santaVersion)
	if err != nil {
		log.Fatalf("Failed to register santactl_version tool: %v", err)
	}

	err = server.RegisterTool("santactl_status",
		"Get the status of the Santa daemon", santaStatus)
	if err != nil {
		log.Fatalf("Failed to register santactl_status tool: %v", err)
	}

	err = server.RegisterTool("santactl_metrics",
		"Get performance metrics from the Santa daemon", santaMetrics)

	if err != nil {
		log.Fatalf("Failed to register santactl_metrics tool: %v", err)
	}

	err = server.RegisterTool("santactl_sync",
		"Have Santa perform a sync of the Santa rules database from the sync server",
		santaSyncRules)

	if err != nil {
		log.Fatalf("Failed to register santactl_sync tool: %v", err)
	}

	err = server.RegisterTool("santactl_fileinfo",
		"Get file information for a given absolute file path", santaFileinfo)
	if err != nil {
		log.Fatalf("Failed to register santactl_fileinfo tool: %v", err)
	}

	err = server.RegisterPrompt("santactl_status_prompt",
		"explain the data in a santactl_status output", santaStatusCommandPrompt)

	if err != nil {
		log.Fatalf("Failed to register prompt: %v", err)
	}

	err = server.RegisterPrompt("santactl_subcommand_prompt",
		"list of valid santactl subcommands", santaSubCommandPrompt)

	if err != nil {
		log.Fatalf("Failed to register prompt: %v", err)
	}

	err = server.RegisterPrompt("santactl_fileinfo_prompt",
		"list of valid santactl subcommands", santaFileInfoPrompt)

	if err != nil {
		log.Fatalf("Failed to register prompt: %v", err)
	}

	err = server.Serve()
	if err != nil {
		log.Fatalf("Failed to serve: %v", err)
	}
	<-done
}

```