Skip to main content
Glama
J-X0
by J-X0

yarrowdalegraph

Graph-based fraud propagation scoring for Elmsworth Diagnostics (Project Yarrowdale). It runs as an MCP server that exposes one tool, score_graph, over JSON-RPC 2.0 on stdio.

What it does

Clinical-diagnostics fraud is rarely visible in a single entity. A colluding provider, the billing accounts it controls, the devices those accounts log in from, and downstream referrers form a connected structure. Yarrowdale models that structure as a graph and diffuses fraud risk across it.

The pipeline is:

  1. Build an entity graph: nodes are providers, patients, accounts, devices and claims; edges are observed relationships with a strength weight. Analysts can attach a seed in [0,1] to any node (a confirmed-fraud entity is 1).

  2. Obtain a per-node prior risk from a graph-ML model via the RiskPriorProvider interface.

  3. Propagate those priors across the graph with a personalised-PageRank-style diffusion until convergence, producing a final risk score per node.

The hard constraint: deterministic fallback

Scoring must keep working when the model is unavailable. That is not handled by a try/catch bolted on at the edge — it drives the architecture:

  • The model is reached only through RiskPriorProvider (src/providers/base.ts). Its sole job is to turn a graph into prior scores.

  • FraudScorer (src/scorer.ts) consults the provider behind three guards. Any of them routes to the deterministic path:

    • available() returns false (or throws),

    • priors() throws,

    • priors() returns a map that fails validation (wrong size / out of range).

  • The fallback (src/fallback.ts) computes priors from graph structure alone — analyst seeds plus weighted neighbour evidence. No network, no model, fully deterministic.

  • Both paths then run the same propagation algorithm, so a fallback score differs from a model score only in its priors, never in the method. That keeps results explainable to a reviewer.

  • Every degradation decision is recorded through the audit callback, and the result carries priorSource ('model' | 'fallback') and fallbackReason.

The test suite runs entirely offline against StubProvider (src/providers/stub.ts), which also has flags (down, throwOnPriors, returnIncomplete) to exercise each degradation branch.

Related MCP server: casuallayer-mcp

Layout

src/
  types.ts              domain model (nodes, edges, params, results)
  graph.ts              validation + row-normalised transition matrix
  propagation.ts        core diffusion algorithm
  fallback.ts           deterministic structural priors
  scorer.ts             orchestration + degradation + audit
  config.ts             environment-driven configuration
  logger.ts             structured JSON logging to stderr
  validation.ts         untrusted-input parsing + resource limits
  server.ts             MCP JSON-RPC request handling
  main.ts               stdio entry point
  index.ts              public exports
  providers/
    base.ts             RiskPriorProvider interface
    stub.ts             deterministic offline provider
    forced-fallback.ts  operator pin to the offline path
tests/                  node:test suites

Architecture

Request flow for a score_graph call:

stdin (JSON-RPC line)
  -> main.ts          framing, ordering, fatal-error guard
  -> server.ts        JSON-RPC dispatch, error codes, timing, audit->log
  -> validation.ts    untrusted JSON -> FraudGraph, resource limits
  -> scorer.ts        model-or-fallback decision (the degradation point)
       -> providers/  model priors, or ...
       -> fallback.ts deterministic structural priors
  -> propagation.ts   diffusion shared by both paths
  -> stdout (JSON-RPC line)   logs -> stderr

The decisions that had a real alternative are recorded in docs/adr/: the provider-interface fallback boundary, the shared propagation path, undirected iterate-don't-invert diffusion, the zero-dependency MCP transport, and fail-fast configuration.

Requirements

Node 22 or newer. Node runs the TypeScript sources directly, so there is no build step and no dist/.

Install and test

npm ci
npm test

npm test runs the built-in node --test runner over tests/**/*.test.ts.

Optional type check (requires devDependencies installed):

npm run typecheck

Running the server

npm start

