Secure MCP endpoint for LAN clients
This commit is contained in:
+2
-1
@@ -1,4 +1,5 @@
|
||||
# The service binds to localhost only. Set a token if your harness supports bearer auth.
|
||||
# Keep localhost for same-Mac clients. For LAN clients, set this to the Mac's LAN IP.
|
||||
MACMINI_MCP_HOST=127.0.0.1
|
||||
MACMINI_MCP_PORT=7331
|
||||
# Required whenever MACMINI_MCP_HOST is not loopback.
|
||||
# MACMINI_MCP_TOKEN=replace-with-a-long-random-token
|
||||
|
||||
@@ -35,7 +35,7 @@ npm run check
|
||||
npm run service:install
|
||||
```
|
||||
|
||||
The service endpoint is:
|
||||
The service defaults to a same-Mac endpoint:
|
||||
|
||||
```text
|
||||
http://127.0.0.1:7331/mcp
|
||||
@@ -71,6 +71,21 @@ Service logs are stored in `.logs/`.
|
||||
For a harness that supports Streamable HTTP, configure the local MCP URL as
|
||||
`http://127.0.0.1:7331/mcp`.
|
||||
|
||||
For a trusted local-network harness such as a Raspberry Pi, set
|
||||
`MACMINI_MCP_HOST` in `.env` to the Mac's LAN IP and set a strong
|
||||
`MACMINI_MCP_TOKEN`. Then configure the remote MCP client with:
|
||||
|
||||
```text
|
||||
URL: http://<mac-lan-ip>:7331/mcp
|
||||
Authorization: Bearer <MACMINI_MCP_TOKEN>
|
||||
```
|
||||
|
||||
Restart after changing `.env`:
|
||||
|
||||
```sh
|
||||
npm run service:restart
|
||||
```
|
||||
|
||||
For a harness that launches stdio servers, use:
|
||||
|
||||
```json
|
||||
@@ -90,11 +105,11 @@ On first use of a Notes, Calendar, or Reminders tool, macOS may ask for
|
||||
Automation access for Node. Permit only the applications you want the server
|
||||
to control under **System Settings > Privacy & Security > Automation**.
|
||||
|
||||
The HTTP service binds to `127.0.0.1` by default. To require bearer
|
||||
authentication even locally, create `.env` from `.env.example` and set
|
||||
`MACMINI_MCP_TOKEN`; clients must then send `Authorization: Bearer <token>`.
|
||||
Do not bind to a non-loopback address without enabling authentication and
|
||||
reviewing the tools first.
|
||||
The HTTP service binds to `127.0.0.1` by default. When configured to bind to a
|
||||
LAN address, it refuses to start without `MACMINI_MCP_TOKEN`; clients must send
|
||||
`Authorization: Bearer <token>`. This is HTTP bearer authentication on your
|
||||
local network, not encrypted transport. Use it only on a trusted LAN or put it
|
||||
behind a private encrypted network such as a VPN.
|
||||
|
||||
## Development
|
||||
|
||||
|
||||
@@ -47,5 +47,5 @@ launchctl enable "$DOMAIN/$LABEL"
|
||||
launchctl kickstart -k "$DOMAIN/$LABEL"
|
||||
|
||||
echo "Installed $LABEL"
|
||||
echo "Endpoint: http://127.0.0.1:7331/mcp"
|
||||
echo "Endpoint: configured by .env (see the service log for the listening URL)"
|
||||
echo "Logs: $LOG_DIR"
|
||||
|
||||
+8
-1
@@ -14,9 +14,16 @@ export function getConfig() {
|
||||
throw new Error("MACMINI_MCP_PORT must be an integer between 1 and 65535.");
|
||||
}
|
||||
|
||||
return {
|
||||
const config = {
|
||||
host: process.env.MACMINI_MCP_HOST || "127.0.0.1",
|
||||
port,
|
||||
token: process.env.MACMINI_MCP_TOKEN || "",
|
||||
};
|
||||
|
||||
const isLoopback = ["127.0.0.1", "::1", "localhost"].includes(config.host);
|
||||
if (!isLoopback && !config.token) {
|
||||
throw new Error("MACMINI_MCP_TOKEN is required when listening beyond localhost.");
|
||||
}
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
+5
-1
@@ -1,4 +1,5 @@
|
||||
import http from "node:http";
|
||||
import { timingSafeEqual } from "node:crypto";
|
||||
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
||||
import { getConfig } from "./config.js";
|
||||
import { createMacMiniMcpServer } from "./server.js";
|
||||
@@ -9,7 +10,10 @@ function authorized(request) {
|
||||
if (!config.token) {
|
||||
return true;
|
||||
}
|
||||
return request.headers.authorization === `Bearer ${config.token}`;
|
||||
const authorization = request.headers.authorization || "";
|
||||
const expected = Buffer.from(`Bearer ${config.token}`);
|
||||
const provided = Buffer.from(authorization);
|
||||
return provided.length === expected.length && timingSafeEqual(provided, expected);
|
||||
}
|
||||
|
||||
const service = http.createServer(async (request, response) => {
|
||||
|
||||
Reference in New Issue
Block a user