# 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 | [](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 |
```