Skip to main content
Glama
Rajkumar2002-Rk

fraud-mcp

fraud-mcp

An MCP server that exposes fraud-investigation tools to an AI agent, built around one architectural claim:

The rules engine decides what fired. The model only interprets and narrates.

An agent connected to this server can pull an account's transactions, run a deterministic rules evaluation, pivot across devices, and record a case. What it cannot do is decide for itself whether a pattern is fraud — that verdict comes from a rules engine with fixed thresholds and reproducible output, and every response carries enough provenance that a human can audit the decision months later.

The dataset is entirely synthetic and generated from a fixed seed. Nothing here is real or scraped.


Why it is built this way

An LLM reading raw transactions and announcing "this looks like structuring" has produced an opinion, not a finding. It is unreproducible (the same rows may get a different answer tomorrow), unauditable (there is no threshold to point at), and unfalsifiable (there is no way to show it was wrong). In a regulated setting that is not merely sloppy, it is unusable: a fraud decision generally has to be explainable to a customer, a reviewer, or a regulator.

So the labour is split:

Rules engine

Model

Decides which rules fired

Chooses thresholds

Assigns severity

Explains what a firing means

Decides which account to look at next

Writes the case narrative

The model is used for the part it is genuinely good at — judgement about where to look next, and turning a set of machine verdicts into prose a human can act on. It is kept away from the part where non-determinism is a liability.

Three design choices follow directly from that split, and they are the ones worth looking at:

1. Every rule reports its thresholds and its evidence. A FIRED result carries the thresholds applied, the values observed, and the exact transaction ids relied on. A narrative that cites no transaction id is unfalsifiable, so the tool makes the citations impossible to miss.

2. There are three rule states, not two. FIRED, NOT_FIRED, and SKIPPED. A rule that could not be evaluated — no baseline history, no device telemetry — returns SKIPPED with a reason. Collapsing that into "did not fire" is how a false negative gets laundered into a clean bill of health, and it is the single easiest way for an agent to be confidently wrong.

3. The evaluation clock is frozen. Rules evaluate against the dataset's as_of timestamp, not wall-clock now. Without it, "5 transactions in 10 minutes" would silently stop firing as the seeded data aged, and every verdict in this README would rot. Every response echoes as_of so a result can be reproduced exactly.


Related MCP server: commerce-ops-harness

Quick start

Requires uv and Python 3.12+.

git clone <your-fork> && cd fraud-mcp
uv sync --extra http
uv run python scripts/serve.py seed    # build the synthetic dataset
uv run python scripts/serve.py check   # print row counts and the as_of clock

Run the test suite and the end-to-end scenarios:

uv run pytest
uv run python scripts/run_scenarios.py

The scenario runner drives a real MCP client against the server through six investigation scenarios — including the failure paths — and asserts on every response. It is a scripted client, not an agent: fully reproducible.

Run the server directly:

uv run python scripts/serve.py stdio
uv run python scripts/serve.py http --port 8000

Why scripts/serve.py and not the fraud-mcp console script? Both work, but serve.py puts src/ on sys.path itself rather than relying on the editable install. During development uv was observed to disable this project's editable .pth entry after a source edit, so the console script would fail with ModuleNotFoundError until the next uv sync --reinstall-package fraud-mcp. An MCP server that intermittently fails to start is a bad demo, so the documented entry point is the one that keeps working across edits.

The dataset lives in ~/.local/share/fraud-mcp/fraud.sqlite3 (override with FRAUD_MCP_DB). It is deliberately kept out of the repository: it is a generated artifact, reproducible from seed 1337, and writing it inside the project tree made uv treat the package as modified on every run.


Connecting it to Claude

Claude Code

A .mcp.json is committed at the repo root, so from inside the project directory the server is picked up automatically. Verify with /mcp. If you edit the tool definitions, see the restart note below.

Claude Desktop

Add this to claude_desktop_config.json (~/Library/Application Support/Claude/ on macOS, %APPDATA%\Claude\ on Windows), then restart Claude Desktop:

