Unofficial, non-affiliated semantic voice/review harvest tools over the Mindcase API (shared core + MCP server and CLI surfaces)
Project description
ca-mindcase-mcp — semantic Mindcase harvest tools
ca-mindcase-mcp is an unofficial, non-affiliated third-party client for the
Mindcase job API, built as one shared core with two thin
surfaces:
- a stdio MCP server for Claude Desktop, and
- a CLI for server and batch use.
Not affiliated with Mindcase. This is an independent tool that talks to the public Mindcase API. It is not produced or endorsed by Mindcase, and it is a different package from Mindcase's own official
mindcase-mcpserver on PyPI. The distribution name isca-mindcase-mcp; the import package ismindcase.
The tools are semantic, not passthrough. Every provider quirk real-world harvest testing surfaced, and every mechanical screen this project committed to, lives inside the tools, so a consuming agent cannot skip them. A raw API passthrough would push that knowledge back into prompt prose and recreate the failure class it exposed (enforcement over prose).
This tool covers voice and review harvest only; ad intelligence is out of scope by design.
Architecture
mindcase/
_client.py raw HTTP client (INSIDE-BOUNDARY, underscore-private)
screens.py pure screens (contamination, reddit thread rank, youtube sorts)
tools.py semantic tools = the public import API (MindcaseTools)
mcp_server.py stdio MCP surface (imports the official mcp SDK)
cli.py CLI surface + smoke subcommand
The public import API is the tools layer (mindcase.tools, re-exported from
mindcase). A downstream orchestrator imports it and inherits the
mechanical screens by construction:
from mindcase import MindcaseTools
tools = MindcaseTools()
result = tools.amazon_reviews_screened("B0XXXXXXXX")
The raw HTTP client lives in the underscore-private mindcase._client and has
no MCP dependency, but it is not part of the public contract.
Threat model: the enforcement boundary (a BOUNDED claim)
The mechanical screens are enforced through every shipped surface -- the MCP
tools, the CLI subcommands, and the documented public import API
(mindcase.tools). The enforcement target is any consumer following the
documentation, including LLM agents. Deliberate access to underscore-private
internals, or direct HTTP with the API key, is outside this contract and is not
prevented by any Python package.
What is claimed, precisely. This package enforces:
- One ingestibility decision. Whether a harvest may be ingested derives from a
single
CompletenessStatecomputed at one client chokepoint (derive_completeness); every surface (a tool's primary rows, Amazoningest_allowed, the CLI exit code) keys on that state, not on a raw provider status. - Two-signal wallet detection. Wallet exhaustion is detected by (a) marker
extraction over an anchored reason-field set (
error/message/notice), read from BOTH the/resultspayload and the job resource on every terminal status, and (b) a balance reconciliation after every harvest, calibrated for the known dedicated-tool agents and fail-closed (audit-only) for any uncalibrated agent reached viarun_agent. - The screened-Amazon partition.
ingest_rowsis the only top-level row-carrying field; the full partition and any partial rows live inaudit.
What is NOT claimed. The silent-ingest class (a wallet-degraded or non-complete harvest presenting as ingestible) is not claimed to be provably closed. Its review arc is the evidence: three successive closure architectures -- quantified status gates, then centralized state derivation, then centralized marker extraction plus a balance backstop -- each shipped, and each was found by a cold adversarial reviewer to be re-enterable through a deeper channel (a status the gate didn't enumerate; a reason field the completed path didn't read; then a marker source, a control-flow branch around the gate, and an uncalibrated agent rate). The current fixes close the three named channels conservative-only (a verified meta-test asserts they only ever move a harvest toward withheld), but we do not assert no further channel exists.
Why shipping is still safe. Every residual vector fails conservative: the raw
provider response is cached to disk as ground truth, the full partition is visible
in audit, and any harvest is re-runnable. So the residual exposure is a degraded
harvest appearing ingestible until audit inspection -- never silent data loss, and
never an unrecoverable state.
Concretely, within the boundary: run_agent refuses every provider group that
has a dedicated tool (so no raw path, including a drifted slug like
amazon/reviews-v2, reaches a guarded agent); amazon_reviews_screened exposes
ingest_rows as the ONLY top-level row-carrying field (empty unless the harvest
is COMPLETE AND the pool screened CLEAN) while the full partition -- admitted /
quarantined rows with reasons, counts, metrics, and any wallet-partial rows --
lives inside a single audit container that is inspection data, never ingest;
every non-screened harvest tool likewise carries its primary rows only when
COMPLETE and moves a partial into audit.partial_rows; a harvest whose raw
response cannot be persisted fails rather than silently returning trimmed rows
only. A downstream orchestrator imports mindcase.tools and inherits all of this.
Ingestibility is a single derived state, not a status check. Whether a
harvest may be ingested is decided in exactly one place -- derive_completeness
at the client chokepoint -- which folds the terminal status, the data shape, AND
the wallet signal into one CompletenessState
(COMPLETE / PARTIAL_WALLET / FAILED / INDETERMINATE). Every downstream
decision (a tool's primary rows, Amazon ingest_allowed, the CLI exit code) keys
on that state; nothing re-reads the raw provider status to judge completeness.
COMPLETE is the only ingestible state, and the derivation is fail-closed:
any (status, wallet, data) combination not explicitly mapped -- a wallet signal
under any status including completed, a novel status a provider might emit --
derives a non-ingestible state, so an unmapped shape defaults to withheld rather
than ingestible (within the bounded claim above). This is the decomposition that
closes the wallet-under-completed regression a distributed status check kept
missing: because the status, wallet, and shape axes are orthogonal, the decision
is centralized rather than the gate being asked to re-enumerate every axis.
Wallet detection is marker-based AND balance-reconciled. The wallet signal
fed to the derivation is the OR of two independent detectors, and the audit
container records which fired (wallet_signals: marker, balance, or both)
plus the post-harvest balance_after:
- Marker --
_extract_wallet_markerreads an anchored reason-field set (error,message,notice), merged from BOTH the/resultspayload and the job resourceGET /jobs/{id}, on every terminal status (completed and failed alike). It is the only code that decides wallet exhaustion from provider prose, and a guard test pins both the field set and that exclusivity, so a marker in one of those fields on either source is seen by every path. A marker the provider places in a field outside the anchored set would be missed by this detector -- the balance backstop is the net for that case, and a new reason field must be added to the set deliberately (which the guard test forces). - Balance backstop -- after every terminal harvest,
GET /balanceis read once; ifusd_remainingcannot cover one more row at the agent's per-row rate, the harvest is forced to a non-ingestible state even with zero markers present. This signal does not depend on provider wording or field placement -- it measures the phenomenon (an exhausted wallet) directly rather than parsing prose about it. It fails closed twice: a failing/unreadable/balancewithholds rather than admits (so a broken balance endpoint does not wave a harvest through), and an agent with no calibrated per-row rate (anyrun_agentescape-hatch agent) is audit-only regardless of balance. False positives are conservative by construction -- a full harvest is withheld toaudit, visible and re-runnable, not passed downstream.
The class-closure gates (machine-checked invariants)
Two failure classes recurred across the review of this package (a bypass class:
a shipped surface reaching a guarded provider raw; and a silent-ingest class: a
non-complete or flagged harvest surfacing as ingestible). Each is closed by a
mechanical invariant that quantifies over its class dimension -- every
module, every field, every harvest-tool x status x wallet combination -- and, for
ingestibility specifically, by centralizing the decision into one fail-closed
derivation rather than asking a gate to re-enumerate orthogonal axes (a lesson
paid for when a completed-with-wallet-marker shape slipped a status-only
quantified gate). Adding a new module, public name, or harvest tool fails these
gates until it is deliberately classified:
- Bypass gate (
tests/test_boundary.py): enumerates every.pymodule in the package and asserts each is classified (in-boundary vs console surface), so a new file fails until placed. Over that full set it asserts (via AST) that the ONLY module importing the raw client type /runis the enforcement layer (tools.py), and that no console surface calls a raw.run(the FastMCP transport'smcp.run()excepted). It also asserts the package/tools/submodule public attributes and CLI subcommands equal explicit allowlists and hold no raw client. - Derivation-table gate (
tests/test_completeness.py): pins the mappedCompletenessStatefor every cell of the full (status x wallet-signal x data-shape) cross product against a hand-authored oracle, including thatcompleted+ wallet + rows derivesPARTIAL_WALLET(the falsification finding) and that every unmapped combination fails closed toINDETERMINATE. Because ingestibility is this one derivation, closing the wallet dimension here closes it for every consumer at once. - Extraction-anchor gate (
tests/test_completeness.py): pins the wallet reason-field enumeration (error/message/notice), asserts a marker is detected in each and ignored outside the set, and asserts (via AST over the client source) that the wallet predicate is called ONLY inside the extractor -- so no code path can decide wallet exhaustion from a field the extractor doesn't read. The balance backstop then catches exhaustion even when no marker exists at all:tests/test_status_matrix.pydrives acompletedharvest with a healthy status, no marker, and a balance below one row's cost and asserts it derivesPARTIAL_WALLETand is withheld, withaudit.wallet_signalsrecording which detector fired. - Ingest gate (
tests/test_status_matrix.py): a property test that, for every screened / non-screened harvest tool and every non-ingestible condition (FLAGGED, wallet-partial under a failed or a completed status, unknown status), recursively walks the entire returned object and asserts zero row-shaped dicts are reachable outsideaudit, whileauditpreserves exactly the provider's rows. OnCOMPLETE+ CLEAN it assertsingest_rows == audit.admitted_rows. - Status-matrix gate (
tests/test_status_matrix.py+test_cli_exit.py): parametrizes over theHARVEST_TOOLSregistry x terminal job status x wallet-marker, asserting which combinations may populate the primary rows field (only aCOMPLETEharvest; for Amazon, onlyCOMPLETEand CLEAN) and that realcli.mainexit codes agree (0 only for a full ingestible result, including non-zero for acompleted-with-wallet-marker harvest). A new harvest tool must join the registry and matrix or the suite fails.
The enforcement claim is bounded to what these gates check: a consumer following
the documentation (including an LLM agent reading the public surface) does not
reach a raw harvest, and does not ingest a non-CLEAN / non-COMPLETE pool by
reading the obvious field, for the channels these gates cover. As stated in the
bounded claim above, the silent-ingest class is not asserted provably closed;
the residual exposure fails conservative (cached ground truth, visible audit,
re-runnable). Deliberate use of underscore-private internals is out of scope.
Only mcp_server.py imports the MCP SDK. Both surfaces call the same
MindcaseTools methods, so behavior is identical whether a call arrives from
Claude Desktop or the command line.
Every tool returns trimmed rows for context economy (text, author, date,
permalink, engagement, plus depth/flags where relevant). The full raw response
is written to a cache dir keyed by job id, so ground truth survives without
transiting a model's context. The cache dir is MINDCASE_CACHE_DIR if set,
otherwise a user-writable location on both operating systems.
WARNING: the docs are unreliable; the live schema is the only truth
Mindcase's own documentation diverges from runtime behavior. Testing
established that the live GET /agents/{group}/{slug} schema is the only
trustworthy schema source. When an agent behaves unexpectedly, fetch its live
schema (the run_agent tool does this automatically for any agent without a
dedicated tool). Do not trust the published docs or curl examples.
run_agent is an escape hatch for agents without a dedicated tool only. An
agent that has a dedicated tool (any reddit/, youtube/, amazon/, instagram/,
twitter/*) is refused with the correct tool named, so the hatch cannot be used
to bypass a tool's quirk shaping, input guards, or the mandatory Amazon
contamination screen.
The quirks (all encoded in the tools)
Each quirk below was established through real-world harvest testing and is encoded in the shipped code (the "Where encoded" column names the module or function).
| # | Quirk | Where encoded |
|---|---|---|
| 1 | Base URL is https://api.mindcase.co/api/v1; the documented /v1/data pattern 404s. |
_client.BASE_URL |
| 2 | Run body MUST wrap params under a params key; a flat body is accepted then silently fails at the worker. |
_client.run |
| 3 | The verbatim failure reason is on GET /jobs/{id}, not GET /jobs/{id}/results. |
_client._verbatim_error |
| 4 | amazon/products.keywords is a string, not an array; run one job per keyword. |
tools.amazon_search_products |
| 5 | amazon/reviews.urls is a single ASIN/URL, not an array; one job per product. |
tools.amazon_reviews_screened |
| 6 | Amazon prefix dedup passes while templated 4-5 star bursts sail through; a contamination screen is required. | screens.amazon_contamination_screen |
| 7 | youtube/search and youtube/video-transcript agents do not exist; use youtube/videos; no transcript is available anywhere. |
tools.youtube_search_videos |
| 8 | youtube/videos has no sort param (relevance only); reproduce popular order post-hoc. |
screens.sort_videos_by_engagement |
| 9 | youtube/comments has no order or creator-exclude param, but rows carry authorIsOwner; sort and filter post-hoc. |
screens.sort_comments_by_likes, filter_creator_comments |
| 10 | youtube/comments effective ceiling is ~100 rows per video regardless of maxResults. |
tools.youtube_video_comments (documented in output) |
| 11 | twitter/tweets keyword search runs via a multi-token string in inputs; a single token is read as a handle, so force it with lang:en; never send query. |
tools._twitter_inputs |
| 12 | instagram/posts hashtag mode is inconsistent; fall back to query mode on zero rows. |
tools.instagram_posts |
| 13 | reddit/posts timeRange only applies under sortBy: top. |
tools.reddit_search_posts (documented) |
| 14 | Selecting highest-comment Reddit threads lands on price/megathread traffic; rank by experiential likelihood first. | screens.reddit_thread_screen |
| 15 | Facebook organic brand-page harvest is excluded (VOICE 0.0% on 4 rows; handle resolution unreliable). | no Facebook tool is exposed |
The screens
amazon_contamination_screen: flags templated / incentivized review bursts. The verdict (CLEAN/FLAGGED) is driven by two signals that cleanly separate organic from templated voice in the ground-truth sample data: burst concentration (share of 4-5 star rows in one calendar month) and template near-duplication (overlap-coefficient clusters among high-star texts). Off-topic token overlap and image-attachment ratio are reported as corroborating context but never flip the verdict alone, because off-topic overlap over-flags terse and non-English organic reviews. Rows are partitioned intoadmitted_rowsandquarantined_rows(admitted + quarantined always equals the total, so nothing is silently dropped). Quarantine is driven only by the reliable triggers, and only when they fire (near-duplicate cluster, burst-month membership); each quarantined row carries its reasons. off_topic and has_image are per-row annotations surfaced in_flagsand the counts, but they never quarantine a row, so a CLEAN pool loses no organic voice. A caller wanting zero cross-product contamination can drop admitted rows whose_flags["off_topic"]is true. AFLAGGEDpool is excluded from avatar weighting with its reasons logged. The partition contract is owned by aScreenResultdataclass whosecountsare derived (admitted + quarantined == total by construction), so no surface can rebuild or truncate them. Burst may drive quarantine only when the high-star pool has at leastMIN_BURST_SUPPORTrows (default 8); a sparse pool trivially reaches full concentration, so below that support burst demotes to a pool-level note rather than quarantining.reddit_thread_screen: ranks candidate threads by experiential likelihood before comment pulls, so results-journey and side-effects threads outrank deal / price / megathread traffic. Comment count is only a small volume tiebreaker.- YouTube post-hoc helpers: reproduce the sort and creator-exclude controls the API lacks.
Install
There are two ways to run it.
With uvx (recommended — no manual install).
uv fetches and runs the server on demand:
uvx ca-mindcase-mcp # starts the stdio MCP server
With pip.
pip install ca-mindcase-mcp
This installs requests and the official mcp Python SDK and creates two
console scripts: ca-mindcase (CLI) and ca-mindcase-mcp (MCP server). The
import package is mindcase (from mindcase import MindcaseTools).
API key
The API key is resolved in this order (works on Windows, macOS, and Linux):
MINDCASE_API_KEYenvironment variable.~/.secrets/mindcase.env(aMINDCASE_API_KEY=...line), if present.
The key is never logged, echoed, or written to the cache, and it must be
mk_live_-prefixed. Never commit it.
Register with Claude Desktop
Add the server to claude_desktop_config.json
(%APPDATA%\Claude\claude_desktop_config.json on Windows,
~/Library/Application Support/Claude/claude_desktop_config.json on macOS). The
one-block uvx config needs no absolute paths — put the key in the env block:
{
"mcpServers": {
"mindcase": {
"command": "uvx",
"args": ["ca-mindcase-mcp"],
"env": {
"MINDCASE_API_KEY": "mk_live_your_key_here"
}
}
}
}
This requires uv on your PATH (install it from
https://docs.astral.sh/uv/). On Windows, if uvx is not found, use its
absolute path as command (e.g.
C:\\Users\\<you>\\.local\\bin\\uvx.exe). If you installed with pip instead,
set "command": "ca-mindcase-mcp" and drop args. Restart Claude Desktop after
editing the config. The server speaks stdio only; it opens no network listener.
CLI usage
ca-mindcase balance
ca-mindcase reddit-posts --subreddit <subreddit> --keyword "product results"
ca-mindcase reddit-comments --post-url "https://www.reddit.com/r/<sub>/comments/xxxx/slug/"
ca-mindcase youtube-videos --query "product review"
ca-mindcase youtube-comments --video-url "https://www.youtube.com/watch?v=XXXX"
ca-mindcase amazon-products --keywords "product category" "alternative"
ca-mindcase amazon-reviews --asin B0XXXXXXXX
ca-mindcase instagram-posts --term <hashtag>
ca-mindcase twitter-search --query "product name"
ca-mindcase twitter-replies --tweet-url "https://x.com/handle/status/12345"
ca-mindcase run-agent --agent google/search --params '{"query":"..."}'
Add --out result.json to write JSON to a file instead of stdout, and
--cache-dir PATH to override where raw responses are cached.
Smoke test (post-install verification)
smoke runs one cheap live call per platform group (a balance read and a single
Reddit post). It is the only code path that touches the live API outside
production use.
ca-mindcase smoke
It prints a per-check report and exits non-zero if any check fails.
Tests
The package is covered by a fixture-based suite (no live API calls) whose boundary and completeness invariants are described in the threat model above: a bypass gate, a derivation-table gate, and ingest / status-matrix gates that quantify over every module, every public name, and every harvest-tool × status × wallet combination. The suite and its fixtures ship with the project source, not in the installable wheel.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file ca_mindcase_mcp-1.0.0.tar.gz.
File metadata
- Download URL: ca_mindcase_mcp-1.0.0.tar.gz
- Upload date:
- Size: 39.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8cbd6e3fda3007df974a6d48b66234849e6e38f7e475f99437f60abe72c015af
|
|
| MD5 |
ee93f2525bc10fdfdcc6422aeda6f2eb
|
|
| BLAKE2b-256 |
139911591e4a0648eb18e3b273ffc8477038dfe6549bf6a0bd170ff40955c426
|
File details
Details for the file ca_mindcase_mcp-1.0.0-py3-none-any.whl.
File metadata
- Download URL: ca_mindcase_mcp-1.0.0-py3-none-any.whl
- Upload date:
- Size: 42.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
dd75e8ada9ceca9fc6605683dd0f5be749c0bd10dece68bd408ffeff31a827ba
|
|
| MD5 |
68b0e4040bc60e6c9829e52a7df6f97c
|
|
| BLAKE2b-256 |
579b6f9280fabb6b73c5c5cb0beb4a1ed43c663bc432a060fed93d73be26b788
|