This starts the MCP server on stdio. It reads one JSON-RPC message per line on stdin and writes one response per line on stdout; all logs go to stderr so they never corrupt the protocol stream. A minimal session:

printf '%s\n%s\n' \
  '{"jsonrpc":"2.0","id":1,"method":"initialize"}' \
  '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"score_graph","arguments":{"graph":{"nodes":[{"id":"p1","kind":"provider","seed":1},{"id":"acc","kind":"account"}],"edges":[{"source":"p1","target":"acc","weight":2}]}}}}' \
  | npm start --silent

The score_graph result is JSON text containing priorSource (model | fallback), fallbackReason, converged, iterations, elapsedMs, and a scores map.

Configuration

All configuration is read from the environment at startup. A malformed value is a fatal startup error (exit code 78), never silently ignored.

Variable

Default

Meaning

YARROWDALE_ALPHA

0.85

Diffusion mixing, strictly in (0,1)

YARROWDALE_TOLERANCE

1e-9

Convergence threshold

YARROWDALE_MAX_ITERATIONS

200

Iteration cap

YARROWDALE_MAX_NODES

50000

Reject graphs with more nodes

YARROWDALE_MAX_EDGES

500000

Reject graphs with more edges

YARROWDALE_FORCE_FALLBACK

false

Pin scoring to the offline path

YARROWDALE_LOG_LEVEL

info

debug | info | warn | error

YARROWDALE_FORCE_FALLBACK=true is the operator switch for a model incident: it routes every request through the deterministic fallback without a redeploy.

Library usage

import { FraudScorer, StubProvider } from './src/index.ts';

const scorer = new FraudScorer(new StubProvider(), {
  audit: (e) => console.error(`[audit] ${e.event}: ${e.detail}`),
});

const result = await scorer.score({
  nodes: [
    { id: 'p1', kind: 'provider', seed: 1 }, // confirmed fraud
    { id: 'acc', kind: 'account' },
    { id: 'dev', kind: 'device' },
  ],
  edges: [
    { source: 'p1', target: 'acc', weight: 2, relation: 'billing' },
    { source: 'acc', target: 'dev', weight: 1, relation: 'login' },
  ],
});

console.log(result.priorSource);   // 'model' or 'fallback'
console.log(result.scores.get('acc'));

Algorithm notes

Propagation solves the fixed point

s = alpha * W s + (1 - alpha) * p

where W is the row-normalised undirected adjacency and p the prior vector. For alpha in (0,1) the iteration is a contraction and converges to the unique solution s = (1 - alpha)(I - alpha W)^{-1} p. Edges are treated as undirected because collusion evidence is symmetric. We iterate rather than invert: the graphs are sparse and a few hundred sweeps are cheaper than an O(n^3) solve. Default alpha = 0.85, tolerance 1e-9, cap 200 iterations.

Known limitations

  • The learned RiskPriorProvider (graph neural network) is not yet shipped; only the interface and the deterministic stub are. main.ts wires in the stub and will select the model provider from config once it exists. See the TODO in src/providers/base.ts.

  • The stdio transport handles one message per line and serialises request handling. That is enough for a single MCP client; JSON-RPC batch arrays and concurrent in-flight scoring are not implemented.

  • Scoring is synchronous and in-memory. A graph near the configured node/edge limits is scored in one event-loop-blocking pass; there is no chunking or backpressure. The limits exist to keep that pass bounded, not to stream.

  • Propagation treats every relationship type the same once weighted. Distinct edge semantics (a shared device vs. a referral) are captured only through the caller-supplied weight, not through per-relation propagation rules.


Elmsworth Diagnostics is an illustrative client; this repository is a self-directed reference implementation built to work end to end.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections. Dates show when Glama detected each change.

No tool schema history has been recorded yet.

Maintenance

ActivityInactive
ResponsivenessNo issues

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Connectors

Related MCP Servers

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/J-X0/elmsworth-diagnostics-graph-ml-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server