{
  "mcpServers": {
    "fraud-mcp": {
      "command": "uv",
      "args": [
        "run",
        "--directory",
        "/absolute/path/to/fraud-mcp",
        "python",
        "scripts/serve.py",
        "stdio"
      ]
    }
  }
}

Use the absolute path to your checkout. If uv is not on the PATH that Claude Desktop sees, use its full path (which uv).

Try: "Account ACC-1013 was reported by a customer. Investigate it and flag a case if warranted."

After editing tool names or descriptions, restart the client

An MCP server is spawned once, when the client starts, and the client holds the schema it received for the life of that process. Editing server.py does not reach a client that is already connected — it keeps advertising the old tool names and the old descriptions until the process is restarted (⌘Q and reopen for Claude Desktop; a new session for Claude Code).

This is worth stating because it is easy to miss and the failure is quiet. The tool schema an agent sees is a deployed artifact with its own lifecycle, and it can drift from the source that defines it. Nothing in the test suite catches the drift, either: the tests import the module directly and always see current code, so they stay green while a connected client serves something months old.

It happened here. check_velocity_rules was renamed to evaluate_fraud_rules (see FINDINGS.md) and a live session went on offering the old name and a description missing a rule that had since been added — tests passing throughout. If you change a name or a description, restart the client and confirm what it actually serves:

uv run python scripts/agent_cli.py list-tools

That prints the schemas as the server advertises them right now, which is the thing to compare against what your client is showing you.


The tools

evaluate_fraud_rules(account_id)

The authoritative risk verdict. Runs all seven rules and returns which fired, which did not, and which could not be evaluated. Deterministic.

{
  "ok": true,
  "account_id": "ACC-1021",
  "verdict": {
    "rules_fired": 2,
    "rules_evaluated": 6,
    "rules_skipped": 0,
    "highest_severity_fired": "critical",
    "fired_rule_ids": ["AMOUNT_SPIKE", "STRUCTURING"],
    "skipped_rule_ids": []
  },
  "rule_results": [
    {
      "rule_id": "STRUCTURING",
      "status": "FIRED",
      "severity": "critical",
      "thresholds": { "reporting_threshold": 10000.0, "min_txns": 3, "window_hours": 72 },
      "observed": { "max_in_band_within_window": 5, "window_total_amount": 46945.0 },
      "evidence_txn_ids": ["TXN-006585", "..."],
      "explanation": "5 transactions between 8500 and 10000 USD within 72 hours..."
    }
  ],
  "interpretation_contract": { "authority": "...", "your_role": "...", "skipped_is_not_clean": "..." },
  "provenance": { "as_of": "2026-09-14T12:00:00+00:00", "rules_version": "2026.09.2", "...": "..." }
}

get_transactions(account_id, days)

Evidence, not a verdict. days is 1–365, counted back from as_of. A response with zero rows carries an empty_result_guidance block stating explicitly what the emptiness does and does not imply.

lookup_device_history(device_id)

Every account and login event seen on a device — the pivot that turns one compromised account into a mapped takeover ring. Surfaces distinct_accounts_with_events, failed_login_count, and password_reset_count.

flag_case(account_id, reason, severity, rule_ids)

Records an investigation outcome. The only tool that writes. Its response contains an audit block that independently re-runs the rules engine and compares the submission against it: cite rules that are not firing, omit rule_ids, or pick a severity the engine does not support, and the case is still recorded but flagged supported_by_engine: false with warnings for the human reviewer.

That audit block matters more than it looks. It means a model that skips straight to flagging, or that escalates on vibes, leaves a machine-readable trace of having done so — rather than producing a case file indistinguishable from a well-founded one.


The rules

Rule

Severity

Fires when

VELOCITY_BURST

high

≥5 transactions in any 10-minute window

AMOUNT_SPIKE

medium

A transaction ≥5× the account's median baseline (90-day baseline, excluding the last 7 days)

