notion-mcp-server
Version:
Notion MCP server with token auth: connect Claude Code, Claude Desktop, Cursor, VS Code, Codex and any MCP client to Notion. Search, create and edit pages, query databases, comment and upload files. Runs headless in CI and agents.
69 lines (68 loc) • 3.19 kB
JavaScript
import { posix, win32 } from "node:path";
function pathFor(platform) {
return platform === "win32" ? win32 : posix;
}
// The default filesystems on Windows and macOS ignore case, so
// ~/library/application support is the same folder as ~/Library/Application Support.
function fold(p, platform) {
return platform === "win32" || platform === "darwin" ? p.toLowerCase() : p;
}
/** Whether `p` is `dir` or below it. Both must be absolute. */
export function isInside(p, dir, platform) {
const path = pathFor(platform);
const rel = path.relative(fold(dir, platform), fold(p, platform));
return rel === "" || (rel !== ".." && !rel.startsWith(".." + path.sep) && !path.isAbsolute(rel));
}
/**
* The first dot-named segment of a path: ~/.ssh, ~/.aws, ~/.config, .env,
* .git, .npmrc. Credentials and tokens live under names like these.
*/
export function dotSegment(p, platform) {
const segments = platform === "win32" ? p.split(/[\\/]/) : p.split("/");
return segments.find((s) => s.startsWith(".") && s !== "." && s !== "..");
}
/**
* The directories outside dot-names that hold credentials or never-ending
* files. Lexical: the caller resolves symlinks in them too.
*
* - /proc, /sys, /dev: /proc/self/environ is a regular file of size 0 that
* holds the server's environment, NOTION_TOKEN included, and /dev/zero
* never ends.
* - /run/secrets and /var/run/secrets: Docker and Kubernetes secrets, the
* pod's service-account token among them.
* - macOS ~/Library: Claude Desktop's claude_desktop_config.json, where the
* README puts NOTION_TOKEN, and the same for Cursor, Windsurf and VS Code
* under Application Support, plus keychains, cookies and mail. iCloud
* Drive and the cloud-storage folders (Dropbox, Google Drive, OneDrive)
* also sit under ~/Library and hold ordinary documents, so they stay open.
* - Windows %APPDATA% and %LOCALAPPDATA% (~\AppData): the same configs, and
* browser profiles.
* - XDG_CONFIG_HOME, XDG_DATA_HOME, XDG_STATE_HOME when set: they default to
* dot-named folders, but can be pointed anywhere.
*/
export function sensitiveZones(platform, home, env) {
const path = pathFor(platform);
const absolute = (dir) => !!dir && path.isAbsolute(dir);
if (platform === "win32") {
return [path.join(home, "AppData"), env.APPDATA, env.LOCALAPPDATA]
.filter(absolute)
.map((dir) => ({ dir }));
}
const zones = ["/proc", "/sys", "/dev", "/run/secrets", "/var/run/secrets"].map((dir) => ({ dir }));
if (platform === "darwin") {
const library = path.join(home, "Library");
zones.push({
dir: library,
allow: [path.join(library, "Mobile Documents"), path.join(library, "CloudStorage")],
});
}
for (const dir of [env.XDG_CONFIG_HOME, env.XDG_DATA_HOME, env.XDG_STATE_HOME]) {
if (absolute(dir))
zones.push({ dir });
}
return zones;
}
/** The zone `p` falls in, or undefined when it falls in none. */
export function zoneOf(p, zones, platform) {
return zones.find((z) => isInside(p, z.dir, platform) && !(z.allow ?? []).some((a) => isInside(p, a, platform)))?.dir;
}