Atlas / MCP servers / oldmoldycake / Snagr

SnagrSAFE

mcp/oldmoldycake/snagr

Selfhosted price tracker that searches marketplaces. A agent uses a headless browser to find, as well as recheck listings with a fastAPI backend and a live updating react dashboard

Verdict
SAFE
Grade
B
Trust score
86 /100
Exposed tools
33 21r · 7w · 5d
Transport
streamable-http
License
AGPL-3.0
Stars
19
01

Overview

From the repository's own README, as read at the audited commit. Badges and raw HTML are left out.

[](https://github.com/oldmoldycake/Snagr/actions/workflows/ci.yml) [](https://github.com/oldmoldycake/Snagr/actions/workflows/codeql.yml) [](https://github.com/oldmoldycake/Snagr/releases) [](LICENSE)

A self-hosted price tracker for secondhand-marketplace hunting. You describe what you're watching for and at what price; an always-on agent drives a real headless browser to find new listings and re-check known ones, and the web UI shows every hunt's state at a glance — current best price, drift against your target, price history, and what the hunter is doing right now.

Status: early (0.x). The whole stack runs end to end, every push to main publishes container images, and release-please cuts a tagged release from the CHANGELOG. Expect rough edges, and read the release notes before upgrading between minor versions.

All three screenshots are the frontend's built-in mock data (see Development).

Contents

  • Features
  • How it works
  • Requirements
  • [Quick start](
Read from source at commit 3afec73517f6OBSERVED · 2026-10-11
02

Connect

Built from this server's own package name, version and transport as found in its source — not copied from anyone's documentation, so it cannot drift against a page we do not control. Replace the environment placeholders with a token scoped to the least it needs.

claude-code
claude mcp add frontend --env AI_API_KEY=${AI_API_KEY} --env BRAVE_API_KEY=${BRAVE_API_KEY} --env HF_TOKEN=${HF_TOKEN} --env JWT_SECRET=${JWT_SECRET} -- npx -y [email protected]
claude-desktop
{
  "mcpServers": {
    "frontend": {
      "command": "npx",
      "args": [
        "-y",
        "[email protected]"
      ],
      "env": {
        "AI_API_KEY": "${AI_API_KEY}",
        "BRAVE_API_KEY": "${BRAVE_API_KEY}",
        "HF_TOKEN": "${HF_TOKEN}",
        "JWT_SECRET": "${JWT_SECRET}"
      }
    }
  }
}
03

Exposed tools (33)

21 read · 7 write · 5 destructive. Blast radius: 5 tools can delete or overwrite — an agent that can be talked into calling a tool can be talked into calling this one.

ToolRiskDescription
cancel_jobreadCancel one of your pending or running hunts (admins: any job). A
confirm_review_entryreadGive the human verdict on a queued photo: label it
create_categorywriteAdd a category, e.g.
create_itemwriteStart watching an item. If the shared catalog already has an item of
create_sitewriteAdd a marketplace the agent can search, e.g. name
delete_categorydestructiveDelete a category AND everything under it: its items, every user
delete_itemdestructiveStop watching an item: removes this user
delete_sitedestructiveDelete a site (id or name). It is unlinked from every category,
discard_review_entrydestructiveDrop a queued photo without a verdict — it is neither real nor fake
enqueue_jobsreadAsk the hunter for something now, and get back the jobs it queued.
get_category_price_changereadPer-item best-price percent change over `range` for every item this
get_dashboard_statsreadThe dashboard tiles for this user over `range`: tracked items, active
get_instancereadWhat this Snagr instance has switched on: its version, whether photo
get_itemreadOne watched item in full: everything list_items shows plus every
get_jobreadOne job with the last 50 lines of its log — what the hunt searched,
get_price_dropsreadThe biggest recent price drops across everything this user watches —
get_price_historyreadOne price series per listing of a watched item over `range`, plus
get_price_summaryreadThe average and best price of a watched item over time, bucketed
jobs_summaryreadWhat the hunter is doing right now, in one object: how many hunts
list_categoriesreadEvery category (video games, trading cards, ...) with its slug, the
list_itemsreadThe items this user watches, each with its target price, current best
list_jobsreadThe hunter
list_listingsreadListings across every item this user watches, newest first — the way
list_price_checksreadThe raw observations behind an item
list_referencesreadAn item
list_review_queuereadListing photos the agent captured on this user
list_sitesreadEvery marketplace the agent can search, with its base URL, the
revoke_referencedestructiveRetire a reference photo from an item
update_categorywriteRename a category and/or replace the set of sites it is searched on —
update_itemwriteChange a watched item
update_listingwriteStop tracking a listing (active=false: it is no longer re-checked
update_sitewriteRename a site or change its base URL (`site` is an id or name).
whoamireadThe account this token acts as, and the scopes it carries (read,
04

Trust audit

SAFEgrade B · trust 86/100 Nothing in the source contradicts what it says it does. Grade A is reserved for packages that have also passed the behavioural sandbox.

LayerWhat it checksResult
L0Provenance & inventoryPASS
L1Static analysis of the codePASS
L2Instruction surface (what it tells the agent)PASS
L3Class-specific surfaceWARN
L4Behavioural (sandbox)SKIPPED

What the source does

Filesystem
declared (4 observation(s))
Network
declared (5 observation(s))
Shell
none-observed
Dependencies
not all pinned
Secrets in source
none-found

Findings (24)

MEDIUMFilesystem / path · mcp.destructive_tools · CWE-22, CWE-59
delete_category, delete_item, delete_site, discard_review_entry, revoke_reference
Why it matters. 5 tool(s) can delete or overwrite
Fix. prefer a read-only mode or scoped tokens; the page states the blast radius
LOWInventory / provenance · inv.hidden_file · CWE-1104
.release-please-manifest.json
.release-please-manifest.json
Why it matters. hidden member outside the usual dotfiles
Fix. review its purpose
LOWInventory / provenance · inv.hidden_file · CWE-1104
frontend/.env.development
.env.development
Why it matters. hidden member outside the usual dotfiles
Fix. review its purpose
LOWInventory / provenance · inv.hidden_file · CWE-1104
frontend/.oxlintrc.json
.oxlintrc.json
Why it matters. hidden member outside the usual dotfiles
Fix. review its purpose
LOWFilesystem / path · fs.traversal · CWE-22, CWE-59
frontend/src/api/endpoints.test.ts:21
const token = '../../admin/invites#'
LOWFilesystem / path · fs.traversal · CWE-22, CWE-59
frontend/src/api/endpoints.test.ts:40
const token = '../../admin/users#'
LOWNetwork egress · net.metadata_ip · CWE-200, CWE-319
agent/tests/test_tool_validation.py:65
"http://169.254.169.254/latest/meta-data/",
Why it matters. cloud metadata endpoint: the classic SSRF credential grab
LOWNetwork egress · net.metadata_ip · CWE-200, CWE-319
agent/tests/test_validation.py:287
"169.254.169.254",
Why it matters. cloud metadata endpoint: the classic SSRF credential grab
LOWNetwork egress · net.metadata_ip · CWE-200, CWE-319
backend/tests/test_channels_api.py:252
"http://169.254.169.254/latest/meta-data/",
Why it matters. cloud metadata endpoint: the classic SSRF credential grab
LOWNetwork egress · net.metadata_ip · CWE-200, CWE-319
backend/tests/test_channels_api.py:427
channel_id = await _saved_before_the_guard(db_session, user_id, "http://169.254.169.254/")
Why it matters. cloud metadata endpoint: the classic SSRF credential grab
LOWNetwork egress · net.metadata_ip · CWE-200, CWE-319
backend/tests/test_channels_api.py:484
({"kind": "webhook", "url": "http://169.254.169.254/latest/meta-data/"}, "url"),
Why it matters. cloud metadata endpoint: the classic SSRF credential grab
LOWNetwork egress · net.raw_ip · CWE-200, CWE-319
README.md:121
PLAYWRIGHT_MCP_URL=http://192.168.1.10:8931/mcp
LOWNetwork egress · net.raw_ip · CWE-200, CWE-319
agent/tests/test_check_images_tool.py:64
monkeypatch.setattr(tools, "VISION_SIDECAR_URL", "http://127.0.0.1:9")
LOWNetwork egress · net.raw_ip · CWE-200, CWE-319
agent/tests/test_static.py:146
["http://vision:8100/rescore", "http://127.0.0.1:8000/", "https://evil.test/p/1"],
LOWNetwork egress · net.raw_ip · CWE-200, CWE-319
agent/tests/test_tool_validation.py:64
"http://127.0.0.1:5432/",
LOWNetwork egress · net.raw_ip · CWE-200, CWE-319
agent/tests/test_tool_validation.py:65
"http://169.254.169.254/latest/meta-data/",
LOWObfuscation / stealth · obf.zero_width · CWE-506, CWE-94
agent/tests/test_validation.py:385
clipped = clip_text("a\x00b\x1b[31mcd", MAX_NOTES)
LOWObfuscation / stealth · obf.zero_width · CWE-506, CWE-94
agent/tests/test_validation.py:387
assert not any(character in clipped for character in "\x00\x1b")
LOWSupply chain · supply.unpinned · CWE-829, CWE-1357
agent/requirements.txt
ruff
Why it matters. 1 requirement(s) not pinned with ==
Fix. pin exact versions
LOWSupply chain · supply.unpinned · CWE-829, CWE-1357
backend/requirements.txt
python-multipart, asyncpg, alembic, pgvector, pydantic-settings, python-dotenv, email-validator, argon2-cffi
Why it matters. 16 requirement(s) not pinned with ==
Fix. pin exact versions
LOWSupply chain · supply.unpinned · CWE-829, CWE-1357
frontend/package.json
@fontsource-variable/big-shoulders-display, @fontsource-variable/ibm-plex-sans, @fontsource/ibm-plex-mono, @radix-ui/react-collapsible, @radix-ui/react-dialog, @radix-ui/react-dropdown-menu, @radix-ui
Why it matters. 32 dependency range(s) float
Fix. pin exact versions or ship a lockfile
LOWSupply chain · supply.unpinned · CWE-829, CWE-1357
vision/requirements.txt
ruff
Why it matters. 1 requirement(s) not pinned with ==
Fix. pin exact versions
LOWPrompt injection · prompt.transfer_instruction · CWE-94, CWE-1427
CHANGELOG.md:160
* **frontend:** send a visitor whose session expired to sign in ([c8ec889](https://github.com/oldmoldycake/Snagr/commit/c8ec889b839fc31a7b8c97749290ffd7fe5fd2bf))
Why it matters. an instruction to move sensitive data to an outside destination
Fix. remove; a skill never needs the user's secrets off the machine
LOWPrompt injection · prompt.transfer_instruction · CWE-94, CWE-1427
CHANGELOG.md:161
* **frontend:** send a visitor whose session expired to sign in ([714b0a0](https://github.com/oldmoldycake/Snagr/commit/714b0a065def5f75a3a9dbf3d103b13b46e9c7a7))
Why it matters. an instruction to move sensitive data to an outside destination
Fix. remove; a skill never needs the user's secrets off the machine

Gates applied: no_behavioural_pass.

Audited 2026-10-11 · audit v0.4.1 · source sha 3afec73517f6full audit observations/trust-audit/mcp-server/oldmoldycake__snagr.json · Report an issue / request a re-scan
05

Audit history

Every audit this server has had. A grade with a past is a grade somebody is still checking.

DateSourceVerdictGradeScoreChange
2026-10-113afec73517f6SAFEB86first audit
06

Questions

What is the Snagr MCP server?

Selfhosted price tracker that searches marketplaces. A agent uses a headless browser to find, as well as recheck listings with a fastAPI backend and a live updating react dashboard

What tools does Snagr expose?

33 in total: 21 read-only, 7 that write, and 5 that can delete or overwrite (delete_category, delete_item, delete_site, discard_review_entry, revoke_reference). Every one is listed on this page with its risk.

Is Snagr safe to connect to an agent?

The audit found nothing in the source that contradicts what it says it does, and graded it B (86/100). Grade A is held back for packages that have also passed a sandboxed behavioural run, which is why a clean server reads B. Separately from the audit: 5 of its tools can destroy data, so scope the token you give it to what you actually need.

What credentials does Snagr need?

It reads AI_API_KEY, BRAVE_API_KEY, HF_TOKEN, JWT_SECRET, LANGFUSE_PUBLIC_KEY, LANGFUSE_SECRET_KEY, S3_ACCESS_KEY and S3_SECRET_KEY from the environment. Give it a token scoped to the least it needs — an agent that can be talked into calling a tool can be talked into calling it with your credentials.

How does Snagr run?

It speaks streamable-http, so it runs as a service you connect to over the network. It is published on npm as frontend at 0.0.0.

How current is this page?

The grade is for one exact copy of the source (3afec73517f6), read on 2026-10-11. The repository is watched and re-audited when it changes.

Advertisement