NEW_GEO_HIGH_VALUE

high

≥1000 USD in a country absent from the prior 180 days

STRUCTURING

critical

≥3 transactions of 8500–10000 USD within 72 hours

SHARED_DEVICE

critical

A device used by ≥3 distinct accounts within 30 days

IMPOSSIBLE_TRAVEL

high

Two card-present transactions in different countries ≤120 minutes apart

DORMANT_REACTIVATION

medium

An account transacts again after 90+ days of silence, opening at ≥500 USD or ≥3 transactions in 48h

Thresholds live in one dict (rules.T) and are quoted back in every response, so there are no magic numbers buried in the logic.

DORMANT_REACTIVATION is the odd one out, and deliberately so. Every other rule that needs history degrades to SKIPPED on a dormant account — which is exactly the population an account takeover prefers, and exactly the moment detection matters. So this rule uses absolute thresholds only and never skips for want of a baseline. On a dormant account that has not yet woken it reports NOT_FIRED armed, naming the dormancy it is watching, so a reviewer can see the tripwire is set rather than inferring it from silence. It was added because an agent found the gap — see FINDINGS.md.


The planted patterns

The dataset contains deliberate fraud so an agent has something real to find. A dataset of uniform noise makes for a demo whose only honest answer is "nothing here", which tests nothing.

Account

Pattern

What is planted

ACC-1007

Velocity burst

7 transactions in ~5 minutes, escalating 1.00 → 1890.00 (card testing then cash-out)

ACC-1013

Account takeover

Failed logins, password reset, then a 2450 USD purchase in a new country — all from DEV-ATO-01

ACC-1014, ACC-1015

Corroborating

Same attacker device, making the device the common factor

ACC-1021

Structuring

5 transfers of 9.1k–9.7k across 40 hours, each below the 10k threshold

ACC-1030

Impossible travel

Card-present in US, then SG 38 minutes later

ACC-1002

Control: clean

Ordinary activity only — nothing should fire

ACC-1040

Dormant reactivation

Silent ~11 months, then a 4-transaction burst opening at 1,450 USD — fires DORMANT_REACTIVATION while the baseline rules SKIP

ACC-1009

Control: dormant

No activity in 180 days — the empty-result trap

The two controls are the interesting ones. ACC-1002 catches a rules engine that fires on noise. ACC-1009 catches an agent that reads an empty result as an all-clear.


Error handling

No tool ever raises across the MCP boundary. Every failure is an envelope:

{
  "ok": false,
  "error": {
    "code": "UNKNOWN_ACCOUNT",
    "message": "No account with id 'ACC-9999' exists in this dataset.",
    "remediation": "Check the identifier against a prior tool response...",
    "details": { "received": "ACC-9999", "example_valid_ids": ["ACC-1000", "..."] }
  }
}

Codes: UNKNOWN_ACCOUNT, UNKNOWN_DEVICE, INVALID_ARGUMENT, INSUFFICIENT_DATA, INTERNAL_ERROR. remediation is mandatory — an error an agent cannot act on is a dead end that usually ends in the agent inventing an answer instead.

One subtlety worth calling out: the MCP SDK validates arguments against the JSON Schema before the handler runs, so an out-of-range days never reaches our code and the client would get a raw pydantic string. middleware.py catches errored tools/call results and rewrites them into the same envelope, so there is exactly one error contract regardless of which layer rejected the call.


Layout

src/fraud_mcp/
  server.py       MCP surface: tool registration, schemas, descriptions
  tools.py        Handlers — plain functions returning plain dicts
  rules.py        The deterministic engine. No model, no randomness
  middleware.py   Normalises schema rejections into the error envelope
  errors.py       Structured error taxonomy
  db.py           SQLite schema, connection, the frozen as_of clock
  seed.py         Synthetic data generator and planted patterns
