#
tokens: 3795/50000 4/4 files
lines: on (toggle) GitHub
raw markdown copy reset
# Directory Structure

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

# Files

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

```
1 | santa-mcp
2 | 
```

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

```markdown
 1 | # santa-mcp
 2 | 
 3 | A PoC MCP Server for [Santa](https://github.com/northpolesec/santa). 
 4 | 
 5 | ##  What is this?
 6 | 
 7 | > [!CAUTION]
 8 | > This is intended solely as a demonstration and is not production-ready. It is not an officially supported product of North Pole Security.
 9 | 
10 | This is a Proof of Concept [Model Context Procotol server](https://modelcontextprotocol.io/introduction) for [santactl](https://northpole.dev/binaries/santactl.html). 
11 | 
12 | It allows you to connect and drive Santa via an LLM that implements an MCP
13 | client.
14 | 
15 | [![Example session With Claude Desktop](https://img.youtube.com/vi/Q_bHdz3wFzQ/0.jpg)](https://youtu.be/Q_bHdz3wFzQ)
16 | 
17 | * [Session with Claude](https://claude.ai/share/9425ecd9-6dfb-40c7-adbd-6478ec857d4a)
18 | 
19 | ## Quickstart
20 | 
21 | * Install [Claude Desktop](https://claude.ai/download) if you don't already have it.
22 | * Run `make`
23 | * Copy the `santa-mcp` binary somewhere on your system
24 | * Edit the `claude_desktop_config.json` to point to the path from the previous
25 |  step
26 | * Copy the `claude_desktop_config.json` file to your `~/Library
27 |   * `cp claude_desktop_config.json ~/Library/Application\ Support/Claude/`
28 | * Open Claude desktop you should see 4 tools
29 |  * santactl_fileinfo
30 |  * santactl_status
31 |  * santactl_sync
32 |  * santactl_version
33 | 
34 | * Ask it questions about Santa e.g. `Why is Santa blocking osascript?`
35 | 
36 | ## Dependencies
37 | 
38 | This depends on [MCP Golang](https://mcpgolang.com/).
39 | 
```

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

```json
1 | {
2 |   "mcpServers": {
3 |     "santa-mcp": {
4 |       "command": "/Users/peterm/bin/santa-mcp",
5 |       "args": []
6 |     }
7 |   }
8 | }
9 | 
```

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

```go
  1 | // This is an MCP server for santactl.
  2 | package main
  3 | 
  4 | import (
  5 | 	"fmt"
  6 | 	"log"
  7 | 	"os/exec"
  8 | 
  9 | 	mcp_golang "github.com/metoro-io/mcp-golang"
 10 | 	"github.com/metoro-io/mcp-golang/transport/stdio"
 11 | )
 12 | 
 13 | // EmptySantaCtlArgs is an empty struct used for commands that do not require
 14 | // any arguments.
 15 | type EmptySantaCtlArgs struct {
 16 | }
 17 | 
 18 | // runSantactlCommand runs the santactl command with the specified arguments.
 19 | func runSantactlCommand(asRoot bool, asJson bool, command string, args []string) (*mcp_golang.ToolResponse, error) {
 20 | 	// Create a command with no arguments
 21 | 	commandAndArgs := []string{}
 22 | 	if asRoot {
 23 | 		commandAndArgs = append(commandAndArgs, "sudo")
 24 | 	}
 25 | 	commandAndArgs = append(commandAndArgs, "/usr/local/bin/santactl")
 26 | 	commandAndArgs = append(commandAndArgs, command)
 27 | 
 28 | 	if asJson {
 29 | 		commandAndArgs = append(commandAndArgs, "--json")
 30 | 	}
 31 | 
 32 | 	commandAndArgs = append(commandAndArgs, args...)
 33 | 
 34 | 	cmd := exec.Command(commandAndArgs[0], commandAndArgs[1:]...)
 35 | 
 36 | 	// Run the command and capture the output
 37 | 	output, err := cmd.Output()
 38 | 	if cmd == nil {
 39 | 		msg := fmt.Sprintf("Error with %s: ", command)
 40 | 		return mcp_golang.NewToolResponse(
 41 | 			mcp_golang.NewTextContent(msg + err.Error())), err
 42 | 	}
 43 | 
 44 | 	return mcp_golang.NewToolResponse(
 45 | 		mcp_golang.NewTextContent(string(output))), nil
 46 | }
 47 | 
 48 | // santaVersion retrieves the version information for all of the santa
 49 | // components. It is a helper running for the santactl version command.
 50 | func santaVersion(args EmptySantaCtlArgs) (*mcp_golang.ToolResponse, error) {
 51 | 	return runSantactlCommand(false, true, "version", nil)
 52 | }
 53 | 
 54 | // santaStatus retrieves the status information for the Santa daemon.
 55 | // This includes information about the rule database, watch items, and more.
 56 | func santaStatus(args EmptySantaCtlArgs) (*mcp_golang.ToolResponse, error) {
 57 | 	return runSantactlCommand(false, false, "status", nil)
 58 | }
 59 | 
 60 | // SantaSyncArgs is the argument structure for the santaSyncRules function.
 61 | // It includes a flag for clean sync which will remove all existing rules.
 62 | type SantaSyncArgs struct {
 63 | 	CleanSync bool `json:"clean_sync"`
 64 | }
 65 | 
 66 | // santaSyncRules forces a sync of the Santa rules database from a sync server
 67 | func santaSyncRules(args SantaSyncArgs) (*mcp_golang.ToolResponse, error) {
 68 | 	if args.CleanSync {
 69 | 		return runSantactlCommand(true, false, "sync", []string{"--clean"})
 70 | 	}
 71 | 
 72 | 	return runSantactlCommand(true, false, "sync", nil)
 73 | }
 74 | 
 75 | // santaMetrics retrieves the metrics information for the Santa daemon. It is
 76 | // a helper function for running santactl metrics.
 77 | func santaMetrics(args EmptySantaCtlArgs) (*mcp_golang.ToolResponse, error) {
 78 | 	// Run the santactl command to get metrics information
 79 | 	return runSantactlCommand(false, false, "metrics", nil)
 80 | }
 81 | 
 82 | type SantaFileinfoArgs struct {
 83 | 	// Absolute path to the file on disk
 84 | 	FilePath         string `json:"file_path"` // Absolute path to the file on disk
 85 | 	ShowEntitlements bool   `json:"show_entitlements"`
 86 | }
 87 | 
 88 | // santaFileinfo retrieves information about a file using the santactl fileinfo command
 89 | func santaFileinfo(args SantaFileinfoArgs) (*mcp_golang.ToolResponse, error) {
 90 | 	// Run the santactl command to get file information
 91 | 	return runSantactlCommand(false, false, "fileinfo", []string{args.FilePath})
 92 | }
 93 | 
 94 | // Prompts below.
 95 | func santaFileInfoPrompt(args EmptySantaCtlArgs) (*mcp_golang.PromptResponse, error) {
 96 | 	// Prompt for the file path
 97 | 	prompt := `santactl fileinfo       
 98 | 
 99 | The details provided will be the same ones Santa uses to make a decision
100 | about executables. This includes SHA-256, SHA-1, code signing information and
101 | the type of file.
102 | Usage: santactl fileinfo [options] [file-paths]
103 |     --recursive (-r): Search directories recursively.
104 |                       Incompatible with --bundleinfo.
105 |     --json: Output in JSON format.
106 |     --key: Search and return this one piece of information.
107 |            You may specify multiple keys by repeating this flag.
108 |            Valid Keys:
109 |                        "Path"
110 |                        "SHA-256"
111 |                        "SHA-1"
112 |                        "Bundle Name"
113 |                        "Bundle Version"
114 |                        "Bundle Version Str"
115 |                        "Download Referrer URL"
116 |                        "Download URL"
117 |                        "Download Timestamp"
118 |                        "Download Agent"
119 |                        "Team ID"
120 |                        "Signing ID"
121 |                        "CDHash"
122 |                        "Type"
123 |                        "Page Zero"
124 |                        "Code-signed"
125 |                        "Rule"
126 |                        "Entitlements"
127 |                        "Signing Chain"
128 |                        "Universal Signing Chain"
129 | 
130 |            Valid keys when using --cert-index:
131 |                        "SHA-256"
132 |                        "SHA-1"
133 |                        "Common Name"
134 |                        "Organization"
135 |                        "Organizational Unit"
136 |                        "Valid From"
137 |                        "Valid Until"
138 | 
139 |     --cert-index: Supply an integer corresponding to a certificate of the
140 |                   signing chain to show info only for that certificate.
141 |                      0 up to n for the leaf certificate up to the root
142 |                     -1 down to -n-1 for the root certificate down to the leaf
143 |                   Incompatible with --bundleinfo.
144 |     --filter: Use predicates of the form 'key=regex' to filter out which files
145 |               are displayed. Valid keys are the same as for --key. Value is a
146 |               case-insensitive regular expression which must match anywhere in
147 |               the keyed property value for the file's info to be displayed.
148 |               You may specify multiple filters by repeating this flag.
149 |               If multiple filters are specified, any match will display the
150 |               file.
151 |     --filter-inclusive: If multiple filters are specified, they must all match
152 |                         for the file to be displayed.
153 |     --entitlements: If the file has entitlements, will also display them
154 |     --bundleinfo: If the file is part of a bundle, will also display bundle
155 |                   hash information and hashes of all bundle executables.
156 |                   Incompatible with --recursive and --cert-index.
157 | 
158 | Examples: santactl fileinfo --cert-index 1 --key SHA-256 --json /usr/bin/yes
159 |           santactl fileinfo --key SHA-256 --json /usr/bin/yes
160 |           santactl fileinfo /usr/bin/yes /bin/*
161 |           santactl fileinfo /usr/bin -r --key Path --key SHA-256 --key Rule
162 |           santactl fileinfo /usr/bin/* --filter Type=Script --filter Path=zip`
163 | 
164 | 	return mcp_golang.NewPromptResponse("santactl_fileinfo_prompt",
165 | 		mcp_golang.NewPromptMessage(mcp_golang.NewTextContent(prompt),
166 | 			mcp_golang.RoleUser)), nil
167 | }
168 | 
169 | func santaSubCommandPrompt(args EmptySantaCtlArgs) (*mcp_golang.PromptResponse, error) {
170 | 	// Prompt for the santactl subcommand
171 | 	santactlPrompt := `santactl has the following sub commands
172 | 
173 | 		Usage: santactl:
174 | 	  fileinfo - Prints information about a file.
175 | 	   metrics - Show Santa metric information.
176 | 	  printlog - Prints the contents of Santa protobuf log files as JSON.
177 | 		  rule - Manually add/remove/check rules.
178 | 		status - Show Santa status information.
179 | 		  sync - Synchronizes Santa with a configured server.
180 | 	   version - Show Santa component versions.
181 | 	   `
182 | 
183 | 	return mcp_golang.NewPromptResponse("santactl subcommands",
184 | 		mcp_golang.NewPromptMessage(mcp_golang.NewTextContent(santactlPrompt),
185 | 			mcp_golang.RoleAssistant)), nil
186 | }
187 | 
188 | func santaStatusCommandPrompt(args EmptySantaCtlArgs) (*mcp_golang.PromptResponse, error) {
189 | 	// Prompt for the status command
190 | 	statusPrompt := `santactl status produces a set of key value pairs
191 | 	describing how santa operates. The database section is for execution rules
192 | 	and the watch items related to file access authorization rules.
193 | 
194 | 	Do not conflate the watch items and the database items keep all statistics
195 | 	separate.
196 | 
197 | 	Additionally if santa is not configured to use a sync service then local
198 | 	admin users may add rules using the santactl rule command. This is also true
199 | 	if santa does not have any static rules.
200 | 	`
201 | 
202 | 	return mcp_golang.NewPromptResponse("santactl_status_prompt",
203 | 		mcp_golang.NewPromptMessage(mcp_golang.NewTextContent(statusPrompt),
204 | 			mcp_golang.RoleAssistant)), nil
205 | }
206 | 
207 | func main() {
208 | 	done := make(chan struct{})
209 | 
210 | 	server := mcp_golang.NewServer(stdio.NewStdioServerTransport())
211 | 
212 | 	err := server.RegisterTool("santactl_version",
213 | 		"List santa daemon versions", santaVersion)
214 | 	if err != nil {
215 | 		log.Fatalf("Failed to register santactl_version tool: %v", err)
216 | 	}
217 | 
218 | 	err = server.RegisterTool("santactl_status",
219 | 		"Get the status of the Santa daemon", santaStatus)
220 | 	if err != nil {
221 | 		log.Fatalf("Failed to register santactl_status tool: %v", err)
222 | 	}
223 | 
224 | 	err = server.RegisterTool("santactl_metrics",
225 | 		"Get performance metrics from the Santa daemon", santaMetrics)
226 | 
227 | 	if err != nil {
228 | 		log.Fatalf("Failed to register santactl_metrics tool: %v", err)
229 | 	}
230 | 
231 | 	err = server.RegisterTool("santactl_sync",
232 | 		"Have Santa perform a sync of the Santa rules database from the sync server",
233 | 		santaSyncRules)
234 | 
235 | 	if err != nil {
236 | 		log.Fatalf("Failed to register santactl_sync tool: %v", err)
237 | 	}
238 | 
239 | 	err = server.RegisterTool("santactl_fileinfo",
240 | 		"Get file information for a given absolute file path", santaFileinfo)
241 | 	if err != nil {
242 | 		log.Fatalf("Failed to register santactl_fileinfo tool: %v", err)
243 | 	}
244 | 
245 | 	err = server.RegisterPrompt("santactl_status_prompt",
246 | 		"explain the data in a santactl_status output", santaStatusCommandPrompt)
247 | 
248 | 	if err != nil {
249 | 		log.Fatalf("Failed to register prompt: %v", err)
250 | 	}
251 | 
252 | 	err = server.RegisterPrompt("santactl_subcommand_prompt",
253 | 		"list of valid santactl subcommands", santaSubCommandPrompt)
254 | 
255 | 	if err != nil {
256 | 		log.Fatalf("Failed to register prompt: %v", err)
257 | 	}
258 | 
259 | 	err = server.RegisterPrompt("santactl_fileinfo_prompt",
260 | 		"list of valid santactl subcommands", santaFileInfoPrompt)
261 | 
262 | 	if err != nil {
263 | 		log.Fatalf("Failed to register prompt: %v", err)
264 | 	}
265 | 
266 | 	err = server.Serve()
267 | 	if err != nil {
268 | 		log.Fatalf("Failed to serve: %v", err)
269 | 	}
270 | 	<-done
271 | }
272 | 
```