live-audit
Skill2 starsChanged 2 months agoMITView on GitHub
Audit mal-mcp — build/test/lint gate, live MCP tool edge-case sweep (input validation, not-found paths, mutations with capture/revert), source-level code review, and docs/metadata consistency. Use when asked to test/audit the published or just-fixed mal-mcp package, hunt for bugs/edge cases, or repeat "the same kind of testing as before.
We split this skill into the rules it gives an agent
9 rules. 1 is common, 2 are distinctive, and 6 are very specific to this project.
1
Common
Most of the 3 edited versions we read keep these.
2
Distinctive
This author's own choices. Worth a look.
6
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- could be automatic: a setting could enforce it
- Distinctive: line 99
What each agent must return, and what you do with it: Every agent returns a list of problems, nothing else: one entry per finding, each …
- Distinctive: line 123
Only then write section 6's report.
- Very specific: line 131
If package.json's version matches the npm-published version, live-testing the running tools _is_ testing the published package.
- Common: line 156
All green is a baseline, not proof of correctness — it only confirms nothing already-covered regressed.
- Very specific: line 246
Empty-because-filtered vs. empty-because-baseline: when checking that a filter (sfw/sfw_strict) removes something, first confirm the …
- Very specific: line 255
Live prompt testing: run the prompt-check skill against every prompt in src/prompts.ts — a static read comparing prompt text against tool …
- Very specific: line 352
Read the sentence the way a calling model would: "staff positions and voiced roles (capped to the first 50)" promises a cap on both, but …
- Very specific: line 384
Run the docs-consistency-check skill.
- Very specific: line 397
If asked to fix: implement the smallest correct change, add/extend a test in the matching src/__tests__/*.test.ts (mirror the existing …
“Common” is measured: kept by at least 6 in 10 of the 3 edited versions of this file we could read. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
live-audit — mal-mcp health check + edge-case hunt
Repo-specific playbook, for any agent/model working on this repo (not tied to
a particular harness — see AGENTS.md's own agent-agnostic framing). Use it
when asked to test/audit the published or just-fixed mal-mcp package, hunt
for bugs/edge cases, or repeat "the same kind of testing as before." Sibling
repos (tmdb-mcp, steam-games-mcp, anilist-mcp-server) keep their own
live-audit/SKILL.md — when either this file or a sibling's improves, sync
the useful parts both ways rather than letting them drift.
Goal: find real bugs/inaccuracies in the live tool behavior (against the real
Tenrai API, its official-MAL-API fallback, and the official MAL API itself)
and in the source, then fix what's found. Read AGENTS.md first if it's not
already in context — every fix must follow its conventions (guard()/
never-throw, format.schemas.ts's .strict() shaper/schema 1:1 rule vs.
clients/mal.ts's deliberate z.looseObject(), commit author/no-Co-Authored-By,
etc.).
This assumes the server is already reachable as an MCP connection in your
current session (e.g. as mcp__mal__* tools in Claude Code). If it isn't
connected, connect it first rather than skipping straight to step 1.
Contents
- How to run this: fan the sections out, don't walk them inline
- Confirm "published"/"fixed" actually means what you think it means
- Static pass first (cheap, catches regressions before you burn API calls)
- Safety rules for live testing (read before calling anything)
- Live edge-case sweep
- Source-level code review
- Docs/metadata consistency
- Report, then fix only what's confirmed
- Commit + changelog, if asked
How to run this: fan the sections out, don't walk them inline
Split the sections below across concurrent subagents and run them in parallel. Walking this file top to bottom in one thread is the wrong default: the sections touch independent surfaces, so serial execution wastes wall-clock and burns the main thread's context on file dumps it only needs a conclusion from. If your environment has no concurrency primitive, run inline and say so in the report.
Split by surface, so no two agents edit or reason about the same files:
| Stream | Sections | Model tier |
| A | 1 (static gate, check:api) plus shaper-vs-live-response drift | cheap |
| B | 5 (docs/metadata consistency) | cheap |
| C | 4, limited to src/lib/ + src/clients/ | strong |
| D | 4, limited to src/tools/ + prompts.ts, plus tool-description-check | mid |
| E | 3, read-only tools only | mid |
| main | 0, mutation testing, synthesis into section 6, every edit | strong |
Tier the models deliberately. Cheap models are fine where the work is
mechanical and the answer is checkable: running the gate scripts, diffing a
doc's tool list against registerTool( call sites, feeding live payloads
through shapers. Keep a strong model wherever the job is telling a real bug
apart from behavior the code or a tool description already documents on
purpose. That distinction is most of this audit's value and the most common
thing a weaker model gets wrong, in both directions: it reports honest
documented behavior as a finding, and it waves through a genuine bug because
some nearby comment sounded authoritative.
Two things never fan out:
- Mutation testing stays in the main thread, sequential. The MAL account is one shared mutable resource. Parallel writers overwrite each other's captured pre-state, which makes every revert unverifiable, and section 2's contract stops meaning anything.
- Don't point several agents at the same rate-limited upstream at once. Tenrai and the official API both throttle. Concurrent sweeps turn into 429s that read like findings.
Give each agent the facts already established, and tell it what not to redo. An agent that re-runs the static gate or re-derives a conclusion you already have is spending tokens to reprint your own notes.
Agents audit. They never edit. Fan-out is for finding things; every
write to the repo happens in the main thread, one change at a time. This is
not a stylistic preference. Parallel writers collide on shared files
(read.ts descriptions and the formatOfficial.ts constants they render,
tenrai.ts and the withFallback signature it calls), they collide on
shared build output (npm test and npm run build both write dist-tests/
and dist/, so concurrent runs clobber each other), and any agent stopped
mid-task leaves a half-applied change with no test behind it. Tried it once
here: five fix agents produced 582 changed lines across 16 files, several of
them incomplete and most without the tests they were asked to add, and the
tree had to be reverted wholesale.
So tell every agent, in the prompt: do not edit any file, do not commit, do
not run npm test or npm run build. Read-only investigation plus a report.
What each agent must return, and what you do with it. Every agent returns a list of problems, nothing else: one entry per finding, each with the file and line, what is actually wrong, a concrete repro or the code path that proves it, and the shape of the fix. Tell each agent not to pad the list — a section with nothing wrong in it should say so instead of manufacturing a finding to look thorough.
Merge only at the end, once every agent has reported. Merging is not concatenation. For each finding you:
- Re-verify it yourself against the source or a live call. Agents misread code and over-claim, and a wrong finding costs more than a missed one because it sends you editing correct code. Cheap-tier agents over-claim the most.
- Drop anything the code or a tool description already documents on purpose. This kills a large share of raw findings. A caveat spelled out in the description is honest behavior, not a defect.
- Deduplicate across agents. The same root cause surfaces in several streams wearing different clothes: a code-level cap and the doc sentence describing that cap are one fix, not two.
- Rank what survives by severity, and decide per finding whether to act. Silently wrong output outranks a misleading sentence, which outranks a nit.
Only then write section 6's report.
0. Confirm "published"/"fixed" actually means what you think it means
node -p "require('./package.json').version"; npm view mal-mcp version; git log --oneline -5If package.json's version matches the npm-published version, live-testing
the running tools is testing the published package. If you've since made
local fixes, remember the running MCP server is a separate process from
your edits — stdio servers don't hot-reload. Ask for a restart before
trusting a live call against fixed code, and state plainly whether findings
apply to the published package or to fixed-but-unreleased/unrestarted code.
1. Static pass first (cheap, catches regressions before you burn API calls)
npm run build && npm test && npm run lint && npm run format:checkOptionally npm run check:api too — a live upstream health-check against
both Tenrai and (if MAL_CLIENT_ID/OAuth creds are set) the official API. A
failure there can mean either a genuine shape drift (a real finding) or a
transient Tenrai/MAL outage (see notes/tenrai-reliability.md — gitignored
local log; check it before re-diagnosing a failure as new). Note
check-api.mjs's own scope: it does a raw fetch() with just
{ Accept: "application/json" } — it never routes through
TenraiClient/HttpClient, so it can neither exercise nor confirm any
client-side header/config fix. Don't treat a check:api pass/fail on a given
route as evidence for or against such a fix; it's testing raw upstream
reachability only.
All green is a baseline, not proof of correctness — it only confirms
nothing already-covered regressed. It says nothing about whether the
interesting logic (error/exception branches especially) is covered at all —
a line can execute inside a test without the test actually asserting on the
specific thing that matters (e.g. a test that triggers a validation error but
only checks isError: true, never the actual message text). npm run
test:coverage (~80% gate) measures lines executed, not whether the
assertions on those lines are meaningful. When reviewing or writing tests as
part of this audit, ask: does a test exist that deliberately triggers this
error path, and does it assert on the specific resulting message/shape?
Anything red here is the actual finding — stop and report it before moving to live testing.
2. Safety rules for live testing (read before calling anything)
- A real authenticated MAL account may be wired into the session. Call
get_my_user_infobefore doing anything else — if it succeeds, every mutation call below acts on a real person's real anime/manga list. - Read-only tools (
search_*,get_*except theget_my_*trio) are always safe to call freely — no special permission needed. - Mutation tools (
update_my_anime_status,update_my_manga_status,delete_my_anime_list_item,delete_my_manga_list_item) require the user's explicit go-ahead before this pass touches them. Reversible live tests against the maintainer's real list are acceptable when asked for — run themutation-test-safetyskill's contract for every mutation call (pre-state viaget_my_anime_list/get_my_manga_list, revert viaupdate_my_anime_status/update_my_manga_statusback to the original fields ordelete_my_*_list_itemif the entry didn't exist before). - Do not call
login_mal/submit_mal_redirectlive — re-running the PKCE OAuth flow can disrupt the session's already-configured token/store and isn't meaningfully revertible mid-session. - Dual-backend awareness:
search_anime,search_manga,get_top_anime,get_top_manga,get_seasonal_anime,get_upcoming_season,get_anime/get_manga,get_anime_statistics, andget_anime_recommendations/get_manga_recommendationsfall back to the official MAL API (MAL_CLIENT_ID, no OAuth) when Tenrai fails — seeReadFallback/withFallbackinsrc/clients/readFallback.ts. When reporting a finding on one of these tools, state which backend actually answered (Tenrai vs. the official fallback) — their available fields differ by design (see the fallback field-gap list indocs/api-references.md), so a "missing field" on a fallback response is expected, not a bug, unless it's missing from a field the fallback is documented to cover.
3. Live edge-case sweep
Batch independent tool calls together where your harness supports it — this
is slow one-at-a-time. Adapt ids/tools to whatever's currently registered
(grep -n 'registerTool(' src/tools/*.ts), don't just replay last run's exact
calls verbatim. This is one of the workstreams in "How to run this" above.
- Input validation boundaries: empty
q, negative/zero/decimalmal_id/page/limit,page/limitat their documented boundary and one past it, an unknown/misspelled param name (everyinputSchemashould reject a typo, not silently ignore it). Prefer read-only tools for the unknown-param probe; on a mutation tool, send only the bogus field (no other real field) — an ignored unknown field plus a real field still mutates the account for real (seemutation-test-safety). - Cross-field pairing rules:
update_my_anime_status/update_my_manga_statuscalled with onlyanime_id/manga_idand no other field — must reject (regression-check for the 0.7.1 fix; re-verify it still does, since this exact bug shipped and was fixed once already).num_watched_episodes/num_chapters_readvs. astatusthat doesn't make sense together (e.g. markingcompletedwith 0 episodes watched) — does MAL/the tool flag this or silently accept it? - Not-found / empty-result paths: a nonexistent-but-well-formed
mal_idfor anime/manga/character/person, a search returning zero results,delete_my_anime_list_item/delete_my_manga_list_itemon an id with no existing list entry. - Score/rating edge cases: an anime/manga with a Tenrai
scoreof exactly0(should surface as absent, not literal0— shipped in 0.2.0, worth a regression spot-check), a brand-new/unranked entry with norank/popularityat all. - Payload-size risk:
get_anime_reviews/get_manga_reviews(1200-char truncation per review,limitparam),get_anime_recommendations/get_manga_recommendations(documented 25-item cap),get_person's voice roles (documented 50-item cap),get_my_anime_list/get_my_manga_listagainst a real account with a large list (pagination fields actually usable, not just present). - Documented vs. actual shape: for anything that looks surprising live,
grep the field back to its
.describe()text and itsformat.schemas.ts.strict()schema — does the tool's own description/outputSchema promise what you just saw (or promise something you didn't)? A.strict()schema rejecting a real live response is itself a finding (shaper/schema drift), not something to silently work around. - Unicode / adult / locale weirdness: emoji-only queries, non-Latin
scripts,
sfwtoggling (including during an official-API fallback, which enforces it client-side via each node'snsfwfield — confirm it isn't silently ignored there), whitespace-only search terms. - Empty-because-filtered vs. empty-because-baseline: when checking that a
filter (
sfw/sfw_strict) removes something, first confirm the UNFILTERED call is non-empty — otherwise an empty filtered result proves nothing. This bit aget_anime_newsdescription once: it claimedsfw"returns an empty list entirely for an NSFW anime," but that was verified against a hentai title that simply had no news to begin with. The real behavior (a mainstream R+ title with actual news) is that it cuts the list down, not to zero. Pick a subject with a real non-empty baseline (e.g. High School DxD, not Bible Black) before concluding what a filter does. - Live prompt testing: run the
prompt-checkskill against every prompt insrc/prompts.ts— a static read comparing prompt text against tool names/params misses argument-handling bugs that only show up when actually rendered through the real MCP protocol. Render each prompt with every subset of its optional arguments, not just none-of-them and all-of-them, and assert each supplied value literally appears in the rendered text. Prompts whose arguments are individually.optional()but only meaningful together drop a partial argument on the floor: passingseasonwithoutyeartoseasonal_overviewrendered the current-season text with no sign the input was discarded.
For anything that looks like a bug, don't stop at the symptom — grep the source for the actual mechanism (the const/regex/schema that produced it) before calling it a finding. A live response that merely looks odd but ties back to correct, intentional code (e.g. a documented fallback field gap) isn't a finding.
The same caution runs the other way: a finding produced by reading source
without calling any live tool is a hypothesis, not a confirmed bug — Tenrai's
and the official API's actual behavior sometimes contradicts what the code's
shape implies (this is exactly why the seasonal_overview item above says
"verify live before reporting"). Before reporting any source-only finding,
spend one live call confirming the actual response shape it depends on.
A third caution, for any upstream that can be transiently flaky: one paired live A/B test is suggestive, not proof of causality. A single "failure without the change, success with it" result can just as easily be the upstream self-resolving mid-test. Before writing up a live-tested fix as confirmed, look for (or run) a same-day control on the identical route through a path that does not carry the change — if that also succeeds, the fix's causal effect is unconfirmed, not settled, regardless of how clean the original paired numbers looked.
4. Source-level code review
Sweep every file under src/tools/, src/clients/, and src/lib/ (lighter
pass on the last group unless something specific points there) for:
- A tool whose field name for a concept diverges from every sibling tool
handling the same concept (e.g. one list tool naming its MAL username/id
parameter differently from another) — grep every call site of a shared
concept and diff the field names, don't just check each in isolation. This
bug class can't be caught by testing well-formed values — every call site
works fine on its own. In this repo every
inputSchemaisz.strictObject(), so a misspelled or sibling-inconsistent name is rejected outright (verified live:update_my_anime_statuswith the read-sidenum_episodes_watchedreturnsUnrecognized key, and so doessearch_animewithoder_by). That turns the failure loud, but it does not make the divergence itself harmless: a caller that guesses the sibling name still gets an error instead of the data. On a sibling project without that strictness the same divergence is silent and far worse — confirmed live on anilist-mcp-server, where a search tool's user-filter parameter was named differently from every other user-scoped tool, so the sibling-consistent (but wrong) name returned the unfiltered global feed instead of erroring or filtering. Re-check the strictness claim before relying on it; it has already gone stale here once. - A
summarize*/shaper function insrc/lib/format.ts/formatOfficial.tsthat doesn't end by calling its paired schema's.parse()insrc/lib/format.schemas.ts— AGENTS.md's schema-first convention requires every shaper validate its own output this way so the shaper and itsoutputSchemacan't drift silently. - A schema that landed in the wrong file for its purpose:
format.schemas.tsschemas must bez.strictObject()(shaped/summarized output), whileclients/mal.ts'sMyUserInfoSchema/MalListResponseSchema/ListStatusUpdateResponseSchemaare deliberatelyz.looseObject()(raw upstream responses forwarded near-verbatim) — a new schema mixing the two styles, or a strict schema applied to a raw upstream response, is a bug. withFallback/ReadFallbackcall sites that don't distinguish a genuine upstream failure (5xx/network/timeout — should fall back) from a real 4xx (e.g. a genuine "not found" — should not silently retry against the official API and potentially mask the real error). Check the classifier from both ends: which codes it lets through, and which genuine-outage codes it silently omits. A found-in-this-repo example of the second kind:rate_limited(429) andunknown("Upstream returned invalid JSON", i.e. a 200 carrying a CDN interstitial) are both Tenrai being down, but neither reached the fallback. When a code's exclusion has no comment, no test and nogit log -Sdiscussion behind it, treat it as an oversight, not a design choice.- Fallback that swallows the primary's error.
fallbackCall()running outside a try/catch means a failure in the fallback becomes the error the agent sees, and the real cause is gone. Concretely: Tenrai 503 plus a revoked Client ID yields "run login_mal" on a read tool that needs no token. Any error-path test asserting onlyassert.rejects()with no predicate will pass straight through this bug — grep for bareassert.rejects(in the fallback tests. - A cache wrapping a fallback instead of wrapping only the primary. If
wrapStaleOnError/#cachedsits outsidewithFallback, one transient 5xx poisons the key with the fallback's deliberately thinner payload for the whole TTL, long after the primary recovered, and nothing in the response says it is degraded. Verify the nesting order at every cached call site, not just the newest one. - A timeout that doesn't span the whole request.
clearTimeoutbelongs in afinallyaround fetch plus body consumption. Scoped to thefetchcall alone it expires at response-headers time, so an upstream that stalls mid-body hangs the tool call forever with no error. Same trap for any future streaming/pagination helper. - A cap or trim applied to only some of the fields the description covers.
Read the sentence the way a calling model would: "staff positions and voiced
roles (capped to the first 50)" promises a cap on both, but
.slice(0, 50)may sit on just one array. Confirm each capped field separately against a deliberately extreme subject (people/8074/fullreturns 534 anime credits), and check the sibling tool that reuses the same shaper —get_random_personsharessummarizePerson's cap and documents none of it. - Text truncated without an ellipsis marker. Everything here trims through
clip(), which appends…; a bare.slice(0, n)leaves the agent unable to tell a cut-off value from a complete one, so it will quote a truncated review as if it ended there. Grep for.slice(0,on string fields. - Rate limiting: does every new Tenrai/official-API call site actually route
through the shared
withThrottle/RateLimiterwiring insrc/clients/httpClients.ts, or does a new method construct its ownHttpClient/bypass the limiter? - Tool failures that don't go through
guard()(src/tools/guard.ts) — AGENTS.md requires every tool failure return{ isError: true }, never a raw throw. TtlCachededup (src/lib/cache.ts's in-flight-request coalescing) — a new cached method that fetches directly instead of routing through#cache/#cachedbypasses this for no reason.- Logger/credential leakage:
MAL_CLIENT_IDand OAuth access/refresh tokens never appear in cleartext in any new debug/warn/error log line (src/lib/logger.tsredacts credentials — check a new call site doesn't route around it by string-concatenating a header value directly into a log message). The token store path is not a credential andtokenStore.tslogs it on purpose when the file is unreadable; don't report that as a leak. - The
tool-description-checkskill (Glama's TDQS rubric) compliance for any new or edited tooldescription/field.describe()text, per AGENTS.md.
5. Docs/metadata consistency
Run the docs-consistency-check skill. Also log any new Tenrai quirk or
reliability data point turned up this pass in notes/tenrai-reliability.md
(gitignored), with a date, the same way past passes have — don't let a
fresh finding live only in this conversation's transcript.
6. Report, then fix only what's confirmed
Rank findings by severity. For each: what's wrong, concrete repro (exact tool
call + params), the file/line causing it, and the fix shape. Silence on a
category you didn't get to (rather than implying full coverage) beats a false
"all clear." Then run the self-learning skill against each confirmed
finding.
If asked to fix: implement the smallest correct change, add/extend a test in
the matching src/__tests__/*.test.ts (mirror the existing test's style in
that file), then re-run the full build && test && lint && format:check gate
before calling it done. Re-verify live only after the running MCP server
process has been restarted (it won't pick up source changes on its own) —
build/test passing is necessary but re-confirming actual live behavior
changed is stronger evidence than trusting the diff alone.
7. Commit + changelog, if asked
One fix:/feat: commit per logically distinct change (don't bundle two
unrelated fixes into one commit), then a separate docs: commit adding to
CHANGELOG.md's [Unreleased] section (style: the changelog-style skill)
with one bullet per fix, each linking that fix commit's short sha
(https://github.com/Grinv/mal-mcp/commit/<7-char-sha>). Author/committer
Grinv <4070730+Grinv@users.noreply.github.com>, no Co-Authored-By
trailer (AGENTS.md's commit convention). Don't push unless explicitly asked.
Other versions3 projects use a version of this.
Word for word · 0
None found.
Edited · 3
- Grinv/tmdb-mcp★ 514% same
- Grinv/steam-games-mcp★ 421% same
- Grinv/anilist-mcp-server★ 123% same
Wrote their own version · 0
None found.
How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.
In this repository9 other files it gives its agents.
- AGENTS.mdAGENTS.md
- CLAUDE.mdCLAUDE.md
- changelog-styleSkill
- docs-consistency-checkSkill
- mutation-test-safetySkill
- prompt-checkSkill
- releaseSkill
- self-learningSkill
- tool-description-checkSkill
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
Reviews can't be read right now.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