tests/            78 tests: rules, handlers, and the MCP protocol surface
scripts/          run_scenarios.py — end-to-end investigation scenarios
FINDINGS.md       Where an agent misused these tools, and what fixed it
notes/runs/       Raw agent transcripts - the evidence behind FINDINGS.md

tools.py is transport-agnostic on purpose: the tests exercise the same code an agent hits, so there is no drift between what is tested and what is called.


FINDINGS.md

FINDINGS.md records what happened when an agent was actually pointed at this server. The experiment is a controlled one: FRAUD_MCP_PROFILE=v0 serves the same rules engine behind a naive first-draft interface, v1 serves it behind the hardened one, and the same five investigation tasks were run against both. Raw transcripts are in notes/runs/.

The short version:

  • All four v0 runs called get_transactions before evaluate_fraud_rules — forming an opinion from raw rows before asking the deterministic engine, which is precisely the failure this design exists to prevent. All five v1 runs reversed it. The fix was three pieces of prose, the most effective of which told a tool what it is not.

  • Opaque errors cost 21 wasted calls. "Error executing tool evaluate_fraud_rules" cannot be recovered from; a typed code with a mandatory remediation can.

  • The agent read my schema examples as data and called one. A planted identifier in an example leaked the answer and made v1 look far better than it was, until the agent volunteered how it had got there.

  • One failure no wording could fix: with no way to list accounts or devices, the agent brute-forced the identifier space — 119 calls in the honest rerun. But given the identical gap, Claude Desktop made four calls and stopped: "if I named more accounts, I'd be inventing them." Same tools, opposite behaviour. The difference was having a human to hand the question back to, which makes an escalation path a safety control in its own right.

  • The guard that mattered most wasn't in the interface at all. It was SKIPPED as a third rule state, distinct from NOT_FIRED. Prose in a description is a suggestion; a state in the data model is a constraint.

  • Some clients search for tools by keyword before loading their schemas, so a tool name has to win a search before its description can influence anything. This one was called check_velocity_rules — a name describing one of its six rules — until that surfaced. Renaming it to evaluate_fraud_rules is the one fix in this project that came from running against a second client.

That document is the point of the project. The server is the apparatus.

Available Tools

4 tools
evaluate_fraud_rulesEvaluate fraud rules (deterministic)A
Read-onlyIdempotent

Run all seven fraud rules against an account and return which ones fired, and why.

THIS IS THE AUTHORITATIVE RISK VERDICT. The engine is deterministic: the same
account always yields the same result. Do not second-guess it, re-derive it
from raw transactions, or soften it.

Rules evaluated: VELOCITY_BURST, AMOUNT_SPIKE, NEW_GEO_HIGH_VALUE, STRUCTURING,
SHARED_DEVICE, IMPOSSIBLE_TRAVEL, DORMANT_REACTIVATION.

Covers, in plain terms: transaction velocity and card testing, spending
anomalies against the account's own baseline, unfamiliar geography, money
laundering by structuring or smurfing below a reporting threshold, account
takeover and credential stuffing via shared devices, and cloned cards
(impossible travel). Use this tool for ANY question about whether an account
is risky, compromised, laundering, or worth escalating - not only questions
phrased around velocity.

Every rule returns one of three states, and the difference matters:
  FIRED     - the threshold was met; `evidence_txn_ids` lists the transactions.
  NOT_FIRED - evaluated, threshold not met. This is a genuine pass.
  SKIPPED   - could NOT be evaluated (e.g. too little history for a baseline).
              This is an UNKNOWN, not a pass. Report skipped rules explicitly.

Each result carries the thresholds that were applied and the observations
measured against them, so any verdict can be recomputed by hand later. Cite
`rule_id` and `evidence_txn_ids` in anything you write.

Call this BEFORE flag_case. Takes no time window: each rule applies its own
documented lookback.
ParametersJSON Schema
NameRequiredDescriptionDefault
account_idYesAccount identifier in the exact form 'ACC-####' (e.g. 'ACC-1013'). Copy it from a previous tool response or the user's message; do not invent or reformat it.

