apple-tools-mcp 2.1.5 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +77 -2
- package/bin/apple-tools-http-proxy.js +15 -0
- package/bin/apple-tools-http.js +16 -0
- package/examples/com.apple-tools-http.plist +35 -0
- package/examples/config.json.example +5 -0
- package/examples/mcp-client.md +98 -0
- package/index.js +535 -469
- package/indexer.js +2 -2
- package/lib/config.js +29 -1
- package/lib/httpAuth.js +106 -0
- package/lib/httpStdioProxy.js +45 -0
- package/lib/httpTransport.js +71 -0
- package/lib/processMode.js +42 -1
- package/package.json +16 -8
package/README.md
CHANGED
|
@@ -374,6 +374,81 @@ launchctl load ~/Library/LaunchAgents/com.apple-tools-mcp.indexer.plist
|
|
|
374
374
|
|
|
375
375
|
KeepAlive belongs on this indexer job only — not on the MCP stdio process.
|
|
376
376
|
|
|
377
|
+
## Remote HTTP transport (v3.0.0)
|
|
378
|
+
|
|
379
|
+
Every tool the stdio server exposes — including the write tools — is also
|
|
380
|
+
available over **Streamable HTTP**, so it's reachable from other machines,
|
|
381
|
+
not just whatever process spawned it locally. This is additive: stdio still
|
|
382
|
+
works exactly as before for local same-machine MCP clients that spawn it
|
|
383
|
+
directly. The intended MacBook clients are Claude Desktop, Claude Code,
|
|
384
|
+
Codex Desktop, and ChatGPT Desktop. Claude Desktop uses the included local
|
|
385
|
+
HTTP-to-stdio proxy; Claude Code and Codex connect to the Mini's HTTP
|
|
386
|
+
endpoint directly. ChatGPT Desktop does not currently support this private
|
|
387
|
+
MCP connection; see the client guide for its provider-supported options.
|
|
388
|
+
|
|
389
|
+
Because HTTP reaches beyond "whoever spawned this process," which stdio's
|
|
390
|
+
trust model relies on, **every HTTP request needs a bearer token.** stdio
|
|
391
|
+
has no equivalent check — a locally spawned child process is already
|
|
392
|
+
trusted by whoever spawned it.
|
|
393
|
+
|
|
394
|
+
**Entrypoint:** `node index.js --transport=http`
|
|
395
|
+
**Convenience bin:** `apple-tools-http` (`bin/apple-tools-http.js`; npm global install provides it)
|
|
396
|
+
**npm script (clone only):** `npm run http`
|
|
397
|
+
|
|
398
|
+
### Auth token
|
|
399
|
+
|
|
400
|
+
The token is a random secret generated on first run and stored in the
|
|
401
|
+
macOS Keychain (service `apple-tools-mcp-http`) — never in a config file,
|
|
402
|
+
the server's config file, or logs after the one-time generation banner.
|
|
403
|
+
There's no external account and no cost; it's a self-issued password,
|
|
404
|
+
like an SSH key.
|
|
405
|
+
|
|
406
|
+
```bash
|
|
407
|
+
apple-tools-mcp http-token # prints the token, generating one first if needed
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
Clients send it as `Authorization: Bearer <token>`. Rotate it by deleting
|
|
411
|
+
the Keychain item and restarting the server (it generates a fresh one on
|
|
412
|
+
the next run), then update every client using the old value.
|
|
413
|
+
|
|
414
|
+
### Config
|
|
415
|
+
|
|
416
|
+
`~/.apple-tools-mcp/config.json` (same file the index interval uses):
|
|
417
|
+
|
|
418
|
+
```json
|
|
419
|
+
{
|
|
420
|
+
"httpHost": "127.0.0.1",
|
|
421
|
+
"httpPort": 8421
|
|
422
|
+
}
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
Env overrides: `APPLE_TOOLS_HTTP_HOST`, `APPLE_TOOLS_HTTP_PORT`. The default
|
|
426
|
+
host is `127.0.0.1`, so HTTP is local-only until you explicitly set a network
|
|
427
|
+
address such as `0.0.0.0`. Default port is 8421 (the indexer's stdio process
|
|
428
|
+
needs no port; this is only for `--transport=http`). For remote access, use a
|
|
429
|
+
Tailscale address for clients that connect directly from your tailnet, or
|
|
430
|
+
put the server behind a TLS-terminating reverse proxy. Do not send the
|
|
431
|
+
bearer token over an untrusted LAN. Cloud-hosted connectors need a separate
|
|
432
|
+
way to reach the server; a private Tailscale address alone is insufficient.
|
|
433
|
+
|
|
434
|
+
### LaunchAgent
|
|
435
|
+
|
|
436
|
+
See `examples/com.apple-tools-http.plist` (copy to
|
|
437
|
+
`~/Library/LaunchAgents/`, replace the `REPLACE_ME*` placeholders the same
|
|
438
|
+
way as the indexer plist above, then `launchctl load` it). Runs alongside
|
|
439
|
+
the indexer LaunchAgent, not instead of it — the HTTP server is a second,
|
|
440
|
+
independent process.
|
|
441
|
+
|
|
442
|
+
### Wiring up clients
|
|
443
|
+
|
|
444
|
+
See `examples/mcp-client.md` for the MacBook setup. Short version:
|
|
445
|
+
|
|
446
|
+
- **On the Mini:** set `httpHost` to its LAN or Tailscale IP and restart the HTTP server. Retrieve the bearer token with `apple-tools-mcp http-token`.
|
|
447
|
+
- **Claude Desktop on the MacBook:** configure the included `apple-tools-http-proxy` as a local MCP server. It forwards tool calls to the Mini over HTTP.
|
|
448
|
+
- **Claude Code and Codex Desktop on the MacBook:** add `http://<mini-address>:8421/mcp` and the token. The client guide has commands for both.
|
|
449
|
+
- **ChatGPT Desktop on the MacBook:** current custom MCP support does not provide a direct private HTTP connection in the desktop app. The client guide describes the documented ChatGPT web route.
|
|
450
|
+
- **Claude / ChatGPT cloud connectors:** a connector does not connect from your device's tailnet interface. [Claude requires a publicly reachable server](https://support.claude.com/en/articles/11175166-get-started-with-custom-connectors-using-remote-mcp); [ChatGPT directs private-network servers to Secure MCP Tunnel and currently supports MCP apps on web only](https://help.openai.com/en/articles/12584461-developer-mode-and-mcp-apps-in-chatgpt). Use the provider's supported network setup before adding this server as a cloud connector.
|
|
451
|
+
|
|
377
452
|
## Available Tools
|
|
378
453
|
|
|
379
454
|
Once configured, your MCP client can use these tools:
|
|
@@ -593,7 +668,7 @@ Ask your MCP client things like:
|
|
|
593
668
|
|
|
594
669
|
## Privacy & Security
|
|
595
670
|
|
|
596
|
-
- **Local Processing**: All embeddings are generated locally using
|
|
671
|
+
- **Local Processing**: All embeddings are generated locally using Hugging Face Transformers.js
|
|
597
672
|
- **No Cloud Services**: No data is sent to external servers; the write bridge is a local unix socket, never a network port
|
|
598
673
|
- **Reads are read-only**: Search and lookup tools never modify your data. The [write tools](#write-tools-200) are the only ones that change anything, and they are opt-in per call, with `confirm` required for deletes and multi-recipient sends
|
|
599
674
|
- **No credentials**: The server holds no tokens or passwords. It uses the Mail, Messages, Calendar, and Contacts apps you are already signed into
|
|
@@ -722,4 +797,4 @@ MIT License - see [LICENSE](LICENSE) for details.
|
|
|
722
797
|
|
|
723
798
|
- Built with the [Model Context Protocol SDK](https://github.com/modelcontextprotocol/sdk)
|
|
724
799
|
- Vector search powered by [LanceDB](https://lancedb.com/)
|
|
725
|
-
- Local embeddings via [
|
|
800
|
+
- Local embeddings via [Hugging Face Transformers.js](https://github.com/huggingface/transformers.js)
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { runHttpStdioProxy } from "../lib/httpStdioProxy.js";
|
|
3
|
+
|
|
4
|
+
const url = process.argv[2];
|
|
5
|
+
if (!url || process.argv.length !== 3) {
|
|
6
|
+
console.error("Usage: apple-tools-http-proxy <http://mini-address:8421/mcp>");
|
|
7
|
+
process.exitCode = 2;
|
|
8
|
+
} else {
|
|
9
|
+
try {
|
|
10
|
+
await runHttpStdioProxy({ url, token: process.env.APPLE_TOOLS_MCP_TOKEN });
|
|
11
|
+
} catch (error) {
|
|
12
|
+
console.error(`Apple Tools HTTP proxy: ${error.message}`);
|
|
13
|
+
process.exitCode = 1;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* npm bin for the HTTP transport.
|
|
4
|
+
* Dedicated wrapper so npm 12 pack/publish keeps the CLI — `index.js` as a
|
|
5
|
+
* bin target is rewritten with "script name index.js was invalid and removed".
|
|
6
|
+
* Injects `--transport=http` so a realpath to this file still starts the
|
|
7
|
+
* HTTP server. `permissions` / `http-token` still win (same contract as the
|
|
8
|
+
* apple-tools-indexer bin name for `--mode=indexer`).
|
|
9
|
+
*/
|
|
10
|
+
if (!process.argv.includes('--transport=http')) {
|
|
11
|
+
const transportIdx = process.argv.indexOf('--transport');
|
|
12
|
+
if (transportIdx === -1 || process.argv[transportIdx + 1] !== 'http') {
|
|
13
|
+
process.argv.splice(2, 0, '--transport=http');
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
await import('../index.js');
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
3
|
+
<plist version="1.0">
|
|
4
|
+
<dict>
|
|
5
|
+
<key>Label</key>
|
|
6
|
+
<string>com.REPLACE_ME.apple-tools-http</string>
|
|
7
|
+
<key>ProgramArguments</key>
|
|
8
|
+
<array>
|
|
9
|
+
<string>/usr/bin/env</string>
|
|
10
|
+
<string>node</string>
|
|
11
|
+
<string>REPLACE_ME_WITH_REPO_PATH/bin/apple-tools-http.js</string>
|
|
12
|
+
</array>
|
|
13
|
+
<key>RunAtLoad</key>
|
|
14
|
+
<true/>
|
|
15
|
+
<key>KeepAlive</key>
|
|
16
|
+
<true/>
|
|
17
|
+
<key>ProcessType</key>
|
|
18
|
+
<string>Background</string>
|
|
19
|
+
<key>WorkingDirectory</key>
|
|
20
|
+
<string>/Users/REPLACE_ME</string>
|
|
21
|
+
<key>EnvironmentVariables</key>
|
|
22
|
+
<dict>
|
|
23
|
+
<key>PATH</key>
|
|
24
|
+
<string>REPLACE_ME_WITH_NODE_BIN_DIR:/usr/bin:/bin:/usr/sbin:/sbin</string>
|
|
25
|
+
<key>NODE_OPTIONS</key>
|
|
26
|
+
<string>--max-old-space-size=4096</string>
|
|
27
|
+
<key>HOME</key>
|
|
28
|
+
<string>/Users/REPLACE_ME</string>
|
|
29
|
+
</dict>
|
|
30
|
+
<key>StandardOutPath</key>
|
|
31
|
+
<string>/Users/REPLACE_ME/Library/Logs/apple-tools-http.out.log</string>
|
|
32
|
+
<key>StandardErrorPath</key>
|
|
33
|
+
<string>/Users/REPLACE_ME/Library/Logs/apple-tools-http.err.log</string>
|
|
34
|
+
</dict>
|
|
35
|
+
</plist>
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# MacBook MCP client setup (Streamable HTTP, v3.0.0+)
|
|
2
|
+
|
|
3
|
+
`--transport=http` exposes the same tools as stdio mode (including writes:
|
|
4
|
+
sending iMessage, editing contacts/calendar) over the network, so every
|
|
5
|
+
request must carry the bearer token from `apple-tools-mcp http-token`.
|
|
6
|
+
This HTTP transport is for connecting the MacBook's Claude Desktop,
|
|
7
|
+
Claude Code, and Codex Desktop to the MCP server on the Mac Mini. ChatGPT
|
|
8
|
+
Desktop is also a target, but its current custom MCP support does not
|
|
9
|
+
accept a private server directly in the desktop app.
|
|
10
|
+
|
|
11
|
+
## Server on the Mac Mini
|
|
12
|
+
|
|
13
|
+
Set `httpHost` in `~/.apple-tools-mcp/config.json` to the Mini's LAN or
|
|
14
|
+
Tailscale IP (or set `APPLE_TOOLS_HTTP_HOST` to that address), then restart
|
|
15
|
+
the HTTP server. The default `127.0.0.1` bind only accepts clients on the
|
|
16
|
+
Mini. Retrieve the token on the Mini with `apple-tools-mcp http-token`.
|
|
17
|
+
|
|
18
|
+
## Claude Desktop on the MacBook
|
|
19
|
+
|
|
20
|
+
Install this package on the MacBook and find the absolute path of
|
|
21
|
+
`apple-tools-http-proxy` with `command -v apple-tools-http-proxy`. In
|
|
22
|
+
`~/Library/Application Support/Claude/claude_desktop_config.json`, add a
|
|
23
|
+
local MCP server entry using that path:
|
|
24
|
+
|
|
25
|
+
```json
|
|
26
|
+
{
|
|
27
|
+
"mcpServers": {
|
|
28
|
+
"apple-tools": {
|
|
29
|
+
"command": "/absolute/path/to/apple-tools-http-proxy",
|
|
30
|
+
"args": ["http://<mini-address>:8421/mcp"],
|
|
31
|
+
"env": { "APPLE_TOOLS_MCP_TOKEN": "<token-from-mini>" }
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Merge this entry into any existing `mcpServers` object, then fully quit and
|
|
38
|
+
reopen Claude Desktop. This local proxy makes the private-network request
|
|
39
|
+
from the MacBook. Claude Desktop's **custom connector** instead connects
|
|
40
|
+
from Anthropic's cloud and cannot use the Mini's private LAN or Tailscale IP.
|
|
41
|
+
[Anthropic documents the distinction](https://support.claude.com/en/articles/11175166-get-started-with-custom-connectors-using-remote-mcp).
|
|
42
|
+
The example puts the token in Claude Desktop's local configuration; restrict
|
|
43
|
+
that file to your account.
|
|
44
|
+
|
|
45
|
+
## Claude Code on the MacBook
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
claude mcp add --transport http apple-tools http://<mini-address>:8421/mcp \
|
|
49
|
+
-H "Authorization: Bearer <token-from-mini>" \
|
|
50
|
+
-s user
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
`<mini-address>` is the Mini's LAN or Tailscale IP, matching the address
|
|
54
|
+
on which the server listens.
|
|
55
|
+
|
|
56
|
+
## Codex Desktop on the MacBook
|
|
57
|
+
|
|
58
|
+
Make the token from the Mini available as `APPLE_TOOLS_MCP_TOKEN` in the
|
|
59
|
+
MacBook environment that runs Codex, then add the server:
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
codex mcp add apple-tools --url http://<mini-address>:8421/mcp \
|
|
63
|
+
--bearer-token-env-var APPLE_TOOLS_MCP_TOKEN
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
The Codex CLI and desktop app share MCP configuration, but the token
|
|
67
|
+
environment variable must be available to whichever Codex process connects.
|
|
68
|
+
[OpenAI's Codex MCP setup guide](https://developers.openai.com/learn/docs-mcp)
|
|
69
|
+
describes their shared configuration.
|
|
70
|
+
|
|
71
|
+
## Network access
|
|
72
|
+
|
|
73
|
+
For access away from the LAN, run Tailscale on both the Mini and MacBook and
|
|
74
|
+
use the Mini's Tailscale IP. Use TLS or a trusted private network before
|
|
75
|
+
sending the bearer token across the network. Keep the Mini's HTTP server
|
|
76
|
+
running while either client uses its tools.
|
|
77
|
+
|
|
78
|
+
## ChatGPT Desktop on the MacBook
|
|
79
|
+
|
|
80
|
+
[OpenAI currently documents custom MCP apps for ChatGPT web](https://help.openai.com/en/articles/12584461-developer-mode-and-mcp-apps-in-chatgpt),
|
|
81
|
+
not ChatGPT Desktop. ChatGPT also cannot connect directly to the Mini's
|
|
82
|
+
private address. If you use ChatGPT web, OpenAI's
|
|
83
|
+
[Secure MCP Tunnel](https://developers.openai.com/api/docs/guides/secure-mcp-tunnels)
|
|
84
|
+
is the documented way to reach a private MCP server, subject to the
|
|
85
|
+
account and workspace access described there. This repository's HTTP
|
|
86
|
+
server does not by itself enable the requested ChatGPT Desktop connection.
|
|
87
|
+
|
|
88
|
+
## Getting/rotating the token
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
apple-tools-mcp http-token # print the current token (generates one if missing)
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
On the Mini, the token lives in the macOS Keychain (service
|
|
95
|
+
`apple-tools-mcp-http`), never in the server's config file or the repo. To
|
|
96
|
+
rotate it, delete the Keychain item and restart the HTTP server — it'll
|
|
97
|
+
generate a new one on the next run —
|
|
98
|
+
then update every client that was using the old value.
|