New:Introducing Socket Scanning for VS Code Marketplace Extensions.Learn more →
Get Started

@getedge/mcp

Package Overview
Dependencies
Maintainers
1
Versions
44
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@getedge/mcp

Edge MCP server: finds a fitting public agent skill for the task at hand and loads it on demand.

latest
npmnpm
Version
0.6.25
Version published
Weekly downloads
589
-78.17%
Maintainers
1
Weekly downloads
 
Created
Source

Edge MCP

Edge is a tiny stdio MCP connector to Edge's hosted skill discovery service, backed by the existing skills.sh ecosystem. It gives Claude Code or Codex these on-demand tools:

  • find_skill: search Edge's index of the skills.sh catalog for candidate skills whenever you are already deciding to look for one, comparing them against the skills available locally. Arguments: query (the full task in one sentence) and optional keywords (the 2-6 words a skill for this would be named with; 2-8 accepted). Both are searched and the hits fused before ranking; callers that send only query work unchanged.
  • use_skill: load one selected skill temporarily with npx skills use.
  • rate_skill: once the task's outcome is known, report how a skill you actually followed performed using its search request_id: helped, hurt, no_difference, or unclear, with one short line on why. Bugs and ideas about Edge go to report_issue.
  • report_issue: report a bug or idea about Edge itself (kind: bug, wrong_skill, setup or idea; message up to 2000 characters; optional request_id, and contact only if the user explicitly gave an email). Agents may call it on their own when an Edge call fails.
  • scan_skill: a security check for one skill, in one of two separate modes. A catalog skill (source + skill) runs Edge's checks on the exact pinned bundle before delivery, or reuses a current matching scan. It shows the findings, date, available outside reports, and whether Edge serves or withholds it. A local skill (local: a path to its folder or SKILL.md, or an installed skill's name) is scanned on the machine by checks bundled in the connector (a port of Edge's edge-static rules plus a committed-secret check). Nothing is uploaded unless the user opts in with share_with_snyk: true, which also runs Snyk Agent Scan if snyk-agent-scan is installed and SNYK_TOKEN is set. Snyk receives the skill's files and the machine's hostname and username.
  • benchmark: plan, and after the user confirms, run a local benchmark of candidate skills on the user's own tasks (see docs/BENCHMARK.md). Stdio only.
  • feedback: deprecated alias of report_issue, with the same input and behaviour. It is kept for at least one connector migration cycle so older prompts and hosts that cached the tool list keep working.

If Edge fails, report it: POST getedge.cc/api/feedback {kind, message}. Plain JSON with the same fields as the report_issue tool, no auth, rate limited per IP, so any agent can report with curl or WebFetch even when the MCP connection itself is broken. The backend accepts the same body at /report_issue and, for connectors up to 0.6.1, at /feedback; stored reports record which name they came in under (via). A connector that finds no /report_issue route (a backend not yet redeployed) sends the report to /feedback instead.

The 0.4 connector sends supports_rate_skill: true on load requests. The backend includes feedback guidance only with that explicit capability, so older connectors can keep loading skills without being asked to call an unavailable tool.

rate_skill requires request_id and outcome. The current outcome ask uses helped, hurt, no_difference, or unclear after the task's result is known. Older connectors can still send partly_helped, loaded_not_used, wrong_skill, or broken; reason is required for partly_helped and wrong_skill and accepts missing_steps, outdated, too_generic, wrong_domain, too_long, or other. The optional note is limited to 280 characters. Do not put the user's query or private information in it. With EDGE_FEEDBACK_DEFAULT enabled, a rating given in the first 20 seconds after loading is returned unrecorded so it can be given when the outcome is known. Only a rating bound to a skill this caller loaded can nudge that same key or install's next similar search, by at most two catalog positions. EDGE_FEEDBACK=off opts out; npx @getedge/mcp feedback-reset forgets stored ratings.

The server identifies the skill from the search's presented candidates and successful loads, ignoring client-supplied skill identifiers. Exactly one distinct skill must have been loaded under that request, and that search must have presented it. A rating is accepted from the client that made the search: the same session, the same install (a restarted stdio connector), the same key, or the remote connector (whose hosts reconnect with a new MCP session id). A rating for another client's search is refused with HTTP 403 and an explanation. Each request and skill can be rated once; duplicates return HTTP 409. Unknown, expired, unloaded, crossed, or ambiguous requests are stored with a binding flag and excluded from counts. Search bindings live in bounded process memory with a sliding 12-hour lifetime; after a restart, old requests are unknown and feedback is not counted. Multiple loaded skills under one request are ambiguous; use a separate search request for each skill you intend to rate.

Feedback appends a rate telemetry event. Notes are stored as text and kept, in the event log, the archive and backups, until the user asks for deletion (docs/RETENTION.md). The backup and restore scripts require python3 (on the source VPS for snapshotting and the verification host for restore checks). Search text is stored in telemetry to improve results (see What Edge stores). /stats exposes ratings counts for each outcome, excluding flagged feedback. With EDGE_FEEDBACK_DEFAULT off, ratings do not change rankings; with it on, counted ratings can nudge only that caller's similar searches.

The host model keeps the final call: prefer a local skill when fit is effectively tied, use a returned candidate when it is clearly better, or choose none. Edge is never reserved as the fallback after a local miss, and Edge never permanently installs a discovered skill.

Setup in under five minutes

Requirements: Node.js 20 or newer.

Claude Code (Amendment 18): npx -y @getedge/mcp setup claude checks the runtime first (node --version 20 or newer, npx --version; MIN_NODE_MAJOR in src/setup-cli.ts, equal to engines and to the Node version CI runs the packed connector on). Compatible: it adds this connector (claude mcp add edge -s user -e npm_config_prefer_offline=true -- npx -y @getedge/mcp@<version>). Not compatible: it installs the hosted Edge plugin instead, never a local entry that would fail first. It ends with the capability mode, local or hosted. Without Node at all, getedge.cc/SKILL.md sends the user straight to the plugin.

No clone or build is required:

claude mcp add edge -- npx -y @getedge/mcp
codex mcp add edge -- npx -y @getedge/mcp
npx -y @getedge/mcp --version   # downloads the package now, prints the version

Wait for the version number before starting the first chat. A first npx start downloads about 120 packages, and both Claude Code and Codex send the first message without a server that is still starting (issue #175).

For Codex and the ChatGPT desktop app, add omit_tools_from = ["deferred"] to the generated [mcp_servers.edge] block in ~/.codex/config.toml. Codex otherwise defers MCP tools behind a tool search, and a deferred find_skill is rarely called unless the user names Edge. Codex also gives optional MCP servers only 1 second before the first message, less than even a warm npx start (about 1.5 seconds), so set mcp_optional_startup_grace_ms = 10000 at the top of the file. Do not mark Edge required = true: Codex then refuses to start any session while Edge cannot start (offline, registry down). startup_timeout_sec = 30 bounds the handshake itself.

mcp_optional_startup_grace_ms = 10000

[mcp_servers.edge]
command = "npx"
args = ["-y", "@getedge/mcp"]
omit_tools_from = ["deferred"]
startup_timeout_sec = 30

Plugins bundle the hosted connector with a small edge skill that tells the model when to call it: a Claude Code plugin, a Codex/ChatGPT plugin, and a skill ZIP for claude.ai. See plugins/README.md.

For Edge development, use the local-build setup below.

cd /absolute/path/to/edge-mcp
npm install
npm test

Claude Code

From any directory:

claude mcp add edge -- node /absolute/path/to/edge-mcp/dist/src/index.js
claude mcp get edge

Project-scoped .mcp.json alternative:

{
  "mcpServers": {
    "edge": {
      "command": "node",
      "args": ["/absolute/path/to/edge-mcp/dist/src/index.js"]
    }
  }
}

Codex

codex mcp add edge -- node /absolute/path/to/edge-mcp/dist/src/index.js
codex mcp get edge

Equivalent ~/.codex/config.toml entry:

[mcp_servers.edge]
command = "node"
args = ["/absolute/path/to/edge-mcp/dist/src/index.js"]

Ratings and issue reports send data to Edge, so some Codex approval policies block them. They are optional: if Codex denies approval, continue the task and do not report that host decision as an Edge bug. If you choose to allow ratings without a per-call prompt, add this narrow override to your Codex config; add an analogous report_issue override only if you also want agent-submitted bug reports. Older connectors may still call its deprecated feedback alias, which needs its own per-tool override:

[mcp_servers.edge.tools.rate_skill]
approval_mode = "approve"

The override is a user choice. Edge does not change your host approval settings.

Remote connector (Claude.ai and Claude desktop)

Hosts that cannot run the stdio connector use the remote MCP server instead, served by the backend over Streamable HTTP:

  • https://getedge.cc/mcp — the URL to give Claude's "Add custom connector". The Claude app drops server instructions, so also suggest the personal preference line Before specialist work, call Edge's find_skill. (lane levers: specialist calls 8-23% → 100%, routine false calls 0/28).
  • https://getedge.cc/api/mcp answers the same MCP traffic, so a connector configured against it keeps working. POST initialize succeeds there and GET answers without a redirect.

It exposes find_skill, use_skill, rate_skill, report_issue (and the deprecated feedback alias) and scan_skill for catalog skills only, by calling the same backend logic in-process, so behavior cannot drift between the stdio and remote surfaces. benchmark and local scan_skill need the stdio connector, because both work on the user's machine. Free works without any credential. Pro users supply their key either as an Authorization: Bearer <key> header (preferred: URLs end up in more logs than headers do) or as a ?key=<key> query parameter. The key is never logged; telemetry carries only its short keyId label.

/mcp needs no sign-in. Its discovery probes (/.well-known/oauth-protected-resource, .../mcp) get a clean JSON 404, so Claude's connector checker reports "no sign-in needed" rather than an error, and /mcp never returns 401 or a WWW-Authenticate header.

  • https://getedge.cc/mcp/pro is the Pro connector for hosts whose connector dialog has no header field (the Claude app, ChatGPT). Same tools, behind MCP OAuth: the app opens a getedge.cc page where the user types their Edge key once, and gets a revocable token. No account, no email. See docs/PRO-CONNECTOR.md.

Each remote connection gets a per-session id from the MCP transport, and every call is tagged client=remote-mcp, keeping remote traffic separable from stdio connector traffic. Most remote clients have no install identity. The Claude Code plugin sends the install id setup created (~/.config/edge/install-id) as an X-Edge-Install header from its headers helper; a valid id goes in the same install field the stdio connector fills, so forget deletes those records too. The helper never creates an id, so a plugin without setup stays session-only.

The candidate nginx configuration adds exact-match /mcp and /api/mcp locations (all transport methods, SSE buffering off, request logging disabled because of ?key=) and proxies the OAuth probe paths to the backend. It is a review artifact and has not been applied. Deploy the backend to staging first (sudo bash scripts/deploy.sh staging on the host), where the existing /api/mcp-staging/ prefix already exposes the transport as POST https://getedge.cc/api/mcp-staging/mcp, review, then promote and apply the nginx candidate.

Ranking your installed skills (opt-in)

Off unless the user chooses it. Setup asks once, with the recommended answer shown:

Rank your installed skills too? Edge and its ranking provider receive skill names and short descriptions, never skill contents.

This is consent text version 2 (2026-10-07). Version 1 said "With Edge Pro, the ranking provider also receives them." A yes to version 2 records on in local-skills and 2 in local-skills-consent next to it; connector 0.6.19 and later then send local_consent: "2" with the installed skills, and only searches carrying it rank installed skills on Free. An on given under version 1 keeps the Free behaviour it was given for (installed skills listed after the catalog, not sent to the ranker) until the user says yes again: setup asks once more in a terminal, or --local-skills on records it. EDGE_LOCAL_SKILLS=1 alone does not record a consent version.

npx -y @getedge/mcp setup claude asks in a terminal, or takes the answer as --local-skills on or --local-skills off (an agent running setup asks the user and passes it). Only an explicit answer is recorded, as on or off in ~/.config/edge/local-skills (under $EDGE_INSTALL_STATE_DIR when set), next to install-id. Codex, Cursor and other npm-connector hosts record the same file by hand. No recorded answer means off, so upgrading never switches an existing install from off to on. The hosted connector (the Claude Code plugin, the Claude app) cannot see the machine and sends no installed skills. Each connector states this in its MCP instructions as canReadLocalSkills (true on stdio, false hosted), and setup asks only when it is true; setup claude reads the installed plugin's own .mcp.json to tell a hosted plugin from a local one. Every call also reports connector_transport (plugin_http, plugin_stdio, npm_stdio, other; EDGE_CONNECTOR_TRANSPORT sets the stdio value, default npm_stdio); see METRICS.md.

Switches, in order of precedence:

  • EDGE_LOCAL_SKILLS in the connector's environment overrides the recorded choice both ways: 1 (or true, yes, on) turns ranking on, any other non-empty value turns it off.
  • ~/.config/edge/local-skills: on or off. Restart the connector after changing it.
  • EDGE_LOCAL_SKILLS_EXCLUDE: comma-separated glob patterns. A pattern without / matches a skill's frontmatter name or its folder name (acme-*); a pattern with / matches the skill folder's path (~/work/client/**). * and ? stay within one path segment, ** crosses them, matching ignores case. Matching skills are dropped while the folders are read, before keyword selection and serialization, so nothing about them leaves the machine.

A Pro search with no choice recorded also appends a one-line instruction asking the same question once; the agent records the answer in the same local-skills file. The older local-consent markers (~/.config/edge/local-consent, ~/.claude/skills/edge/LOCAL-CONSENT, ~/.codex/skills/edge/LOCAL-CONSENT) still suppress that question but never turn ranking on. Explicit flags, Free or rejected keys, and no local skills suppress it too.

For a new setup after a Yes answer:

npx -y @getedge/mcp setup claude --local-skills on
mkdir -p ~/.config/edge && printf 'on\n' > ~/.config/edge/local-skills   # Codex, Cursor

The connector reads skill name and description frontmatter from:

  • Personal folders: ~/.claude/skills, ~/.codex/skills, ~/.config/opencode/skills, and ~/.agents/skills.
  • Project folders: .claude/skills, .codex/skills, .opencode/skills, and .agents/skills, from the connector's working directory up to and including the nearest Git root. Git worktrees are supported. Outside a Git repository, only the working directory is checked.

Launch the MCP connector with the agent's project working directory. Discovery runs once at startup, so restart the connector after changing projects or installing skills. Skills are deduplicated by frontmatter name: the nearest project folder wins, then parent project folders, then personal folders. Within each level, the folder order above breaks ties.

What leaves the machine with local skills on

Up to 40 skill names and descriptions (first 240 characters, after text cleanup), selected by keyword match, with the whole search body under 14 KB, are sent to Edge's backend at getedge.cc with each search. They are also sent to the ranking service at api.typesafe.ai: for Pro keys, and on Free when the choice was recorded under consent text version 2 and the daily allowance below is not used up. Otherwise Free searches send it the catalog only. Discovery does not attach skill file locations, file bodies, or scripts. Paths or other sensitive information embedded in names or descriptions are transmitted as part of that metadata; description cleanup does not redact them. Edge's backend records counts (localOffered, localPresented), not skill names or descriptions: they are not written to its event log, its other state files or its service log, and are held in memory only for the life of the search binding (checked against the backend code and production state and logs, October 2026). What api.typesafe.ai retains is outside Edge's control and has not been verified.

When they are ranked (Pro, or Free under consent version 2 within the allowance) they are in the same list as the catalog, and at most three of the five slots go to them. Otherwise installed skills that share a word with the task are listed after the catalog candidates, and the result says they were not ranked.

Session scan: skills for what you keep doing (local)

npx @getedge/mcp sessions                 # local report: nothing sent, nothing written
npx @getedge/mcp sessions --search slides # look up Edge skills for chosen categories

sessions reads this machine's Claude Code (~/.claude/projects, or $CLAUDE_CONFIG_DIR/projects) and Codex ($CODEX_HOME or ~/.codex, sessions/ and archived_sessions/) session logs from the last 30 days (--days), and lists the specialist task categories that came up in at least 3 sessions (--min). Linux and macOS only for now.

  • What is read: only the prompts you typed. Assistant output, tool calls and results, injected context, subagent turns and every other file are ignored. Each prompt is matched against a fixed list of about 40 task categories shipped in the package (src/session-taxonomy.ts) and dropped.
  • What is printed: category ids, session and folder counts, last-seen days, and which of the list's own trigger words matched. Never prompt text, file names, paths or project names. If an agent runs the command, its host sends this output to its model provider, like any command output.
  • What can be sent: only with --search, and only for the categories you name (at most 10): each category's fixed query and keywords from the package, plus a client label, a random run id, the connector version, your existing Edge install id if there is one, and EDGE_KEY if set, as with any Edge search. Choosing a category does tell Edge it recurs in your sessions. The command shows exactly what goes where and asks first; off a terminal it needs --yes, which an agent should pass only after you approve.
  • What is written: nothing. No cache, no state, and no install id is created.
  • Trust assumption: the log folders are controlled only by you and your agents. Symlinks and special files in them are skipped, never followed. Slow or stale mounts can make it hang.

npx itself downloads the package from npm. To run with no download, install once (npm i -g @getedge/mcp) and run mcp sessions.

Invocation and setup

Setup 0.5.0 (skills/edge/SKILL.md, served at getedge.cc/SKILL.md) is a plain setup guide written for the person, not a script addressed to the agent (issue #119). It has one required step, claude mcp add edge -s user -- npx -y @getedge/mcp, taken after the user says yes; that same question is when a Pro key goes in (-e EDGE_KEY=...), and without one Edge runs on the Free plan. Setup ends by naming the plan in use. It writes no CLAUDE.md/AGENTS.md line and saves no skill file: routing comes from the server's MCP instructions field (SERVER_INSTRUCTIONS in src/render.ts, shared with the remote server), and no setup ping is sent. The release 3 site handoff, the setup demo and the setup pings below describe setup 0.3.0 and are kept for existing installs.

The local prompt hook adds a specialist-work reminder in Claude Code and supported Codex versions. Its installer preserves existing settings and unrelated hooks. The hook classifies locally and sends no data; a later Edge search sends the task query as usual.

Use the same-session setup demo before the final restart instruction. The connector clears FIRST-TASK-PENDING only after a successful find and adds a short reminder in successful find/use results to compare skills again for the next specialist task.

The invocation evaluation records the labeled prompts, held-out split, and staging results. See the invocation lane report for measured results and limits, including process startup latency.

Smoke test

Start a fresh Claude Code or Codex session and ask:

Find me a skill that would help create a polished investor deck. If a candidate is clearly useful, temporarily use it and tell me which one you selected. Then continue with the task.

The host should visibly call edge.find_skill alongside its local skill comparison, optionally call edge.use_skill, and continue the original task. For a trivial edit, it should not call Edge. For specialist tasks, compare Edge candidates with local skills even when a local skill already exists.

An expected find_skill result looks like:

3 candidate skills for "polished investor deck", in the order of Edge's own index of the skills.sh catalog.

1. shawnpang/startup-founder-skills@pitch-deck
   Source: shawnpang/startup-founder-skills
   Skill: pitch-deck
   URL: https://raw.githubusercontent.com/shawnpang/startup-founder-skills/<audited-commit>/pitch-deck/SKILL.md

To use a candidate temporarily, call use_skill with its exact Source and Skill values.

Reliability behavior

  • find_skill answers only from Edge's own index (edge-search, semantic plus lexical, EDGE_INDEX_URL, default http://127.0.0.1:8140/api/search), built from the skills.sh crawl. It never calls skills.sh per search: no live CLI search, no audit fetch, no page fetch for descriptions. It returns at most five candidates, each with copyable Source/Skill values and its URL. A search with no results succeeds with a neutral "choosing none is valid" message. If the index is unreachable, the backend logs it and returns HTTP 503 with a clear message; it does not fall back to skills.sh.
  • use_skill returns instructions only for a package that passed Edge's own audit. It fetches the complete audited bundle at the pinned Git commit, verifies each file's bytes, and inlines text support files before serving it. Binary or oversized support files are withheld until the transport can deliver them safely.
  • use_skill accepts a short name. When skill is not a Source and Skill any live search offered, it is matched against the search named by request_id, or the session's latest search: exact slug, then unique slug prefix. An ambiguous or unmatched name returns that search's candidates to retry with, instead of loading anything.
  • On HTTP 429, 502, 503 or 504 (and network errors) the stdio connector and the remote connector retry twice with a short backoff, honouring a Retry-After of up to 5 seconds. If the call still fails the agent is told what happened and when to retry (for example "Edge is rate limiting parallel searches; retry in 5 s or search sequentially"), not a raw status.
  • find_skill times out after 30 seconds; use_skill after 60 seconds.
  • Commands use argument arrays, never shell interpolation.
  • Output is stripped of terminal color codes and bounded to 2 MB.
  • Failures return a short MCP error that explicitly tells the agent to continue without Edge; discovery then falls back to the local candidate set.
  • The public npm package is a protocol connector; skill discovery and retrieval execute on Edge's hosted service.
  • use_skill is temporary and never calls skills add on the user's machine.

Staging and promotion

Point the connector at staging with EDGE_BACKEND in its MCP server environment:

{
  "mcpServers": {
    "edge-staging": {
      "command": "npx",
      "args": ["-y", "@getedge/mcp@next"],
      "env": { "EDGE_BACKEND": "https://getedge.cc/api/mcp-staging" }
    }
  }
}

EDGE_BACKEND takes precedence over the older EDGE_MCP_API_URL setting. Without either setting the connector uses https://getedge.cc/api/mcp. Setting the variable only in an unrelated shell will not change an already running MCP server.

The candidate nginx configuration adds /api/mcp-staging/, strips that prefix, and proxies to loopback port 8142 with the same 16 KiB body cap, POST restriction, rate limit, and timeouts as production. It is a review artifact, not an applied configuration. The staging unit has the production hardening, a separate working directory, /etc/edge-mcp/staging.env, and StateDirectory=edge-mcp-staging. Systemd supplies STATE_DIRECTORY, keeping keys, telemetry and audit cache apart. Staging runs as edgemcp-staging, separate from production's edgemcp, and hides production paths with InaccessiblePaths. Setup includes permission and signal isolation checks. Do not put production state paths or port overrides in the staging env file.

After a human provisions the host using REPORT.md, run these commands from a clean, committed checkout on that host. They restart services; they are not commands to run on a developer laptop:

sudo bash scripts/deploy.sh staging
# Review staging before promotion; keep HEAD at the same commit.
sudo bash scripts/deploy.sh promote

Both commands take a shared lock, validate the runtime manifest and installed dependencies, clean dist, run npm ci, build and run npm test before touching the target. Staging copies the build into a saved release, backs up the old target dist, stops the service, rsyncs the release, restarts, waits for readiness, and runs scripts/live-smoke.mjs through the public staging route. The smoke script explicitly forwards backend selection and EDGE_KEY to its connector subprocess. The gate reads /etc/edge-mcp/staging-smoke.key or /etc/edge-mcp/production-smoke.key and requires successful Pro ranking; degraded/baseline ranking fails smoke, and so does a key the backend's find_skill does not return pro: true for (Free is ranked too, so the header alone no longer proves the key). See REPORT.md for provisioning.

Only a successful staging smoke writes /var/lib/edge-mcp-deploy/staging-passed: three lines containing the Git commit, saved release directory name, and SHA-256 digest of the build's file hashes. Starting another staging attempt invalidates the old pass. Promotion refuses a dirty checkout, different commit, missing pass, or changed artifact. It repeats build/tests, then deploys the saved staging build, not the rebuild. Production receives its own smoke test.

Failures during replacement, restart or smoke restore the backed-up dist and restart the old service. On a first install without an old dist, rollback stops the service and removes the failed install. Every attempt that replaces dist prints an exact manual rollback command. Backups and releases remain under /var/lib/edge-mcp-deploy; retain the recorded release and any backups needed for rollback. A force kill or host power loss cannot run a shell trap: use the printed rollback command after recovery. There is a brief service interruption while dist is replaced. Runtime dependencies, secrets, service units and nginx are provisioned separately and are not rolled back by this script.

For isolated fixtures, the script supports EDGE_DEPLOY_STATE, EDGE_STAGING_DIR, EDGE_PRODUCTION_DIR, and EDGE_DEPLOY_READY_PORT. Leave these unset on the host. The smoke URLs and service names remain fixed. Tests use temporary directories, a random loopback port and fake npm, node and systemctl commands; they never contact either public backend or restart a real service.

Test connector releases

Use a unique prerelease version in a release checkout and publish it under next:

npm version prerelease --preid=next
npm publish --tag next

The package's prepublishOnly hook runs npm test. The next tag leaves the stable latest tag unchanged. Test with npx -y @getedge/mcp@next and the staging EDGE_BACKEND setting above. Commit the version and lockfile together. Publishing requires an authorized npm account and is a separate human release action; the backend deployment script never publishes packages.

After a release that changes plugins/ or the marketplace files, sync the public plugin repository with scripts/sync-public-plugins.sh (see plugins/README.md).

Day-3 activation nudge

server/activation-nudge.ts, run daily at 9:10 Pacific by deploy/edge-activation-nudge.timer. Signup-form key holders (channel form, by signup-form) whose first key was issued at least 3 days ago and on/after 2026-10-07T04:00Z, with zero searches and zero loads on the dashboard People tab (peopleActivity in server/dashboard.ts, the same identity join, dedup and internal exclusion), get a fresh Pro key (source nudge-d3) and one email with the one-paste setup line and a release date 7 days out. On that date, if the person still has no search or load, both keys are revoked (reason activation-unused); anyone who became active keeps both. Holders of followup-1006 keys (the 2026-10-06 campaign) are never nudged. At most 50 sends per run. Every send and release is appended to /var/lib/edge-mcp/activation-nudges.jsonl; key values are never logged. The run fails closed (exit 1, nothing sent) when the dashboard data is incomplete or EDGE_RESEND_KEY (from /etc/getedge/interest.env) is missing.

Preview on the host: sudo systemd-run --pipe --wait -p User=edgemcp -p EnvironmentFile=/etc/getedge/interest.env -p Environment=STATE_DIRECTORY=/var/lib/edge-mcp /usr/bin/node /opt/edge-mcp-service/dist/server/activation-nudge.js --dry-run (prints emails and key labels only). Install: install -m 0644 deploy/edge-activation-nudge.service deploy/edge-activation-nudge.timer /etc/systemd/system/ && systemctl daemon-reload && systemctl enable --now edge-activation-nudge.timer.

Search index and cache refresh

Everything find_skill reads is local to the host and refreshed by one daily job, never per request:

  • the search index served by edge-search (/opt/edge-search/served), which carries each skill's description;
  • the audit snapshot the security gate reads (audit-cache.json in the service state directory, layered with the nightly library snapshot named by EDGE_AUDIT_LIBRARY).

scripts/refresh-edge.sh has three stages. index (root) promotes a freshly built index from EDGE_INDEX_CANDIDATE (default /var/lib/edge-index/candidate). The pipeline that builds it (crawl, embed, build_index.py; it needs the encoder and is not part of this repository) must write COMPLETE, holding the row count, into the candidate last. The stage refuses a missing file, a COMPLETE count that disagrees with rows.json, fewer than EDGE_INDEX_MIN_ROWS rows (default 100,000), an index more than 5% smaller than the current one (EDGE_INDEX_MIN_RATIO), an unreadable served count, or a copy that differs from what it checked. It swaps the index in, keeps the last three as served-prev-*, restarts edge-search, and rolls back if /health does not report the new row count within two minutes. No candidate, no COMPLETE, or a candidate not newer than what is served is a no-op. copies rebuilds the identical-copies map (needs GITHUB_TOKEN; bounded by EDGE_COPIES_MAX_RUNTIME and resumable). Its memory is bounded: rows are streamed, each fetched SKILL.md is cached only as its normalised sha256, a 64-value MinHash signature and its length (never the text), and near-copy candidates (signature estimate at least COPIES_FLOOR, default 0.85) are re-fetched two at a time so the exact 5-word-shingle Jaccard decides at 0.95. At 218k rows it peaks around 470 MB RSS; the heap is capped at EDGE_COPIES_HEAP_MB (default 512). Caches from before this change, which held the text, are converted and compacted on first load. Each group's original needs the first commit of every tied member: these are cached per file (born, carried over when a file is re-fetched at the same path) and looked up in GraphQL batches of 25 (newest commit and count, then the oldest via the history cursor), with REST as the fallback, so the catalog's ~20,000 lookups cost about 1,600 GraphQL points instead of four hours of the REST budget. caches (edgemcp) runs scripts/refresh-audits.mjs over the served rows.json: one lookup per second on two workers (EDGE_AUDIT_RPS), backing off on 429 and honouring Retry-After, stopping after 25 consecutive failures or at EDGE_AUDIT_MAX_RUNTIME_MS (the next run resumes). It looks up missing rows first, then the most installed, and re-checks a verdict after 2 days (1,000+ installs) or 10 days otherwise. It stores only the audit fields the admission policy reads, checkpoints every 15 minutes, and atomically replaces the audit cache; the backend notices without a restart.

sudo -u edgemcp STATE_DIRECTORY=/var/lib/edge-mcp /opt/edge-mcp-service/scripts/refresh-edge.sh caches   # by hand

scripts/deploy.sh ships the three refresh scripts into <target>/scripts with the tested dist/. deploy/edge-refresh.service and deploy/edge-refresh.timer run the stages daily at 03:05 UTC. Install steps, staging first: REPORT-connector-fixes.md, "Deploying the refresh job".

A skill without three current Edge scanner records for the same package hash is withheld. Search never fetches audits on the request path. use_skill reads the same snapshot and fetches only the pinned, hash-verified instruction file.

Library integrity

Public catalog candidates must meet the versioned screening policy, including independent scanner corroboration, audit freshness and available revision evidence. Missing audits are withheld. Existing summary-only audit caches do not satisfy this policy; prepare a complete evidence snapshot before release. See library operations for search fallback, audit sweeps, public count publication, count checks and the uninstalled nightly service.

What Edge receives

find_skill sends a short description of the deliverable and domain, plus optional generic keywords. Describe a task as “seed pitch deck outline”, without names, file contents or work details. The stdio connector removes credentials, emails, URLs, file paths, phone numbers and long number strings before sending; the server repeats that cleanup before search and telemetry. Search text is capped at 300 characters and keywords at 120 (stored searches from before 2 October 2026 run up to 500 characters). For ranking, the task text and the candidate skill descriptions go to TypeSafe (api.typesafe.ai), without the install id, session or key.

Daily presence signals (local connector)

Besides searches, loads and ratings, the local connector (npm or plugin stdio) sends two small daily signals, so Edge can tell a quiet day with Edge running apart from a day without Edge (src/presence.ts, server/presence.ts):

  • connector_ready_day: at most once per install per UTC day, after the connector finished the MCP handshake with its tools registered. A connector still running after midnight sends the new day once.
  • nudge_fired_day: at most once per install per UTC day, when the prompt hook showed its nudge at least once that day. The hook itself sends nothing: it leaves an empty file named nudge-day-YYYY-MM-DD in ~/.config/edge (only where the local connector created an install id), and the connector sends it and keeps it as .sent. Markers older than 7 days are dropped unsent.

Each carries only the install id, the UTC day, the host's name and version, the connector version and its transport. Never the key, Pro status, a session, prompt or task text, a skill name, a count, a duration or the classifier's score. They are kept like other events (docs/RETENTION.md) and forget deletes them. Set EDGE_TELEMETRY=0 (in the shell, or in the edge entry's env) to turn both off; searches still send their task text, because that is the search. The dashboard shows them as presence, never as product use (METRICS.md, "Retention after the first load").

What Edge stores

Minimized search text is stored to improve results; local skill contents and file paths are never sent. It has no scheduled expiry. npx -y @getedge/mcp forget deletes an install's (or, with EDGE_KEY, a key's) events, ratings and reports, archive included, and prints the counts; email fede@getedge.cc for anything else. See docs/RETENTION.md.

Every find_skill event, Free and Pro, records queryText: the search text with control characters replaced by spaces, capped at 300 characters, next to the existing queryHash and queryLength. It also records the presented candidates in order (presentedSkills, with installed skills as null so their names are not stored) and every load (use events with source, skill and the search's requestId). The anonymous site comparison (/compare) stays hash-only. Setup and hello events never carry query text.

Reports from the report_issue tool (and its feedback alias) and POST /api/feedback go to /var/lib/edge-mcp/feedback.jsonl: an allowlist of kind, message, optional request_id and contact, key id (never the key), client, version, session, install and via (the tool or route name it came in under). Control characters in text become spaces and anything shaped like a credential (Edge keys, API tokens, Bearer headers, ?key= parameters) is redacted before it is written. The event log gets a feedback event with the kind only, never the message. IP addresses are never stored: they are used transiently for rate limiting and for the internal-traffic check below, which compares SHA-256 hashes only.

Free ranking

Every Free catalog search uses the same task-fit ranking service as Pro, without a daily ranking allowance. A missing or rejected key does not lower catalog search quality. If the upstream ranking service is unavailable, both Free and Pro searches degrade to the catalog ordering; ranker and degraded in the search event show what actually happened.

A valid Pro key remains the identity for personal activity. Installed-skill metadata joins the ranking pool on Pro under the local-skills opt-in, and on Free (since 2026-10-07) only when the request carries local_consent: "2": the connector sends it when the user's yes was given to consent text version 2, which names the ranking provider for Free too. Free searches without it (connectors before 0.6.19, or an opt-in under version 1, which said only Pro sends installed skills to api.typesafe.ai) keep the earlier behaviour: only catalog candidates go to the ranking service and local matches are shown separately. Free find events carry free_ranked; those that carried installed skills under version 2 also carry freeLocalRanked.

Free installed-skill ranking allowance

Free searches that rank installed skills spend a daily allowance: per install (EDGE_FREE_LOCAL_RANK_PER_INSTALL, default 100; a request without an install id is counted by a day-salted hash of its address, else its session) and across all Free traffic (EDGE_FREE_LOCAL_RANK_GLOBAL, default 5000). Past either, the search is served the earlier Free way (catalog ranked, installed matches listed after it, unranked), never as an error, and the cap is not surfaced in agent-facing text. The find event records freeLocalRanked: false and cap_hit (install, global, disabled for a 0 setting, config for an invalid one). Edge's internal traffic is exempt. Counts persist in free-local-rank-budget.json in the state directory and reset at midnight UTC. (free-rank-budget.json, if present, is a leftover of the retired Free catalog allowance and is not read.)

Pro ranking fair use

Pro task-fit ranking has a per-key daily allowance, configured on the backend with EDGE_PRO_RANK_PER_INSTALL (default 300). Searches are counted by the authenticated key label, so each Pro key has its own allowance; there is no global Pro cap. The day resets at midnight UTC. Only searches with at least two ranking candidates use an allowance, and Edge's internal traffic is exempt.

After the allowance, find_skill still returns catalog results in baseline order, with installed skills shown separately. The find event records cap_hit: "install"; the cap is not surfaced in agent-facing text. Setting the value to 0 disables Pro task-fit ranking and records cap_hit: "disabled". An invalid nonnegative-integer setting also uses baseline order and records cap_hit: "config". Free ranking is unaffected by this setting.

Counts persist in pro-rank-budget.json under STATE_DIRECTORY, then EDGE_MCP_STATE_DIR, then the working directory, and survive a service restart.

Event archive and offsite backup

The live log rotates daily and the state directory keeps 31 days, which is all /stats and the dashboard read. Before a rotated day can be pruned, the service writes it gzip-compressed to archive/ inside the state directory (/var/lib/edge-mcp/archive/, same owner as the service; override with EDGE_ARCHIVE_DIR). The archive is never pruned. A day whose archive cannot be written stays in the state directory rather than being deleted. Every field, raw search text and rating notes included, and any malformed legacy line is kept verbatim; data is deleted only on request (docs/RETENTION.md). Days already rotated when this ships are archived on the next rotation pass. scripts/kpis.mjs reads the archive automatically for days no longer in the state directory.

scripts/backup-events.sh copies the archive to the Hetzner Storage Box. It runs on AX41 (the host with the storage-box key), reads the archive over ssh from the VPS, seals each new day with the same age recipient as the state backup (EDGE_MCP_BACKUP_RECIPIENT in /etc/edge-mcp-backup/backup.env), stages it in /var/backups/edge-mcp-archive/ and mirrors with rsync --ignore-existing and no --delete. Each day is sealed once; nothing is ever deleted locally or offsite. The archive contains search text, so it is never copied in the clear.

The nightly timer is documented, not installed. To install on AX41:

install -m 0755 scripts/backup-events.sh /usr/local/sbin/backup-edge-events
install -m 0644 deploy/ops/edge-events-archive-backup.service deploy/ops/edge-events-archive-backup.timer /etc/systemd/system/
systemctl daemon-reload
systemctl start edge-events-archive-backup.service   # first run, check the output line
systemctl enable --now edge-events-archive-backup.timer

The timer fires at 04:37 UTC (up to 15 minutes later), after the service's daily rotation and clear of the 03:47 state backup. Restore a day with age --decrypt -i <identity> events-YYYY-MM-DD.jsonl.gz.age | gunzip; the identity is getedge/edge-mcp-backup/age-identity in AgentWallet.

Internal traffic

Every backend event carries internal: true|false. A request is internal when:

  • its key's SHA-256 is listed in EDGE_INTERNAL_KEY_HASHES (comma or space separated; full 64-hex hashes or 8-hex keyId prefixes),
  • its client name (before any /version) is one of edge-live-smoke, lsmoke, dogfood, edge-cli, codex-edge-task or knowledge-sync, or
  • its source IP is listed in EDGE_INTERNAL_IPS (raw IPs or their SHA-256 hex). IPs are hashed for matching and neither the IP nor its hash is stored.

/stats, the dashboard and scripts/kpis.mjs exclude internal events by default. Include them with /stats?internal=1, /dashboard?internal=1 or --include-internal. Events written before the flag existed are classified from their recorded client name and Pro keyId.

Internal usage dashboard

The backend serves GET /dashboard, with an exact route in the candidate nginx configuration for https://getedge.cc/api/mcp/dashboard. Set EDGE_DASHBOARD_TOKEN to a long random secret in the backend environment. Visit that URL with ?token=<your-token> once; the page removes the query from browser history and sets a Secure, HttpOnly, SameSite=Strict session cookie. Subsequent refreshes use the cookie. Missing or incorrect credentials return 404, and changing or removing the environment token invalidates existing cookies. Responses are not cached. HTTPS is required outside localhost. Keep the initial credential URL private; configure access logs to omit query strings if they currently record them. The candidate dashboard route disables access logging. Apply that candidate through the normal deployment process; the existing general MCP route is POST-only and cannot serve this page on its own.

The page refreshes every 60 seconds and reads the same bounded, rotated event history as /stats. Today starts at midnight UTC; seven days means rolling 168 hours. Install activity uses stable random install identifiers, as in the KPI report. Next-day return uses complete UTC cohorts only. Hover or focus a metric for its definition; hover table values for definitions. No query text, hashes, session identifiers, key identifiers, rating notes or error text is rendered.

Library count reads eligible from library.json in STATE_DIRECTORY, then EDGE_MCP_STATE_DIR, then the working directory. EDGE_LIBRARY_PATH can point to the snapshot explicitly. Missing or invalid snapshots show Not available. No new persistent storage is introduced.

For local visual verification, run npm run build then python3 scripts/verify-dashboard.py with Python Playwright installed and Chrome at /usr/bin/google-chrome and nginx at /usr/sbin/nginx. It serves snapshots with python3 -m http.server on 8128, a test backend on 8129, and the isolated candidate dashboard proxy on 8130. It writes screenshots and checks to shots/ and stops all three servers. Populated screenshots use synthetic fixtures, not usage claims.

Starred skills

Ask the agent to "star this skill" (or "save this one") and it calls star_skill with the Source and Skill a search printed; starred: false unstars. The agent answers with the line ⭐ Edge · starred <skill>. From then on, find_skill puts a starred skill first, marked ⭐ starred by you, whenever the ranker judged it a fit for the task; a starred skill that does not fit is never promoted or marked, and the audit gate still applies. Stars are part of retrieval too: each search scores the user's starred skills (up to 200) against the task with the index's own embeddings (scope=ids, deploy/edge-search/ids.patch), in parallel with the catalog search and cached per user for 10 minutes; up to three that reach the recall line Edge's own skills use (SEMANTIC_MIN) join the pool even when the search did not retrieve them, and the ranker judges them like any other candidate. "Show my starred skills" calls list_starred: name, source, a one-line description and the skill's getedge.cc page when the site has one. Stars are stored server-side in stars.json in the state directory, keyed like rating signals: by the Pro key (so they follow the key to every install), else by the install id. A hosted connection with neither is told starring needs a key. forget deletes them; star and unstar events go to the event log as event: "star".

Your Edge page

https://getedge.cc/you shows the last 7 days (UTC): one card per task Edge worked on (a search with at least one successful load), the skill, the agent's own line on what it changed when it reported one, and the outcome with its 0-10 score. Totals and the most-used skills follow. Free installs and Pro keys both get the page; a free install is identified by its install id, a Pro key by the key (an install once seen with a key opens the key's page).

There is no password and no permanent link. Ask the agent "open my Edge page": it calls my_activity, which returns https://getedge.cc/you/open/<token>, a single-use exchange link valid for 10 minutes. In a terminal, npx @getedge/mcp you asks for the same link and opens it (--print only prints it). Opening the link signs that browser in with a Secure, HttpOnly, SameSite=Lax cookie for 30 days and redirects to a clean /you. Used, expired and unknown links all get the same "This link no longer works" page; a browser that is already signed in goes straight to /you. Tokens and sessions are stored only as SHA-256 hashes (you-access.json in the state directory) and never logged. The page offers Sign out other sessions (also cancels unused links) and Clear activity history (asks to confirm, then deletes cards and totals). It sends noindex, Referrer-Policy: no-referrer and a CSP that only allows getedge.cc's own assets. Task labels are short agent-written labels (task_label on use_skill, or task.label on rate_skill), never the query; names, emails, URLs, handles, phone numbers and keys are redacted before they are stored.

The first successful find_skill result each Monday-based UTC ISO week with at least one skill load can append ⚡ Edge · your week is ready · ask me "open my Edge page". It never carries a link. Pro keys share one slot across connector sessions; free sessions have their own slot.

Definitions:

  • Skills used: successful use_skill loads, including repeated loads. It is not a distinct-skill count or proof the agent followed the instructions.
  • Better picks: one per bound search where a successfully loaded catalog skill differs from skills.sh's actual first result for that search (the approved most-installed comparator). The upstream list is not re-sorted. The baseline is observed before Edge injection, audit filtering and ranking. A missing baseline, failed load, or crossed/unbound selection cannot earn a better pick. This measures a selection difference, not task quality.
  • Helped rate: helped / counted ratings, with all counted outcomes in the denominator. Duplicate, ambiguous and unbound ratings are excluded. No ratings is displayed as no data, not a zero percent success rate.

The comparator is the index's own first result for that search; there is no live skills.sh lookup for it.

Counters start when this feature begins recording; “All time” is explicitly labelled with that start date. There is no historical backfill from incomplete, retention-limited events. Counters live in $STATE_DIRECTORY/value-kpis/ (or $EDGE_MCP_STATE_DIR/value-kpis/), with private permissions and atomic replacement. They contain counts, public skill IDs and, for the last 15 days, the redacted task labels, outcomes, scores, agent-written change lines and time-estimate categories the Your Edge page shows, plus lifetime task, estimated-minute and skill totals and the current streak's dates; never queries. Files are keyed by domain-separated hashes. Include this directory in backups. The store assumes one backend writer; do not share it between concurrent backend replicas. Existing search bindings expire or disappear on restart, so old requests cannot establish new comparisons or counted ratings after that point. Legacy unbound loads without a key cannot be attributed to Pro; the current connector sends its key on every call.

Generate a plain-text email body locally, without sending anything:

npm run build
EDGE_MCP_STATE_DIR=/path/to/state node scripts/weekly-value-email.mjs --key-label X --dry-run

The label must identify exactly one issued key. Omitting --dry-run fails closed. The script has no email transport. It renders this week, all time, searches and top skills from the same counters as the page, without query text or credentials.

The candidate nginx configuration includes an exact GET/POST route for the page, preserves its public cookie path and disables access/error logs on that route to protect query credentials. It has not been deployed. Use the canonical HTTPS URL; external proxies must also avoid logging key-bearing URLs.

Optional discovery and value-counter storage failures do not turn successful searches, skill loads or accepted ratings into errors. The backend logs a fixed warning without task text or identifiers; affected counters may be incomplete until storage is repaired.

Anonymous install identity and activation measurement

The connector creates a cryptographically random 128-bit identifier once in ~/.config/edge/install-id, with file mode 0600. Claude and Codex running as the same OS user share this file. It is never derived from hardware, a username, a path, or a key. Every connector request carries it as install, alongside a new session for each connector process. Concurrent starts publish a complete identity atomically. EDGE_INSTALL_STATE_DIR can override the state directory for isolated environments; use the same setting in setup hooks and both hosts. An unreadable or invalid identity disables install tracking without blocking tools.

The first initialized connector claims ~/.config/edge/hello-sent and attempts one best-effort hello containing install, session, normalized client, connector version, validated Pro status, and local-skills opt-in status. The marker is claimed before sending, so failures are not retried and delivery is not guaranteed. No query, raw key, IP, local skill names, or file paths are added to activation events. A setup ping may carry an optional ref (8 hex characters, the sharer's keyId from a getedge.cc/?ref=<keyId> link); see docs/identity-keyid.md.

To reset this anonymous installation, stop both hosts, delete ~/.config/edge/install-id and ~/.config/edge/hello-sent, then restart. This creates a new identity and hello; it does not erase already recorded history. Deleting only install-id does not reset the once-only hello marker.

Setup hook handoff contains the start and finish curl lines for the site lane. The backend accepts POST /setup_ping and POST /hello under the existing public /api/mcp/ proxy. Both routes share a bounded, in-memory per-IP rate limiter and strictly allowlist stored fields.

node scripts/kpis.mjs --events /path/events.jsonl --ledger /path/pro-keys-issued.jsonl reports daily UTC sequential funnels for Pro issuance cohorts and all-install setup cohorts. Repeat --ledger for seeded keys and --events for extra retained history. --json includes the same funnels and invocation metrics. The authenticated usage dashboard shows both. Pro linkage uses a SHA-256 key prefix; ambiguous issuance prefixes are excluded from conversion attribution.

Daily and weekly active installs and next-day return now use install, never process sessions. Legacy events without it remain in traffic totals but cannot contribute to install activity or retention. Sessions remain useful for individual search-to-load and skill-quality analysis. Sequential funnel steps must be observed in order; a missing setup start leaves an install outside that funnel and is reported in coverage. Next-day conversion requires activity on the UTC day after cohort entry, after a successful load. Incomplete next days have no return rate.

Invocation rows are calculated separately per install and per validated Pro key. Searches per active day divide all search attempts, including failures, by UTC days with a search or load in the displayed window. Days installed with zero searches count complete UTC days since the first observed hello, including silent days. Today is excluded. Without a hello, installed days are unavailable. These counts do not prove that an agent ran or that Edge remained installed; there is no uninstall beacon. Report-local row labels avoid exporting identifiers. The dashboard reads bounded retained history (30 days, subject to the event read budget), so a hello outside that history cannot establish installed days. Supply all available rotations to the CLI for the most complete historical attribution.

Load-policy adoption experiment

EDGE_LOAD_POLICY=current|directive|inline|load-first|inline-lite selects how a find result hands off to use_skill. It defaults to directive (at most three candidates, ending with the exact use_skill call for the top match), which won the round-2 comparison in REPORT.md; set EDGE_LOAD_POLICY=current to roll back to the previous result layout. Set it on the stdio connector process, or on the backend for remote MCP. See the policy and evidence gates and adoption-grid run spec. Inline requires scanner evidence bound to the delivered content; legacy audit metadata falls back to an explicit load call.

FAQs

Package last updated on 15 Oct 2026

Related posts