Output Schema

ParametersJSON Schema
NameRequiredDescription

No output parameters

TDQS

A4.7/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Beyond the read-only/idempotent annotations, it discloses determinism, the meaningful distinction between FIRED/NOT_FIRED/SKIPPED, and that SKIPPED is an UNKNOWN rather than a pass. It adds that thresholds and observations are returned for manual recomputation, which is behavior an agent needs to trust and report correctly.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Though longer than typical descriptions, it is modular: purpose/authority, rule list, human-readable coverage, ternary result semantics, evidence recomputation, and ordering. Each section earns its place, and the critical directive is front-loaded.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a rule-evaluation tool with an output schema, it covers the full decision context: all seven rule IDs, what each covers in plain terms, the three result states and their interpretation, evidence/instructions for citing rule_id and evidence_txn_ids, and the relationship to flag_case. Nothing needed to invoke it correctly is left unresolved.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

There is one parameter and the schema already documents it thoroughly with format, pattern, examples, and the instruction to copy rather than invent or reformat the account_id. The tool description adds no extra parameter-level meaning, so the baseline of 3 applies.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The first sentence names a specific verb ('Run all seven fraud rules against an account') and the exact resource ('an account'), then states the return value ('which ones fired, and why'). It differentiates itself from siblings by framing itself as the authoritative risk verdict, so an agent won't confuse it with get_transactions or flag_case.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description explicitly says to use it for ANY risk-related question, not just ones phrased around velocity, and it gives sequencing advice ('Call this BEFORE flag_case'). It also tells the agent not to second-guess or re-derive from raw transactions. It explains that no time-window parameter is needed because each rule has its own documented lookback.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

flag_caseFlag an investigation caseA

Record an investigation outcome against an account. THIS WRITES TO THE CASE LOG.

Call this only after `evaluate_fraud_rules`, and only when you can cite the
rule ids that fired. The response includes an `audit` block that independently
re-runs the rules engine and compares your submission against it: if you cite
rules that are not firing, omit rule ids entirely, or set a severity the engine
does not support, the case is still recorded but flagged with warnings for the
human reviewer.

Not idempotent - each call creates a new case. Do not retry on success.
ParametersJSON Schema
NameRequiredDescriptionDefault
reasonYesWritten justification, minimum 20 characters. MUST name the rule ids that fired and the transaction ids they cite, e.g. 'STRUCTURING fired: five transfers TXN-001234..TXN-001238 of 9.1k-9.7k within 40h, each below the 10k reporting threshold.' A reason without evidence cannot be reviewed by a human analyst and will be marked unsupported.
rule_idsNoThe rule ids that justify this case - pass `fired_rule_ids` from the evaluate_fraud_rules response verbatim. Omitting this records the case as UNSUPPORTED and unauditable. Always call evaluate_fraud_rules first so you have real ids to pass.
severityYesCase severity. Use the `highest_severity_fired` value returned by evaluate_fraud_rules. Do not pick a severity by intuition - if you deviate from the engine's value, the response will record a warning and you must justify it.
account_idYesAccount identifier in the exact form 'ACC-####' (e.g. 'ACC-1013'). Copy it from a previous tool response or the user's message; do not invent or reformat it.

Output Schema

ParametersJSON Schema
NameRequiredDescription

No output parameters

TDQS

A4.7/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

