+12
-3
@@ -220,5 +220,14 @@ #!/usr/bin/env node | ||
| `Not saved — "${result.memory.text}" looks like a semantic duplicate of an ` + | ||
| `existing memory: ${near}. If it is genuinely the same fact, nothing to do. If it ` + | ||
| `is a distinct fact or an update, re-save with a \`subject\` so the gate treats it ` + | ||
| `as its own memory (or a time-aware update of that subject).`; | ||
| `existing memory: ${near}. If it is genuinely the same fact, nothing to do. ` + | ||
| // The advice has to match what the caller actually sent. Since 0.8.0 this branch | ||
| // is reachable WITH a subject (D-044), and telling an agent that just supplied | ||
| // one to "re-save with a subject" reads as the gate ignoring its input. Point it | ||
| // at the existing memory's subject, which is the thing that would actually work. | ||
| (result.memory.subject | ||
| ? `If it is an UPDATE to that memory, re-save it with the EXISTING memory's ` + | ||
| `subject (shown above) rather than "${result.memory.subject}" — supersession ` + | ||
| `matches on the subject string, so a different spelling reads as a different ` + | ||
| `topic. If it is a genuinely distinct fact, say so and re-save it.` | ||
| : `If it is a distinct fact or an update, re-save with a \`subject\` so the gate ` + | ||
| `treats it as its own memory (or a time-aware update of that subject).`); | ||
| } | ||
@@ -225,0 +234,0 @@ else { |
+12
-1
@@ -10,3 +10,14 @@ import { execFile } from "node:child_process"; | ||
| const execFileAsync = promisify(execFile); | ||
| const VALID_CLIENTS = ["claude-code", "claude-desktop", "cursor", "windsurf"]; | ||
| const VALID_CLIENTS = [ | ||
| "claude-code", | ||
| "claude-desktop", | ||
| "cursor", | ||
| "windsurf", | ||
| "gemini-cli", | ||
| "vscode", | ||
| "cline", | ||
| "roo", | ||
| "opencode", | ||
| "zed", | ||
| ]; | ||
| /** Parse `setup` flags: `--dry-run`, `--remote <url>`, `--token <token>`, `--client <id>...`. */ | ||
@@ -13,0 +24,0 @@ export function parseSetupArgs(argv, env) { |
+207
-26
@@ -6,2 +6,8 @@ import { join } from "node:path"; | ||
| } | ||
| /** The XDG config base (`$XDG_CONFIG_HOME` or `~/.config`), used by the CLIs/editors that | ||
| * follow the spec on macOS + Linux (Gemini, OpenCode, Zed). */ | ||
| function xdgConfig(env) { | ||
| const h = home(env); | ||
| return env.XDG_CONFIG_HOME || (h ? join(h, ".config") : null); | ||
| } | ||
| /** Resolve the base config directory used by Claude Desktop, per platform. */ | ||
@@ -17,5 +23,28 @@ function claudeDesktopDir(platform, env) { | ||
| // Linux and everything else follow the XDG base-dir spec. | ||
| const base = env.XDG_CONFIG_HOME || (h ? join(h, ".config") : null); | ||
| const base = xdgConfig(env); | ||
| return base ? join(base, "Claude") : null; | ||
| } | ||
| /** VS Code's per-user data directory (`.../Code/User`), where the user-level `mcp.json` lives | ||
| * and under which VS Code extensions (Cline, Roo) keep their `globalStorage`. Stable "Code" | ||
| * only — Insiders/VSCodium use a different folder and are left to manual config. */ | ||
| function vscodeUserDir(platform, env) { | ||
| const h = home(env); | ||
| if (platform === "darwin") { | ||
| return h ? join(h, "Library", "Application Support", "Code", "User") : null; | ||
| } | ||
| if (platform === "win32") { | ||
| return env.APPDATA ? join(env.APPDATA, "Code", "User") : null; | ||
| } | ||
| const base = xdgConfig(env); | ||
| return base ? join(base, "Code", "User") : null; | ||
| } | ||
| /** Zed's config base: `~/.config/zed` on macOS + Linux (Zed uses XDG even on macOS), | ||
| * `%APPDATA%\Zed` on Windows. */ | ||
| function zedDir(platform, env) { | ||
| if (platform === "win32") { | ||
| return env.APPDATA ? join(env.APPDATA, "Zed") : null; | ||
| } | ||
| const base = xdgConfig(env); | ||
| return base ? join(base, "zed") : null; | ||
| } | ||
| export const CLIENTS = [ | ||
@@ -26,2 +55,4 @@ { | ||
| serverKey: "jamgate", | ||
| containerKey: "mcpServers", | ||
| shape: "claude-code", | ||
| supportsRemote: true, | ||
@@ -43,2 +74,4 @@ configPath(_platform, env) { | ||
| serverKey: "jamgate", | ||
| containerKey: "mcpServers", | ||
| shape: "plain", | ||
| // Claude Desktop installs local (stdio) servers natively; remote servers go through the | ||
@@ -60,2 +93,4 @@ // connectors UI / mcp-remote, not a plain HTTP entry, so we don't claim remote support. | ||
| serverKey: "jamgate", | ||
| containerKey: "mcpServers", | ||
| shape: "plain", | ||
| supportsRemote: true, | ||
@@ -75,5 +110,7 @@ configPath(_platform, env) { | ||
| serverKey: "jamgate", | ||
| // Windsurf's remote support is SSE-based (`serverUrl`) rather than Streamable HTTP; rather | ||
| // than write an entry we haven't verified round-trips, we wire stdio only. | ||
| supportsRemote: false, | ||
| containerKey: "mcpServers", | ||
| // Windsurf (Cascade) now officially documents Streamable HTTP for remote servers, using its | ||
| // own `serverUrl` field + a `headers` object (D-046). We wire that in remote mode. | ||
| shape: "windsurf", | ||
| supportsRemote: true, | ||
| configPath(_platform, env) { | ||
@@ -88,2 +125,114 @@ const h = home(env); | ||
| }, | ||
| { | ||
| id: "gemini-cli", | ||
| label: "Gemini CLI", | ||
| serverKey: "jamgate", | ||
| containerKey: "mcpServers", | ||
| // Remote is Streamable HTTP via `httpUrl` (Gemini reserves plain `url` for SSE). | ||
| shape: "gemini", | ||
| supportsRemote: true, | ||
| // settings.json is Gemini's whole CLI config, not an MCP-only file. | ||
| sharedConfig: true, | ||
| configPath(_platform, env) { | ||
| const h = home(env); | ||
| return h ? join(h, ".gemini", "settings.json") : null; | ||
| }, | ||
| detectPaths(_platform, env) { | ||
| const h = home(env); | ||
| return h ? [join(h, ".gemini")] : []; | ||
| }, | ||
| }, | ||
| { | ||
| id: "vscode", | ||
| label: "VS Code (Copilot)", | ||
| serverKey: "jamgate", | ||
| // VS Code's container key is `servers`, not `mcpServers`, and every entry carries a `type`. | ||
| containerKey: "servers", | ||
| shape: "vscode", | ||
| supportsRemote: true, | ||
| configPath(platform, env) { | ||
| const dir = vscodeUserDir(platform, env); | ||
| return dir ? join(dir, "mcp.json") : null; | ||
| }, | ||
| detectPaths(platform, env) { | ||
| const dir = vscodeUserDir(platform, env); | ||
| return dir ? [dir] : []; | ||
| }, | ||
| }, | ||
| { | ||
| id: "cline", | ||
| label: "Cline", | ||
| serverKey: "jamgate", | ||
| containerKey: "mcpServers", | ||
| // Remote transport tag is the camelCase `streamableHttp` (NOT Roo's hyphenated form). | ||
| shape: "cline", | ||
| supportsRemote: true, | ||
| configPath(platform, env) { | ||
| const dir = vscodeUserDir(platform, env); | ||
| return dir | ||
| ? join(dir, "globalStorage", "saoudrizwan.claude-dev", "settings", "cline_mcp_settings.json") | ||
| : null; | ||
| }, | ||
| detectPaths(platform, env) { | ||
| const dir = vscodeUserDir(platform, env); | ||
| return dir ? [join(dir, "globalStorage", "saoudrizwan.claude-dev")] : []; | ||
| }, | ||
| }, | ||
| { | ||
| id: "roo", | ||
| label: "Roo Code", | ||
| serverKey: "jamgate", | ||
| containerKey: "mcpServers", | ||
| // Remote transport tag is the hyphenated `streamable-http` (NOT Cline's camelCase form). | ||
| shape: "roo", | ||
| supportsRemote: true, | ||
| configPath(platform, env) { | ||
| const dir = vscodeUserDir(platform, env); | ||
| return dir | ||
| ? join(dir, "globalStorage", "rooveterinaryinc.roo-cline", "settings", "mcp_settings.json") | ||
| : null; | ||
| }, | ||
| detectPaths(platform, env) { | ||
| const dir = vscodeUserDir(platform, env); | ||
| return dir ? [join(dir, "globalStorage", "rooveterinaryinc.roo-cline")] : []; | ||
| }, | ||
| }, | ||
| { | ||
| id: "opencode", | ||
| label: "OpenCode", | ||
| serverKey: "jamgate", | ||
| // OpenCode nests servers under `mcp`, with a single `command` array and an `enabled` flag. | ||
| containerKey: "mcp", | ||
| shape: "opencode", | ||
| supportsRemote: true, | ||
| // opencode.json holds providers/models/etc., not just MCP servers. | ||
| sharedConfig: true, | ||
| configPath(_platform, env) { | ||
| const base = xdgConfig(env); | ||
| return base ? join(base, "opencode", "opencode.json") : null; | ||
| }, | ||
| detectPaths(_platform, env) { | ||
| const base = xdgConfig(env); | ||
| return base ? [join(base, "opencode")] : []; | ||
| }, | ||
| }, | ||
| { | ||
| id: "zed", | ||
| label: "Zed", | ||
| serverKey: "jamgate", | ||
| // Zed's container key is `context_servers`; its custom-server entry is a bare {command,args}. | ||
| containerKey: "context_servers", | ||
| shape: "plain", | ||
| supportsRemote: true, | ||
| // settings.json is Zed's entire editor config (and commonly `//`-commented). | ||
| sharedConfig: true, | ||
| configPath(platform, env) { | ||
| const dir = zedDir(platform, env); | ||
| return dir ? join(dir, "settings.json") : null; | ||
| }, | ||
| detectPaths(platform, env) { | ||
| const dir = zedDir(platform, env); | ||
| return dir ? [dir] : []; | ||
| }, | ||
| }, | ||
| ]; | ||
@@ -96,30 +245,62 @@ export function clientById(id) { | ||
| } | ||
| /** The bearer `Authorization` header for the remote endpoint, or undefined when no token. */ | ||
| function authHeaders(token) { | ||
| return token ? { Authorization: `Bearer ${token}` } : undefined; | ||
| } | ||
| /** | ||
| * Build the server entry to write for a given client and mode. The stdio entry runs the | ||
| * published package with `npx jamgate` so it always tracks the installed version; the remote | ||
| * entry points at the user's self-hosted HTTP endpoint with a bearer header. Claude Code gets | ||
| * an explicit `type` to match how its own `claude mcp add` records entries. | ||
| * entry points at the user's self-hosted HTTP endpoint with a bearer header. Each client's | ||
| * exact field names come straight from its official docs — see {@link EntryShape}. | ||
| */ | ||
| export function buildEntry(client, params) { | ||
| if (params.mode === "remote") { | ||
| if (!params.url) | ||
| throw new Error("remote mode requires a url"); | ||
| const headers = {}; | ||
| if (params.token) | ||
| headers.Authorization = `Bearer ${params.token}`; | ||
| const entry = { url: params.url }; | ||
| if (Object.keys(headers).length > 0) | ||
| entry.headers = headers; | ||
| if (client.id === "claude-code") | ||
| entry.type = "http"; | ||
| return entry; | ||
| const remote = params.mode === "remote"; | ||
| if (remote && !params.url) | ||
| throw new Error("remote mode requires a url"); | ||
| const headers = authHeaders(params.token); | ||
| switch (client.shape) { | ||
| case "claude-code": | ||
| if (remote) | ||
| return withHeaders({ type: "http", url: params.url }, headers); | ||
| // Match `claude mcp add`'s recorded shape exactly (type + empty env) so an entry it wrote | ||
| // is recognised as already-configured on re-run rather than read as a spurious diff. | ||
| return { command: "npx", args: ["jamgate"], type: "stdio", env: {} }; | ||
| case "gemini": | ||
| // Streamable HTTP is `httpUrl` in Gemini CLI; plain `url` would select SSE instead. | ||
| if (remote) | ||
| return withHeaders({ httpUrl: params.url }, headers); | ||
| return { command: "npx", args: ["jamgate"] }; | ||
| case "vscode": | ||
| if (remote) | ||
| return withHeaders({ type: "http", url: params.url }, headers); | ||
| return { type: "stdio", command: "npx", args: ["jamgate"] }; | ||
| case "cline": | ||
| if (remote) | ||
| return withHeaders({ type: "streamableHttp", url: params.url }, headers); | ||
| return { command: "npx", args: ["jamgate"] }; | ||
| case "roo": | ||
| if (remote) | ||
| return withHeaders({ type: "streamable-http", url: params.url }, headers); | ||
| return { command: "npx", args: ["jamgate"] }; | ||
| case "opencode": | ||
| if (remote) | ||
| return withHeaders({ type: "remote", url: params.url, enabled: true }, headers); | ||
| return { type: "local", command: ["npx", "jamgate"], enabled: true }; | ||
| case "windsurf": | ||
| // Windsurf's remote URL field is `serverUrl`, not `url`. | ||
| if (remote) | ||
| return withHeaders({ serverUrl: params.url }, headers); | ||
| return { command: "npx", args: ["jamgate"] }; | ||
| case "plain": | ||
| default: | ||
| if (remote) | ||
| return withHeaders({ url: params.url }, headers); | ||
| return { command: "npx", args: ["jamgate"] }; | ||
| } | ||
| const entry = { command: "npx", args: ["jamgate"] }; | ||
| // Claude Code records stdio servers as {type, command, args, env}. Match that shape exactly | ||
| // so an entry written by `claude mcp add` is recognised as already-configured on re-run | ||
| // (otherwise the empty `env` would read as a spurious diff and re-"update" every run). | ||
| if (client.id === "claude-code") { | ||
| entry.type = "stdio"; | ||
| entry.env = {}; | ||
| } | ||
| } | ||
| /** Attach a `headers` object to an entry only when there is one (keeps token-less remote entries | ||
| * free of an empty `headers` key so they stay byte-identical across re-runs). */ | ||
| function withHeaders(entry, headers) { | ||
| if (headers) | ||
| entry.headers = headers; | ||
| return entry; | ||
@@ -126,0 +307,0 @@ } |
@@ -32,9 +32,11 @@ function isPlainObject(value) { | ||
| * @param entry The desired server entry. | ||
| * @param container The top-level key the servers live under ("mcpServers" for most clients; | ||
| * "servers"/"context_servers"/"mcp" for VS Code/Zed/OpenCode). | ||
| */ | ||
| export function planMerge(existing, key, entry) { | ||
| export function planMerge(existing, key, entry, container = "mcpServers") { | ||
| const hadValidRoot = isPlainObject(existing); | ||
| const root = hadValidRoot ? { ...existing } : {}; | ||
| const hadServers = isPlainObject(root.mcpServers); | ||
| const hadServers = isPlainObject(root[container]); | ||
| const servers = hadServers | ||
| ? { ...root.mcpServers } | ||
| ? { ...root[container] } | ||
| : {}; | ||
@@ -51,4 +53,4 @@ const current = servers[key]; | ||
| servers[key] = entry; | ||
| root.mcpServers = servers; | ||
| root[container] = servers; | ||
| return { config: root, status, changed: true }; | ||
| } |
+21
-3
@@ -19,2 +19,8 @@ import { promises as fs } from "node:fs"; | ||
| export const BACKUP_SUFFIX = ".jamgate-backup"; | ||
| /** Does a wired entry describe a remote (HTTP) transport? Remote entries carry a URL field — | ||
| * `url`, or a client-specific alias (`httpUrl` for Gemini, `serverUrl` for Windsurf) — whereas | ||
| * stdio entries carry `command`. Used only for status reporting. */ | ||
| function isRemoteEntry(entry) { | ||
| return "url" in entry || "httpUrl" in entry || "serverUrl" in entry; | ||
| } | ||
| async function pathExists(p) { | ||
@@ -99,3 +105,15 @@ try { | ||
| const { existing, malformed, fileExists } = await readConfig(path); | ||
| const plan = planMerge(existing, client.serverKey, entry); | ||
| // Safety guard for SHARED config files (Gemini/Zed/OpenCode): if the file exists but doesn't | ||
| // parse as strict JSON (almost always `//` comments), we must NOT rewrite it — doing so would | ||
| // clobber the user's entire settings down to just our entry. Skip with a clear pointer instead. | ||
| // Dedicated MCP-only files (Cursor, Cline, …) keep the tolerant "rewrite malformed" behaviour, | ||
| // since there the backup already covers the only thing at risk. | ||
| if (client.sharedConfig && malformed && fileExists) { | ||
| return { | ||
| ...base, | ||
| outcome: "skipped", | ||
| detail: `existing config isn't plain JSON (comments?) — add jamgate to ${client.containerKey} manually to keep it intact`, | ||
| }; | ||
| } | ||
| const plan = planMerge(existing, client.serverKey, entry, client.containerKey); | ||
| if (plan.status === "already-configured") { | ||
@@ -178,3 +196,3 @@ return { ...base, outcome: "already-configured" }; | ||
| const servers = existing && typeof existing === "object" && existing !== null | ||
| ? existing.mcpServers | ||
| ? existing[client.containerKey] | ||
| : undefined; | ||
@@ -186,3 +204,3 @@ const entry = servers && typeof servers === "object" && servers !== null | ||
| wired = true; | ||
| transport = "url" in entry ? "http" : "stdio"; | ||
| transport = isRemoteEntry(entry) ? "http" : "stdio"; | ||
| } | ||
@@ -189,0 +207,0 @@ } |
+1
-1
| { | ||
| "name": "jamgate", | ||
| "version": "0.8.0", | ||
| "version": "0.9.0", | ||
| "mcpName": "io.github.amirj4m/jamgate", | ||
@@ -5,0 +5,0 @@ "description": "A neutral, cross-agent memory quality gate for AI agents, delivered as an MCP server — a gate, not a store.", |
+99
-5
@@ -95,3 +95,4 @@ # Jamgate | ||
| One command detects the MCP clients installed on your machine (Claude Code, Claude Desktop, | ||
| Cursor, Windsurf) and wires Jamgate into each: | ||
| Cursor, Windsurf, Gemini CLI, VS Code / Copilot, Cline, Roo Code, OpenCode, Zed) and wires | ||
| Jamgate into each: | ||
@@ -157,2 +158,74 @@ ```bash | ||
| **Gemini CLI** — add the same `mcpServers` block to `~/.gemini/settings.json`. | ||
| **Cline / Roo Code** — add the same `mcpServers` block to the extension's MCP settings file | ||
| (Cline: `.../globalStorage/saoudrizwan.claude-dev/settings/cline_mcp_settings.json`; Roo: | ||
| `.../globalStorage/rooveterinaryinc.roo-cline/settings/mcp_settings.json`), or use each | ||
| extension's "Configure/Edit MCP Servers" button. | ||
| **VS Code (Copilot)** — add to the user `mcp.json` (Command Palette → **MCP: Open User | ||
| Configuration**). VS Code uses a `servers` key and an explicit `type`: | ||
| ```json | ||
| { | ||
| "servers": { | ||
| "jamgate": { "type": "stdio", "command": "npx", "args": ["jamgate"] } | ||
| } | ||
| } | ||
| ``` | ||
| **OpenCode** — add to `~/.config/opencode/opencode.json` under the `mcp` key (note the single | ||
| `command` array and `enabled` flag): | ||
| ```json | ||
| { | ||
| "mcp": { | ||
| "jamgate": { "type": "local", "command": ["npx", "jamgate"], "enabled": true } | ||
| } | ||
| } | ||
| ``` | ||
| **Zed** — add to `settings.json` under `context_servers`: | ||
| ```json | ||
| { | ||
| "context_servers": { | ||
| "jamgate": { "command": "npx", "args": ["jamgate"] } | ||
| } | ||
| } | ||
| ``` | ||
| #### Supported agents | ||
| `jamgate setup` auto-wires every agent below whose MCP config it can merge **losslessly** — | ||
| each entry shape is verified against the vendor's official docs. Agents whose config lives in a | ||
| non-JSON format we can't safely round-trip (TOML / YAML) are listed as **manual** with the | ||
| one-liner to add yourself. | ||
| | Agent | Config file | `setup` | Remote (`--remote`) | | ||
| | --- | --- | --- | --- | | ||
| | Claude Code | `~/.claude.json` | ✅ auto | ✅ | | ||
| | Claude Desktop | `claude_desktop_config.json` | ✅ auto | connectors UI | | ||
| | Cursor | `~/.cursor/mcp.json` | ✅ auto | ✅ | | ||
| | Windsurf | `~/.codeium/windsurf/mcp_config.json` | ✅ auto | ✅ | | ||
| | Gemini CLI | `~/.gemini/settings.json` | ✅ auto | ✅ | | ||
| | VS Code (Copilot) | `<Code>/User/mcp.json` | ✅ auto | ✅ | | ||
| | Cline | `.../saoudrizwan.claude-dev/settings/cline_mcp_settings.json` | ✅ auto | ✅ | | ||
| | Roo Code | `.../rooveterinaryinc.roo-cline/settings/mcp_settings.json` | ✅ auto | ✅ | | ||
| | OpenCode | `~/.config/opencode/opencode.json` | ✅ auto | ✅ | | ||
| | Zed | `~/.config/zed/settings.json` | ✅ auto | ✅ | | ||
| | Codex CLI | `~/.codex/config.toml` (TOML) | manual¹ | — | | ||
| | Goose | `~/.config/goose/config.yaml` (YAML) | manual¹ | — | | ||
| | Continue | `~/.continue/config.yaml` (YAML) | manual¹ | — | | ||
| ¹ **Manual** — these use TOML/YAML; rather than risk mangling comments/formatting we don't | ||
| auto-edit them. Add Jamgate by hand: **Codex CLI** → | ||
| `[mcp_servers.jamgate]` with `command = "npx"` and `args = ["jamgate"]` in `~/.codex/config.toml`; | ||
| **Goose** → a `stdio` extension under `extensions:` with `cmd: npx` / `args: ["jamgate"]`; | ||
| **Continue** → an `mcpServers:` list entry with `command: npx` / `args: [jamgate]`. | ||
| > For agents that live in a shared, comment-friendly settings file (Gemini, OpenCode, Zed), | ||
| > `setup` will **skip** rather than overwrite a file it can't parse as strict JSON — so a | ||
| > `//`-commented `settings.json` is never clobbered; add the block by hand in that case. | ||
| Restart the agent. It now has three tools: | ||
@@ -172,2 +245,21 @@ | ||
| ## Agent skill: `memory-discipline` | ||
| Wiring in the three tools gives an agent the *ability* to remember. The | ||
| **`memory-discipline`** skill teaches it the *habits* — recall before answering, | ||
| save one granular durable fact at a time with a specific reused `subject`, never send | ||
| secrets, and treat gate verdicts as answers rather than errors to retry. Its rules are | ||
| distilled straight from Jamgate's own [decision log](./DECISIONS.md) (D-040…D-045). | ||
| It ships in this repo at [`skills/memory-discipline/SKILL.md`](./skills/memory-discipline/SKILL.md) | ||
| as a portable [agentskills.io](https://agentskills.io) instruction pack, installable into | ||
| 70+ coding agents with one command: | ||
| ```bash | ||
| npx skills add amirj4m/jamgate | ||
| ``` | ||
| The skill is prompt text, not code — it is **not** part of the npm package (the | ||
| `files` whitelist ships only `dist`), so it never bloats the runtime install. | ||
| ## Optional: local semantic search | ||
@@ -361,3 +453,4 @@ | ||
| - **Desktops (Claude Code, Cursor, Windsurf) — one command:** | ||
| - **Desktops (Claude Code, Cursor, Windsurf, Gemini CLI, VS Code, Cline, Roo Code, OpenCode, | ||
| Zed) — one command:** | ||
@@ -369,3 +462,3 @@ ```bash | ||
| This wires every detected client on that machine to your instance (Streamable HTTP clients | ||
| only; others are skipped with a reason). | ||
| only; others — e.g. Claude Desktop — are skipped with a reason). | ||
@@ -608,4 +701,5 @@ - **Phone (Claude app) and claude.ai in a browser:** Settings → **Connectors** → *Add custom | ||
| - **One-click install** — `npx jamgate setup` wires every detected client (Claude Code, | ||
| Claude Desktop, Cursor, Windsurf) in one idempotent, backup-first command, plus a Cursor | ||
| deeplink and a Claude Desktop `.mcpb` bundle. | ||
| Claude Desktop, Cursor, Windsurf, Gemini CLI, VS Code / Copilot, Cline, Roo Code, OpenCode, | ||
| Zed) in one idempotent, backup-first command, plus a Cursor deeplink and a Claude Desktop | ||
| `.mcpb` bundle. | ||
| - **Deploy your own** *(no terminal)* — a `Dockerfile`, a Render blueprint, and a Railway | ||
@@ -612,0 +706,0 @@ config so a non-technical user can click a button, log into a platform, and get their own |
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
262590
5.56%4666
4.97%731
14.76%