The description goes well beyond the annotations: it discloses that the call writes to the case log, that the response contains an audit block re-running the rules engine, and what happens when the submission mismatches the engine. It also adds actionable non-idempotency guidance: 'Not idempotent - each call creates a new case. Do not retry on success.' No contradiction with annotations.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is compact and front-loaded with the core purpose, followed by the key usage condition, audit behavior, and idempotency warning. Every sentence adds operational value; there is no filler or repetition that weakens it.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a write tool with a rich schema and output schema, the description covers prerequisites, correctness constraints, response audit behavior, and retry policy. An agent has everything needed to decide whether and how to call this tool correctly.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, and the parameter descriptions already document the rule-id source, severity alignment, account format, and reason requirements. The main description reinforces these, but it does not add meaning beyond what the schema already provides, so the baseline of 3 is appropriate.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description opens with a specific verb and resource: 'Record an investigation outcome against an account.' It also names the case log and the prerequisite sibling (`evaluate_fraud_rules`), so an agent can distinguish this tool from get_transactions and lookup_device_history without needing the schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Usage is explicit: 'Call this only after evaluate_fraud_rules, and only when you can cite the rule ids that fired.' This clearly states when the tool should be used and positions it relative to the preceding rules-evaluation step, with no ambiguity about sequencing.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

get_transactionsGet recent transactionsA
Read-onlyIdempotent

Return an account's transactions within a lookback window, with a summary.

This is EVIDENCE, not a verdict. It does not evaluate risk. To learn whether
an account has tripped any fraud rule, call `evaluate_fraud_rules` - reading
these rows and forming your own opinion is exactly what this server is built
to prevent.

Returns an error envelope (`ok: false`) for unknown accounts or an out-of-range
`days`. A successful response with zero transactions carries an
`empty_result_guidance` block explaining what the emptiness does and does not
imply; read it before drawing any conclusion.
ParametersJSON Schema
NameRequiredDescriptionDefault
daysYesLookback window in days, counted back from the dataset's frozen `as_of` clock (NOT from today's date). Use 7-30 for a velocity or takeover review; 90+ to establish a spending baseline or to investigate a dormant account. An empty result means the window was too narrow, not that the account is clean.
account_idYesAccount identifier in the exact form 'ACC-####' (e.g. 'ACC-1013'). Copy it from a previous tool response or the user's message; do not invent or reformat it.

Output Schema

ParametersJSON Schema
NameRequiredDescription

No output parameters

TDQS

A5/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Beyond the readOnlyHint and idempotentHint annotations, the description discloses the error envelope for unknown accounts or out-of-range days, the empty_result_guidance block on empty success responses, and the frozen 'as_of' clock behavior. It also warns that reading these rows does not constitute a risk assessment. No contradiction with annotations.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The main description is three compact, information-dense sentences. The 'EVIDENCE, not a verdict' warning is front-loaded, and the error/empty-result behavior is stated in the last sentence. Parameter descriptions add targeted guidance without redundancy.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With a full input schema, an output schema, and safe-read annotations, the description still covers the essential behavioral edge cases: error responses, empty-result interpretation, and the exact distinction from evaluate_fraud_rules. Nothing critical for a correct call is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Even though the schema already documents both parameters at 100%, the description adds high-value semantics: days are counted from a frozen as_of clock, 7-30 vs 90+ refer to review types, and account_id must be copied exactly as-is. This is a strong layer beyond the schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description opens with a specific verb and object: 'Return an account's transactions within a lookback window, with a summary.' It also explicitly contrasts itself with evaluate_fraud_rules, making the tool's non-verdict role clear and distinguishing it from the most natural sibling.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It states when this tool is appropriate ('EVIDENCE, not a verdict') and explicitly routes to evaluate_fraud_rules when risk evaluation is needed. It also gives concrete lookback guidance: 7-30 days for velocity/takeover reviews and 90+ days for baselines or dormant accounts.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

lookup_device_historyLook up device historyA
Read-onlyIdempotent

Return every account and login event seen on a device.

Use this to pivot from one compromised account to the others driven from the
same machine - the standard way an account-takeover ring is mapped. Pay
attention to `distinct_accounts_with_events`, `failed_login_count`, and
`password_reset_count`: a device with many accounts, failed logins, and resets
is a takeover tool, not a shared family tablet.

Returns an error envelope for unknown or malformed device ids.
ParametersJSON Schema
NameRequiredDescriptionDefault
device_idYesDevice identifier such as 'DEV-2000'. Obtain it from the `device_id` field of a transaction, or from `evidence_device_ids` on a SHARED_DEVICE rule result. Device ids are NOT derived from account ids - 'DEV-1013' is not the device for 'ACC-1013'.

Output Schema

ParametersJSON Schema
NameRequiredDescription

No output parameters

TDQS

A4.3/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnlyHint=true and idempotentHint=true, covering safety. The description adds the error envelope for unknown or malformed IDs, which is behavioral information not present in annotations. It also hints at the nature of the output (account and login events) without contradicting any hints. This adds value beyond the structured metadata.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is about 80 words, covering purpose, usage, interpretation, and error behavior in four sentences. It is front-loaded with the purpose statement and avoids redundancy with schema fields. The structure is logical and every sentence earns its place, though it could be slightly shorter without losing value.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a single-parameter read-only tool with an output schema present, the description is nearly complete. It covers the main purpose, usage scenario, error handling, and even hints at result fields. It does not explicitly mention prerequisites or permissions, but the read-only annotation and error message cover most operational needs. Minor gaps like pagination or limits are not critical for this scope.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the baseline is 3. The description adds meaning by explaining where to obtain device_id (from transaction device_id field or evidence_device_ids) and explicitly clarifying that device IDs are not derived from account IDs, which prevents a common mistake. This goes beyond the schema's pattern and examples, making it a 4.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description opens with a specific verb+resource: 'Return every account and login event seen on a device.' It clearly states scope (all accounts and logins) and distinguishes its investigative role from siblings by naming the pivot use case (mapping an account-takeover ring). This is unambiguous and sets it apart from get_transactions, evaluate_fraud_rules, and flag_case without opening schemas.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It gives explicit context for when to use this tool: 'Use this to pivot from one compromised account to the others driven from the same machine.' It also provides interpretive guidance on which fields to focus on, implying when results are suspicious. It does not explicitly name alternatives or state when not to use it, but the context is strong enough for an agent to select it appropriately.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

  1. 4 tool updatesv0.1.0
    • First observedevaluate_fraud_rules
    • First observedflag_case
    • First observedget_transactions
    • First observedlookup_device_history

TDQS

A4.4/5.0

Scored across 4 tools

Disambiguation5/5

Each tool has a clearly distinct purpose: transaction retrieval, rule evaluation, device history lookup, and case flagging. The descriptions explicitly differentiate evidence from verdicts and mandate a workflow, leaving no ambiguity between tools.

Naming Consistency4/5

Tool names follow a clear verb_noun pattern (get_transactions, evaluate_fraud_rules, lookup_device_history, flag_case). The minor deviation is 'flag_case' which could be 'create_case' for consistency, but the pattern is consistent enough to be highly predictable.

Tool Count4/5

With 4 tools, the server is lean and focused on the core fraud investigation workflow: read, evaluate, pivot, and act. The count is at the lower end of the acceptable range but each tool is essential and earns its place, making the small set well-scoped.

Completeness4/5

The server covers the primary investigation lifecycle well: retrieving evidence, evaluating rules, pivoting on device intelligence, and recording outcomes. An obvious gap is the lack of a tool to comment on or update an existing case (e.g., edit_case), but the core workflow is complete and the missing piece is a minor extension.

Maintenance

ActivityMaintained
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers

  • F
    license
    Not graded
    quality
    D
    maintenance
    AI-powered fraud detection and investigation platform that exposes tools for querying, scoring, and investigating financial applications using LangGraph, MLflow, and SQLite in-memory.
    -
  • A
    license
    A
    quality
    B
    maintenance
    Investigate fraud directly from Claude, Cursor, or any MCP-compatible client. Analyze suspicious activity with clear, evidence-backed verdicts. Pivot from a single signup to every account sharing the same device, IP address, or email inbox. Check entities against a cross-operator abuse network, review linked accounts, and efficiently process your fraud review queue. Read-only by default, with no r
    10
    232 npm
    MIT