diff --git a/apps/playground/app/docs/layout.tsx b/apps/playground/app/docs/layout.tsx new file mode 100644 index 0000000..266c6bb --- /dev/null +++ b/apps/playground/app/docs/layout.tsx @@ -0,0 +1,15 @@ +import type { Metadata } from "next"; + +export const metadata: Metadata = { + title: "Documentation | Intuition MCP", + description: + "Complete guide to the Intuition MCP monorepo — installation, Claude Desktop integration, tool reference, and algorithm documentation for the Intuition MCP and Trust Score MCP servers.", +}; + +export default function DocsLayout({ + children, +}: { + children: React.ReactNode; +}) { + return <>{children}; +} diff --git a/apps/playground/app/docs/page.tsx b/apps/playground/app/docs/page.tsx index 75ae3fe..a291ce3 100644 --- a/apps/playground/app/docs/page.tsx +++ b/apps/playground/app/docs/page.tsx @@ -1,5 +1,7 @@ +"use client"; + +import { useState, useEffect, useCallback } from "react"; import Link from "next/link"; -import type { Metadata } from "next"; import { getMcpRegistry } from "@/lib/mcp-registry"; import { Card, @@ -10,17 +12,110 @@ import { } from "@/components/ui/card"; // --------------------------------------------------------------------------- -// Metadata +// Navigation structure +// --------------------------------------------------------------------------- + +interface NavItem { + id: string; + label: string; + children?: NavItem[]; +} + +const NAV_ITEMS: NavItem[] = [ + { id: "overview", label: "Overview" }, + { id: "installation", label: "Installation" }, + { id: "monorepo-structure", label: "Monorepo Structure" }, + { id: "claude-desktop", label: "Claude Desktop Integration" }, + { id: "tools-reference", label: "Tools Reference" }, + { id: "http-api", label: "HTTP API Endpoints" }, + { + id: "algorithms", + label: "Algorithm Documentation", + children: [ + { id: "algo-eigentrust", label: "EigenTrust" }, + { id: "algo-agentrank", label: "AgentRank" }, + { id: "algo-composite", label: "Composite Scoring" }, + { id: "algo-transitive", label: "Multi-Hop Transitive Trust" }, + { id: "algo-sybil", label: "Sybil Resistance" }, + { id: "algo-indexer", label: "Graph Indexer" }, + { id: "algo-predicates", label: "Predicate Weights" }, + ], + }, + { id: "resources", label: "Resources" }, +]; + +const ALL_SECTION_IDS = NAV_ITEMS.flatMap((item) => + item.children ? [item.id, ...item.children.map((c) => c.id)] : [item.id] +); + +const ALGORITHMS_URL = + "https://github.com/intuition-box/mcp/blob/main/packages/mcp-trust/ALGORITHMS.md"; + +// --------------------------------------------------------------------------- +// Algorithm summaries // --------------------------------------------------------------------------- -export const metadata: Metadata = { - title: "Documentation | Intuition MCP", - description: - "Complete guide to the Intuition MCP monorepo — installation, Claude Desktop integration, and tool reference for both the Intuition MCP and Trust Score MCP servers.", -}; +interface AlgorithmSummary { + id: string; + name: string; + anchor: string; + description: string; +} + +const ALGORITHM_SUMMARIES: AlgorithmSummary[] = [ + { + id: "algo-eigentrust", + name: "EigenTrust", + anchor: "#1-eigentrust", + description: + "Iterative power-iteration algorithm computing global trust scores by propagating normalized attestation weights across the graph until convergence. Based on the Kamvar, Schlosser, Garcia-Molina (2003) paper for sybil-resistant reputation.", + }, + { + id: "algo-agentrank", + name: "AgentRank", + anchor: "#2-agentrank", + description: + "PageRank variant with stake-weighted edges for influence ranking. Produces per-node influence scores alongside network-level metrics including Gini coefficient and Shannon entropy for inequality and diversity measurement.", + }, + { + id: "algo-composite", + name: "Composite Scoring", + anchor: "#3-composite-scoring-engine", + description: + "Weighted combination of EigenTrust (0.4), AgentRank (0.3), and Transitive Trust (0.3) into a normalized 0\u2013100 score with confidence indicators. Supports per-query weight overrides and batch computation.", + }, + { + id: "algo-transitive", + name: "Multi-Hop Transitive Trust", + anchor: "#4-multi-hop-transitive-trust", + description: + "Personalized trust propagation through multi-hop paths with configurable per-hop decay, stake weighting, and predicate-specific multipliers. Supports both targeted pairwise queries and full outgoing network scans.", + }, + { + id: "algo-sybil", + name: "Sybil Resistance", + anchor: "#5-sybil-resistance", + description: + "Simulation-based detection that injects synthetic sybil clusters into the graph, measures their impact on trust scores, then cleans up. Produces a resistance score quantifying how well the network withstands coordinated manipulation.", + }, + { + id: "algo-indexer", + name: "Graph Indexer", + anchor: "#6-graph-indexer", + description: + "Pipeline syncing Intuition attestation data from the GraphQL API into Neo4j using cursor-based pagination and batch MERGE operations. Tracks sync health metrics including duration, error counts, and graph size.", + }, + { + id: "algo-predicates", + name: "Predicate Weights", + anchor: "#7-predicate-weights", + description: + "Configurable weight system where each attestation predicate (trusts, follows, is-qualified) carries a numeric multiplier scaling its contribution to trust calculations. Supports per-query overrides and custom predicate registration.", + }, +]; // --------------------------------------------------------------------------- -// Reusable presentational helpers +// Presentational helpers // --------------------------------------------------------------------------- function SectionHeading({ children }: { children: React.ReactNode }) { @@ -43,179 +138,383 @@ function InlineCode({ children }: { children: React.ReactNode }) { ); } +// --------------------------------------------------------------------------- +// Sidebar +// --------------------------------------------------------------------------- + +function Sidebar({ + activeSection, + onNavigate, + onBackToTop, +}: { + activeSection: string; + onNavigate: (id: string) => void; + onBackToTop: () => void; +}) { + const activeParent = NAV_ITEMS.find( + (item) => + item.id === activeSection || + item.children?.some((c) => c.id === activeSection), + )?.id; + + return ( + + ); +} + // --------------------------------------------------------------------------- // Page // --------------------------------------------------------------------------- export default function DocsPage() { + const [activeSection, setActiveSection] = useState("overview"); const mcps = getMcpRegistry(); const totalTools = mcps.reduce((sum, m) => sum + m.tools.length, 0); + useEffect(() => { + document.title = "Documentation | Intuition MCP"; + }, []); + + // Track which section is visible via IntersectionObserver + useEffect(() => { + const visibleEntries = new Map(); + + const observer = new IntersectionObserver( + (entries) => { + for (const entry of entries) { + if (entry.isIntersecting) { + visibleEntries.set(entry.target.id, entry); + } else { + visibleEntries.delete(entry.target.id); + } + } + + if (visibleEntries.size > 0) { + let topmost: string | null = null; + let topY = Infinity; + for (const [id, entry] of visibleEntries) { + if (entry.boundingClientRect.top < topY) { + topY = entry.boundingClientRect.top; + topmost = id; + } + } + if (topmost) setActiveSection(topmost); + } + }, + { rootMargin: "-80px 0px -40% 0px", threshold: 0 }, + ); + + for (const id of ALL_SECTION_IDS) { + const el = document.getElementById(id); + if (el) observer.observe(el); + } + + return () => observer.disconnect(); + }, []); + + const scrollToSection = useCallback((id: string) => { + const el = document.getElementById(id); + if (el) el.scrollIntoView({ behavior: "smooth", block: "start" }); + }, []); + + const scrollToTop = useCallback(() => { + window.scrollTo({ top: 0, behavior: "smooth" }); + }, []); + return (
-
- {/* Page header */} -
-

- Documentation -

-

- Complete guide to the Intuition MCP monorepo -

-
+ -
- {/* ---------------------------------------------------------------- - Introduction - ---------------------------------------------------------------- */} -
- Introduction -

- The Intuition MCP monorepo ships{" "} - {mcps.length} MCP servers exposing{" "} - {totalTools} tools that let AI assistants like - Claude query the Intuition knowledge graph, compute trust scores, - and verify on-chain reputation data. + {/* Main content */} +

+
+ {/* Page header */} +
+

+ Documentation +

+

+ Complete guide to the Intuition MCP monorepo

+
-
- {mcps.map((mcp) => ( - - - {mcp.name} - - {mcp.tools.length} tools ·{" "} - - View playground - - - - - {mcp.description} - - - ))} -
-
- - {/* ---------------------------------------------------------------- - Installation - ---------------------------------------------------------------- */} -
- Installation - - -
-

- 1. Clone the monorepo -

- +
+ {/* --------------------------------------------------------------- + Overview + --------------------------------------------------------------- */} +
+ Overview +

+ The Intuition MCP monorepo ships{" "} + {mcps.length} MCP servers exposing{" "} + {totalTools} tools that let AI assistants like + Claude query the Intuition knowledge graph, compute trust scores, + and verify on-chain reputation data. +

+ +
+ {mcps.map((mcp) => ( + + + {mcp.name} + + {mcp.tools.length} tools ·{" "} + + View playground + + + + + {mcp.description} + + + ))} +
+
+ + {/* --------------------------------------------------------------- + Installation + --------------------------------------------------------------- */} +
+ Installation + + +
+

+ 1. Clone the monorepo +

+ {`git clone https://github.com/intuition-box/mcp.git cd mcp`} - -
+ +
-
-

- 2. Install dependencies -

-

- Run npm install at the repository - root. Workspaces are configured so every package gets its - dependencies resolved in one pass. -

- npm install -
+
+

+ 2. Install dependencies +

+

+ Run npm install at the repository + root. Workspaces are configured so every package gets its + dependencies resolved in one pass. +

+ npm install +
-
-

- 3. Configure environment -

- +
+

+ 3. Configure environment +

+ {`# apps/playground/.env.local NEXT_PUBLIC_INTUITION_GRAPH_URL=https://graph.intuition.systems/graphql`} - -
+
+
-
-

- 4. Start the playground -

- +
+

+ 4. Start the playground +

+ {`cd apps/playground npm run dev`} - -

- The playground runs at{" "} - http://localhost:3000 and gives you - an interactive UI to explore both MCP servers. -

-
- - -
- - {/* ---------------------------------------------------------------- - Monorepo Structure - ---------------------------------------------------------------- */} -
- Monorepo Structure - - - + +

+ The playground runs at{" "} + http://localhost:3000 and gives you + an interactive UI to explore both MCP servers. +

+
+ + + + + {/* --------------------------------------------------------------- + Monorepo Structure + --------------------------------------------------------------- */} +
+ Monorepo Structure + + + {`mcp/ -├── packages/ -│ ├── mcp-general/ # Intuition MCP server (knowledge graph) -│ └── mcp-trust/ # Trust Score MCP server (EigenTrust, AgentRank) -├── apps/ -│ └── playground/ # Next.js interactive playground & docs -├── package.json # Workspace root -└── README.md`} - - - -
- - {/* ---------------------------------------------------------------- - Claude Desktop Integration - ---------------------------------------------------------------- */} -
- Claude Desktop Integration - - - - Configure both MCP servers - - - Add the following to your Claude Desktop config file to - register both servers. - - - -
-

- Config file location -

-
    -
  • - macOS:{" "} - - ~/Library/Application Support/Claude/claude_desktop_config.json - -
  • -
  • - Windows:{" "} - - %APPDATA%\Claude\claude_desktop_config.json - -
  • -
-
+\u251c\u2500\u2500 packages/ +\u2502 \u251c\u2500\u2500 mcp-general/ # Intuition MCP server (knowledge graph) +\u2502 \u2514\u2500\u2500 mcp-trust/ # Trust Score MCP server (EigenTrust, AgentRank) +\u251c\u2500\u2500 apps/ +\u2502 \u2514\u2500\u2500 playground/ # Next.js interactive playground & docs +\u251c\u2500\u2500 package.json # Workspace root +\u2514\u2500\u2500 README.md`} + +
+
+
+ + {/* --------------------------------------------------------------- + Claude Desktop Integration + --------------------------------------------------------------- */} +
+ Claude Desktop Integration + + + + Configure both MCP servers + + + Add the following to your Claude Desktop config file to + register both servers. + + + +
+

+ Config file location +

+
    +
  • + macOS:{" "} + + ~/Library/Application Support/Claude/claude_desktop_config.json + +
  • +
  • + Windows:{" "} + + %APPDATA%\Claude\claude_desktop_config.json + +
  • +
+
- + {`{ "mcpServers": { "intuition": { @@ -238,97 +537,97 @@ npm run dev`} } } }`} - - -

- Replace /absolute/path/to/mcp with - the actual path where you cloned the repository. Restart - Claude Desktop after saving changes. -

-
-
-
- - {/* ---------------------------------------------------------------- - Tools Reference — generated from the registry - ---------------------------------------------------------------- */} -
- Tools Reference - - {mcps.map((mcp) => ( -
-

- {mcp.name} - - {mcp.tools.length} tools - -

- -
- {mcp.tools.map((tool) => ( - - - - - {tool.name} - - - - - {tool.description} - - - ))} -
-
- ))} -
- - {/* ---------------------------------------------------------------- - HTTP API Endpoints - ---------------------------------------------------------------- */} -
- HTTP API Endpoints -

- When the playground dev server is running you can also call the - tools via HTTP. -

- -
- - - - GET /api/trust-score - - - - -{`curl "http://localhost:3000/api/trust-score?address=0x..."`} + +

+ Replace /absolute/path/to/mcp with + the actual path where you cloned the repository. Restart + Claude Desktop after saving changes. +

+
- - - - GET /api/attestations - - - - + {/* --------------------------------------------------------------- + Tools Reference + --------------------------------------------------------------- */} +
+ Tools Reference + + {mcps.map((mcp) => ( +
+

+ {mcp.name} + + {mcp.tools.length} tools + +

+ +
+ {mcp.tools.map((tool) => ( + + + + + {tool.name} + + + + + {tool.description} + + + ))} +
+
+ ))} +
+ + {/* --------------------------------------------------------------- + HTTP API Endpoints + --------------------------------------------------------------- */} +
+ HTTP API Endpoints +

+ When the playground dev server is running you can also call the + tools via HTTP. +

+ +
+ + + + GET /api/trust-score + + + + +{`curl "http://localhost:3000/api/trust-score?address=0x..."`} + + + + + + + + GET /api/attestations + + + + {`curl "http://localhost:3000/api/attestations?subject=0x...&limit=50"`} - - - + + + - - - - POST /api/mcp - - - - + + + + POST /api/mcp + + + + {`curl -X POST http://localhost:3000/api/mcp \\ -H "Content-Type: application/json" \\ -d '{ @@ -338,103 +637,153 @@ npm run dev`} "claim": "expert-in-defi" } }'`} - - - -
-
- - {/* ---------------------------------------------------------------- - Resources - ---------------------------------------------------------------- */} -
- Resources -
- - - - Intuition Systems - - - Learn about the Intuition protocol - - - - - intuition.systems → - - - + + + +
+
- - - - Model Context Protocol - - - Official MCP specification - - - - - modelcontextprotocol.io → - - - + {/* --------------------------------------------------------------- + Algorithm Documentation + --------------------------------------------------------------- */} +
+ Algorithm Documentation +

+ The Trust Score MCP server implements seven core algorithm areas + for computing reputation, influence, and sybil resistance over + the Intuition attestation graph. Each algorithm is documented in + detail in the{" "} + + full technical specification + + . +

- - - - GitHub Repository - - - View source code & contribute - - - - + {ALGORITHM_SUMMARIES.map((algo) => ( + - github.com/intuition-box/mcp → - - - + + {algo.name} + + +

+ {algo.description} +

+ + Read full specification → + +
+ + ))} +
+ - - - - MCP Directory - - - Explore & try all available servers - - - - - Browse MCP Directory → - - - -
- + {/* --------------------------------------------------------------- + Resources + --------------------------------------------------------------- */} +
+ Resources +
+ + + + Intuition Systems + + + Learn about the Intuition protocol + + + + + intuition.systems → + + + + + + + + Model Context Protocol + + + Official MCP specification + + + + + modelcontextprotocol.io → + + + + + + + + GitHub Repository + + + View source code & contribute + + + + + github.com/intuition-box/mcp → + + + + + + + + MCP Directory + + + Explore & try all available servers + + + + + Browse MCP Directory → + + + +
+
+ - + ); } diff --git a/packages/mcp-trust/ALGORITHMS.md b/packages/mcp-trust/ALGORITHMS.md new file mode 100644 index 0000000..fdef55f --- /dev/null +++ b/packages/mcp-trust/ALGORITHMS.md @@ -0,0 +1,666 @@ +# Trust Scoring Algorithms -- Technical Specification + +Intuition Protocol's trust infrastructure for computing reputation, influence, +and sybil resistance over on-chain attestation graphs. These algorithms power +the MCP tools that AI agents and developers use to query, score, and reason +about trust relationships in the Intuition ecosystem. + +**Quick Jump:** +[EigenTrust](#1-eigentrust) | [AgentRank](#2-agentrank) | [Composite Scoring](#3-composite-scoring-engine) | [Transitive Trust](#4-multi-hop-transitive-trust) | [Sybil Resistance](#5-sybil-resistance) | [Graph Indexer](#6-graph-indexer) | [Predicate Weights](#7-predicate-weights) + +--- + +## Table of Contents + +- [MCP Tools Quick Reference](#mcp-tools-quick-reference) +- [1. EigenTrust](#1-eigentrust) + - [Overview](#overview) + - [Input](#input) + - [Algorithm](#algorithm) + - [Output](#output) + - [Key Parameters](#key-parameters) + - [Complexity](#complexity) +- [2. AgentRank](#2-agentrank) + - [Overview](#overview-1) + - [Algorithm](#algorithm-1) + - [Input](#input-1) + - [Output](#output-1) + - [Influence Metrics](#influence-metrics) + - [Key Parameters](#key-parameters-1) +- [3. Composite Scoring Engine](#3-composite-scoring-engine) + - [Overview](#overview-2) + - [Default Weights](#default-weights) + - [Weight Redistribution](#weight-redistribution) + - [Normalization](#normalization) + - [Confidence Metric](#confidence-metric) + - [Caching](#caching) + - [Batch API](#batch-api) + - [Output](#output-2) +- [4. Multi-Hop Transitive Trust](#4-multi-hop-transitive-trust) + - [Overview](#overview-3) + - [Path Traversal](#path-traversal) + - [Per-Hop Trust Formula](#per-hop-trust-formula) + - [Default Parameters](#default-parameters) + - [Path Aggregation](#path-aggregation) + - [Personalized PageRank](#personalized-pagerank) + - [Direct Trust Shortcut](#direct-trust-shortcut) + - [Available Operations](#available-operations) +- [5. Sybil Resistance](#5-sybil-resistance) + - [Overview](#overview-4) + - [Simulation Process](#simulation-process) + - [Resistance Score](#resistance-score) + - [Default Configuration](#default-configuration) + - [Output](#output-3) +- [6. Graph Indexer](#6-graph-indexer) + - [Overview](#overview-5) + - [Sync Pipeline](#sync-pipeline) + - [Pagination Strategy](#pagination-strategy) + - [Transform Pipeline](#transform-pipeline) + - [Neo4j Schema](#neo4j-schema) + - [Sync Health Tracking](#sync-health-tracking) + - [Auto-Sync Cron Job](#auto-sync-cron-job) + - [Sync Result](#sync-result) +- [7. Predicate Weights](#7-predicate-weights) + - [Predicate Registry](#predicate-registry) + - [Weight Resolution](#weight-resolution) + - [Custom Overrides](#custom-overrides) + - [Design Rationale](#design-rationale) + +--- + +## MCP Tools Quick Reference + +| Tool | Description | Required Params | Optional Params | +|------|-------------|-----------------|-----------------| +| `get_graph_stats` | Graph statistics (node/edge counts, labels) | -- | -- | +| `get_sync_health` | Sync health metrics (status, duration, errors) | -- | -- | +| `get_sync_status` | Auto-sync cron job status | -- | -- | +| `get_predicate_config` | Predicate registry with term IDs and weights | -- | -- | +| `get_lens_registry` | Available trust lenses (filtered graph views) | -- | -- | +| `compute_eigentrust` | Global EigenTrust scores for all addresses | -- | -- | +| `compute_agentrank` | Global AgentRank influence scores | -- | `topN` | +| `compute_composite_score` | Composite score combining all trust signals | `address` | `fromAddress`, weight overrides | +| `compute_personalized_trust` | Personalized trust between two addresses | `fromAddress`, `toAddress` | `maxHops`, `minStake` | +| `find_trust_paths` | All trust paths between two addresses | `fromAddress`, `toAddress` | `maxHops`, `predicateWeights` | +| `simulate_sybil_attack` | Sybil resistance simulation and scoring | -- | `numSybilNodes`, `targetAddress` | + +--- + +## 1. EigenTrust + +### Overview + +Implementation of the EigenTrust algorithm (Kamvar, Schlosser, Garcia-Molina, 2003) +for sybil-resistant global trust computation. Computes the principal eigenvector of +a normalized trust matrix through iterative power iteration. + +### Input + +The attestation graph stored in Neo4j: +- **Nodes**: Address entities with id, label, total stake, and attestation count +- **Edges**: ATTESTS relationships with stake amount, predicate type, triple ID, and timestamp + +All addresses and attestation edges are fetched in a single pass before +computation begins. + +### Algorithm + +1. **Initialize** -- uniform trust distribution: `t[i] = 1/n` for all `n` addresses. +2. **Build transition matrix** -- for each edge from `j` to `i`: + - Edge weight = `stakeAmount * predicateWeight` + - Row-normalize so each node's outgoing weights sum to 1 (stochastic matrix). + - Nodes with no outgoing edges are dangling nodes; their trust is + distributed uniformly during iteration. +3. **Iterate** -- single iteration formula: + +``` +t'[i] = (1 - alpha) * p[i] + alpha * SUM_j( C[j][i] * t[j] ) +``` + +Where: +- `alpha = 1 - pretrustWeight` (default `1 - 0.1 = 0.9`) +- `p[i]` is the pretrust vector (uniform distribution in the base implementation) +- `C[j][i]` is the normalized transition matrix entry +- Dangling node contribution is added uniformly: `danglingSum / n` + +4. **Normalize** scores after each iteration to maintain a proper distribution. +5. **Check convergence** -- stop when `max(|t'[i] - t[i]|) < threshold` or + `iterations >= maxIterations`. + +### Output + +- `scores` -- sorted descending by score, each entry containing: + address, score (0-1), confidence (0-1), path count, and source addresses +- `iterations` -- number of iterations until convergence +- `converged` -- whether the algorithm converged within the iteration limit +- `computationTimeMs` -- wall-clock computation time + +Confidence is calculated on a logarithmic scale based on incoming attestation count: +`confidence = min(1, log2(incomingCount + 1) / log2(n))`. + +### Key Parameters + +| Parameter | Default | Description | +|-----------|---------|-------------| +| `maxIterations` | 100 | Upper bound on power iteration rounds | +| `convergenceThreshold` | 0.0001 | Max absolute delta to declare convergence | +| `decayFactor` | 0.6 | Used by pathfinding, not directly in EigenTrust iteration | +| `pretrustWeight` | 0.1 | Controls pretrust vector influence (higher = more uniform) | + +### Complexity + +- **Time**: O(iterations * edges) per iteration -- matrix-vector multiply +- **Space**: O(nodes + edges) for the transition matrix and score vectors +- Typical convergence within 15-30 iterations on real attestation graphs + +[Back to top](#table-of-contents) + +--- + +## 2. AgentRank + +### Overview + +PageRank variant that computes influence scores based on graph structure and +stake weights. Unlike EigenTrust, AgentRank has no pre-trusted peer requirement +and is purely structure-based. + +### Algorithm + +Standard PageRank with weighted edges: + +``` +rank'[i] = (1 - d) / n + d * SUM_j( rank[j] * w[j->i] / outW[j] ) +``` + +Where: +- `d` is the damping factor (default 0.85) +- `w[j->i]` is the edge weight from `j` to `i` (stakeAmount * predicateWeight) +- `outW[j]` is the sum of all outgoing edge weights from `j` +- `n` is the total number of nodes +- Dangling nodes (no outgoing edges) distribute rank uniformly + +### Input + +Same attestation graph as EigenTrust. The weighted adjacency is constructed as: +- `inLinks` -- for each node, a map of incoming neighbors to their normalized edge weights +- `outWeights` -- total outgoing weight per node + +An optional `stakeWeighted` flag (default `true`) controls whether stake amounts +are factored into edge weights. When false, only predicate weights are used. + +### Output + +- `ranks` -- map of address to influence score +- `iterations`, `converged`, `computationTimeMs` +- `topAgents` -- top N agents with in-degree and out-degree +- `influenceMetrics` -- network-level distribution statistics + +### Influence Metrics + +Computed over the final rank distribution: + +| Metric | Formula | Meaning | +|--------|---------|---------| +| Gini coefficient | `(2 * weightedSum) / (n * totalRank) - (n+1)/n` | Rank inequality (0 = equal, 1 = concentrated) | +| Shannon entropy | `-SUM(p_i * log2(p_i))` where `p_i = rank_i / totalRank` | Rank distribution spread | +| Top 10% share | Sum of top-decile ranks / total rank | Concentration in top agents | +| Median rank | Middle value in sorted rank list | Typical agent influence | + +### Key Parameters + +| Parameter | Default | Description | +|-----------|---------|-------------| +| `dampingFactor` | 0.85 | Probability of following a link (vs random teleport) | +| `maxIterations` | 100 | Upper bound on iteration rounds | +| `convergenceThreshold` | 1e-6 | Max absolute delta for convergence | +| `minRank` | 0.001 | Floor preventing zero-rank nodes | +| `stakeWeighted` | true | Whether to incorporate stake amounts | + +[Back to top](#table-of-contents) + +--- + +## 3. Composite Scoring Engine + +### Overview + +The primary consumer-facing scoring module. Combines three independent signals +into a single 0-100 score per address: + +1. **EigenTrust** -- global sybil-resistant trust +2. **AgentRank** -- structural influence +3. **Transitive Trust** -- personalized path-based trust (optional) + +### Default Weights + +| Component | Weight | When | +|-----------|--------|------| +| EigenTrust | 0.4 | Always | +| AgentRank | 0.3 | Always | +| Transitive Trust | 0.3 | Only when `fromAddress` is provided | + +Weights are configurable per-call via `eigentrustWeight`, `agentRankWeight`, +and `transitiveTrustWeight` parameters on the `compute_composite_score` tool. + +### Weight Redistribution + +When `fromAddress` is omitted, transitive trust cannot be computed. Its weight +is redistributed proportionally to the remaining components: + +``` +redistributedEigentrust = eigentrust / (eigentrust + agentrank) +redistributedAgentrank = agentrank / (eigentrust + agentrank) +transitiveTrust = 0 +``` + +Edge case: if both `eigentrust + agentrank <= 0`, falls back to 0.5 / 0.5. + +### Normalization + +Each component is normalized against the network maximum before weighting: + +``` +etNormalized = etScore / maxEigentrustScore (0-1) +arNormalized = arScore / maxAgentrankScore (0-1) +ttScore = raw transitive trust score (already 0-1) + +rawComposite = w_et * etNormalized + w_ar * arNormalized + w_tt * ttScore +compositeScore = clamp(rawComposite * 100, 0, 100) +``` + +### Confidence Metric + +Measures data availability and signal strength on a 0-1 scale: + +``` +availabilityFactor = signalsPresent / totalExpectedSignals +strengthFactor = mean( min(1, score * 1000) for each nonzero signal ) +confidence = 0.6 * availabilityFactor + 0.4 * strengthFactor +``` + +EigenTrust and AgentRank raw scores are scaled by 1000x before clamping because +they are typically very small (e.g. 0.003). Transitive trust scores are used +directly since they are already on a 0-1 scale. + +### Caching + +Global computations (EigenTrust + AgentRank) are cached with a configurable +TTL (default 5 minutes). Per-address lookups against cached data are O(1). +Cache is shared across calls within the same process. + +| Setting | Default | Description | +|---------|---------|-------------| +| `cacheResults` | `true` | Whether to cache global algorithm results | +| `cacheTTL` | 300,000 ms | Time-to-live for cached results | + +### Batch API + +The batch endpoint runs EigenTrust and AgentRank once, then resolves all +requested addresses in O(1) per address. When `fromAddress` is provided, +transitive trust is computed sequentially for each target to avoid overloading +the database with concurrent path queries. + +Self-loop optimization: when `fromAddress` equals the target address, the +engine returns `{score: 1, paths: 0}` without running a path query. + +### Output + +``` +{ + address: string, + compositeScore: number, // 0-100 + confidence: number, // 0-1 + breakdown: { + eigentrust: { score, normalizedScore, rank }, + agentrank: { score, normalizedScore, rank }, + transitiveTrust: { score, paths, maxHops }, + }, + metadata: { + totalNodes: number, + computeTimeMs: number, + dataFreshness: Date, + } +} +``` + +[Back to top](#table-of-contents) + +--- + +## 4. Multi-Hop Transitive Trust + +### Overview + +Computes personalized trust between two specific addresses by traversing +attestation paths in the graph. This is the only algorithm that takes a +source/target pair rather than computing over the entire network. + +### Path Traversal + +Uses variable-length pattern matching to discover all attestation paths +between a source and target address: + +```cypher +MATCH path = (source:Address {id: $from})-[:ATTESTS*1..]->(target:Address {id: $to}) +``` + +The hop range is clamped to `[1, 10]` regardless of input. Results are limited +to 1,000 paths maximum. + +### Per-Hop Trust Formula + +For each hop `i` in a path: + +``` +hopTrust[i] = normalizeStake(stakeAmount) * predicateWeight * decayFactor^i +``` + +Where: +- `normalizeStake(stake) = log(stake + 1) / log(1e18)` -- logarithmic normalization + that maps raw stake (in wei) to [0, 1] with diminishing returns +- `predicateWeight` is looked up from the predicate registry (custom overrides supported) +- `decayFactor^i` reduces trust exponentially with distance + +Total path trust is the product of all hop trusts, clamped to [0, 1]: + +``` +pathTrust = PRODUCT( hopTrust[i] for i in 0..hops-1 ) +``` + +### Default Parameters + +| Parameter | Default | Description | +|-----------|---------|-------------| +| `decayFactor` | 0.6 | Trust retained per hop (0.6^3 = 21.6% at 3 hops) | +| `maxHops` | 3 | Default traversal depth (configurable 1-10) | +| `minStake` | 0 | Minimum stake threshold for path filtering | + +### Path Aggregation + +Multiple paths are combined into a single score using weighted averaging. +Shorter paths receive higher weight: + +``` +weight[path] = 1.5 ^ (maxHops - pathLength) +``` + +For example with `maxHops = 3`: +- 1-hop path: weight = 1.5^2 = 2.25 +- 2-hop path: weight = 1.5^1 = 1.50 +- 3-hop path: weight = 1.5^0 = 1.00 + +Confidence is computed from two factors (60/40 blend): +- **Path count factor**: `min(1, log2(pathCount + 1) / log2(threshold + 1))` + where `threshold = 5` +- **Consistency factor**: `1 - min(1, standardDeviation)` of path trust values + +### Personalized PageRank + +A full personalized PageRank can be run from a source address, with +teleportation probability back to the source. This scores all reachable +addresses relative to the source. + +| Parameter | Value | +|-----------|-------| +| Damping factor | 0.85 | +| Max iterations | 50 | +| Convergence threshold | 0.0001 | + +### Direct Trust Shortcut + +For single-hop queries, a fast path checks for a direct attestation and +uses sigmoid normalization: + +``` +normalizedStake = 2 / (1 + e^(-stake / 1e15)) - 1 +``` + +### Available Operations + +| Operation | Purpose | +|-----------|---------| +| Find trust paths | All paths between two addresses, ranked by strength | +| Find outgoing paths | All reachable addresses from a source | +| Calculate path trust | Trust value for a single path | +| Normalize stake | Log-scale stake normalization to [0, 1] | +| Personalized trust | Aggregated trust from one address to another | +| Personalized network | Personalized PageRank from a source to all reachable nodes | +| Trust with decay | 70% strongest path + 30% aggregate blend | +| Direct trust | Single-hop attestation check | + +[Back to top](#table-of-contents) + +--- + +## 5. Sybil Resistance + +### Overview + +The sybil simulation measures how well EigenTrust and AgentRank resist +coordinated fake-identity attacks. It runs a controlled before/after +experiment on the live graph with guaranteed cleanup. + +### Simulation Process + +1. **Baseline capture** -- run EigenTrust and AgentRank on the clean graph. +2. **Inject sybil nodes** -- create `numSybilNodes` fake addresses with + deterministic, identifiable IDs. +3. **Create collusion edges** -- add random attestation relationships between + sybil pairs. Each edge carries a configurable stake (default 0.01 ETH). + Self-loops and duplicates are avoided. +4. **Attack capture** -- rerun EigenTrust and AgentRank on the contaminated graph. +5. **Cleanup** -- remove all sybil nodes and their relationships (guaranteed + via try/finally). +6. **Calculate resistance** -- compare baseline and attack scores for + legitimate addresses only. + +### Resistance Score + +``` +avgAbsoluteChange = mean( |attacked[i] - baseline[i]| ) for legitimate addresses only +avgBaselineScore = mean( baseline[i] ) for legitimate addresses only +resistance = clamp(1 - avgAbsoluteChange / avgBaselineScore, 0, 1) +``` + +| Score | Meaning | +|-------|---------| +| 1.0 | No impact -- scores unchanged | +| 0.7 - 0.9 | Good resistance -- minor perturbation | +| 0.3 - 0.6 | Moderate vulnerability | +| 0.0 | Complete compromise -- scores fully manipulated | + +The result also includes `maxChange` (worst single-address deviation) and +`avgChange` (mean absolute change across all legitimate addresses). + +### Default Configuration + +| Parameter | Default | Description | +|-----------|---------|-------------| +| `numSybilNodes` | 50 | Fake identities to inject | +| `numCollusionEdges` | 200 | Random inter-sybil attestations | +| `sybilStake` | 0.01 | ETH stake per collusion edge | +| `targetAddress` | -- | Optional specific address to attempt boosting | + +### Output + +- `resistanceScore` (0-1, higher = more resistant) +- `maxChange`, `avgChange` -- impact metrics +- `baselineScores`, `attackedScores` -- full score maps for analysis +- `sybilCount`, `collusionEdges` -- simulation parameters used + +[Back to top](#table-of-contents) + +--- + +## 6. Graph Indexer + +### Overview + +The indexer syncs attestation data from the Intuition GraphQL API into the +Neo4j graph database. It handles pagination, batched writes, error recovery, +and sync health tracking. + +### Sync Pipeline + +``` + 1. Load configuration (Neo4j URI, GraphQL endpoint, batch size) + 2. Initialize Neo4j driver + 3. Verify connection + 4. Create schema (constraints and indexes, idempotent) + 5. Initialize GraphQL client + 6. [optional] Clear graph (if clearFirst is true) + 7. Fetch triples (async generator yielding batches) + 8. Transform triples (convert to address nodes + attestation edges) + 9. Upsert addresses (MERGE nodes in batches) +10. Upsert attestations (MERGE edges in batches) +11. Write sync metadata (Meta node with health data) +12. Close driver +``` + +### Pagination Strategy + +Triples are fetched through an async generator that yields pages from the +Intuition GraphQL API. Each page fetches up to `pageSize` triples (default 1000). +The generator continues until all triples are fetched or `maxPages` is reached. + +Each yielded batch is processed independently -- transformed, then upserted to +Neo4j before the next batch is fetched. This keeps memory usage bounded +regardless of total triple count. + +### Transform Pipeline + +For each attestation triple: + +1. **Validate** creator address (must match `0x` + 40 hex characters) +2. **Extract creator node** -- lowercase address, label from creator metadata + or truncated address fallback +3. **Extract subject node** -- from the subject's wallet address if valid +4. **Calculate stake** -- `total_assets / 1e18` (wei to ETH conversion) +5. **Create edge** -- creator to subject with predicate label, stake, triple ID, + and timestamp +6. **Deduplicate** -- nodes in the same batch are merged, accumulating + total stake and attestation count + +### Neo4j Schema + +Created idempotently on each sync: + +| Type | Name | Target | +|------|------|--------| +| Unique constraint | `address_id_unique` | `Address.id` | +| Index | `address_label_index` | `Address.label` | +| Index | `attests_predicate_index` | `ATTESTS.predicate` | +| Index | `attests_timestamp_index` | `ATTESTS.timestamp` | +| Index | `attests_triple_id_index` | `ATTESTS.tripleId` | + +### Sync Health Tracking + +After each sync, a Meta node is written (or updated) in Neo4j with health data: + +| Field | Type | Description | +|-------|------|-------------| +| `lastSyncedAt` | ISO 8601 string | Timestamp of sync completion | +| `status` | `"success"` or `"partial"` | `"partial"` if any batch errors occurred | +| `durationMs` | number | Total wall-clock time | +| `nodesCreated` | number | Addresses upserted in this sync | +| `edgesCreated` | number | Attestations upserted in this sync | +| `errorCount` | number | Batch errors encountered | +| `totalNodes` | number | Total addresses in graph post-sync | +| `totalEdges` | number | Total attestations in graph post-sync | + +These fields are exposed through the `get_graph_stats` and `get_sync_health` tools. + +### Auto-Sync Cron Job + +Configured via environment variables: + +| Variable | Default | Description | +|----------|---------|-------------| +| `ENABLE_SYNC_CRON` | `"false"` | Must be `"true"` to start the cron job | +| `SYNC_SCHEDULE` | `"0 * * * *"` | Cron expression (default: top of every hour) | +| `SYNC_INTERVAL_PRESET` | -- | Human-friendly alias (takes precedence over `SYNC_SCHEDULE`) | + +Preset values for `SYNC_INTERVAL_PRESET`: + +| Preset | Cron Expression | Schedule | +|--------|----------------|----------| +| `"hourly"` | `0 * * * *` | Every hour at minute 0 | +| `"daily"` | `0 0 * * *` | Midnight daily | +| `"twice-daily"` | `0 0,12 * * *` | Midnight and noon | + +The cron job prevents overlap -- if a sync is still running when the next +scheduled run fires, the second run is skipped. + +The `get_sync_status` tool returns: `isRunning`, `nextRun`, `lastRunSuccess`. + +### Sync Result + +``` +{ + nodesCreated: number, + nodesUpdated: number, + edgesCreated: number, + edgesUpdated: number, + errors: string[], + duration: number // milliseconds +} +``` + +[Back to top](#table-of-contents) + +--- + +## 7. Predicate Weights + +### Predicate Registry + +All 9 recognized predicates with their on-chain term IDs and default weights: + +| Predicate | Term ID | Default Weight | Purpose | +|-----------|---------|----------------|---------| +| `trusts` | `0x3a73f3b1...c3ee9` | **1.0** | Primary trust signal | +| `distrust` | `0x93dd055a...9710` | **-0.5** | Negative trust (penalizes score) | +| `follow` | *(none)* | **0.7** | Social follow relationship | +| `visits_for_work` | `0x73872e18...d76b` | **0.4** | Work-related visits | +| `visits_for_learning` | `0x5d6fcc89...a282` | **0.3** | Learning-related visits | +| `visits_for_fun` | `0xb8b8ab8d...2459` | **0.2** | Leisure visits | +| `visits_for_inspiration` | `0xd635b746...9825` | **0.2** | Inspirational visits | +| `visits_for_buying` | `0x3b2089f0...845d` | **0.2** | Purchase visits | +| `visits_for_music` | `0xdeced28a...8fb7` | **0.2** | Music-related visits | + +Full term IDs are available via the `get_predicate_config` tool. + +### Weight Resolution + +Predicate weights are resolved with the following priority: + +``` +1. Custom runtime override (if provided per-call) +2. Registry default (from the predicate table above) +3. 0 (unknown predicate fallback) +``` + +### Custom Overrides + +Custom weights can be passed at the tool level: + +- `find_trust_paths` accepts a `predicateWeights` object + (e.g. `{"trusts": 1.0, "follow": 0.5}`) +- `compute_eigentrust` accepts predicate weight overrides for matrix construction +- Custom weights only need to include predicates being overridden; unmentioned + predicates fall back to their registry defaults + +### Design Rationale + +- **trusts (1.0)** -- highest-signal predicate, an explicit trust relationship +- **distrust (-0.5)** -- negative weight penalizes trust scores along paths + containing distrust attestations +- **follow (0.7)** -- strong social signal but less explicit than trust +- **visits_for_work (0.4)** -- behavioral signal with moderate weight +- **visits_for_* (0.2-0.3)** -- weakest signals, behavioral but not explicit + trust indicators +- Visit weights decrease by intentionality: work (0.4) > learning (0.3) > + fun / inspiration / buying / music (0.2) + +[Back to top](#table-of-contents) diff --git a/packages/mcp-trust/BENCHMARKS.md b/packages/mcp-trust/BENCHMARKS.md new file mode 100644 index 0000000..e30ee6b --- /dev/null +++ b/packages/mcp-trust/BENCHMARKS.md @@ -0,0 +1,160 @@ +# Performance Benchmarks + +Performance benchmark suite for all major trust scoring operations in `mcp-trust`. + +--- + +## Running Benchmarks + +Run the full benchmark suite: + +```bash +npx vitest run src/__tests__/benchmarks.test.ts +``` + +Run with verbose output (shows individual test names): + +```bash +npx vitest run src/__tests__/benchmarks.test.ts --reporter=verbose +``` + +The suite prints a summary table to stdout after all benchmarks complete. + +--- + +## Methodology + +### Test Graph + +Each benchmark runs against a deterministic mock graph: + +| Parameter | Value | +|-----------|-------| +| Address nodes | 100 | +| Attestation edges | 500 | +| Predicates used | trusts, follow, vouches, visits_for_work, visits_for_learning | +| Stake range | 0.1 -- 1.0 ETH (in wei) | +| Edge distribution | Deterministic pseudo-random via prime-based index mapping | + +The graph is large enough to exercise convergence loops, matrix construction, +and path aggregation at realistic scale, while small enough to run in CI +without dedicated infrastructure. + +### Execution + +- Each operation is executed **5 times** sequentially +- Wall-clock time is measured per run using `performance.now()` +- The scoring engine cache is cleared before each benchmark to ensure + full recomputation (no cache hits) +- Neo4j is mocked at the session level -- the benchmark measures algorithm + computation time (matrix building, power iteration, convergence detection, + path trust calculation), not network latency + +### What Is Measured + +The benchmarks isolate **algorithm computation time** by mocking the Neo4j +driver. The mock returns pre-generated data for each query pattern (address +lists, edge lists, path objects, count results). This means: + +- **Included**: matrix construction, iterative convergence, score normalization, + path trust calculation, confidence computation, resistance scoring +- **Excluded**: Neo4j network round-trips, Cypher query planning, disk I/O + +This is intentional -- the algorithms must be fast regardless of database +latency. Neo4j performance should be monitored separately via query profiling. + +--- + +## Benchmarked Operations + +| # | Operation | Description | +|---|-----------|-------------| +| 1 | `computeEigenTrust` | Full EigenTrust power iteration over 100 nodes, 500 edges. Builds transition matrix, iterates until convergence (threshold 0.0001, max 100 iterations), computes confidence scores. | +| 2 | `computeAgentRank` | Full PageRank computation over the same graph. Builds weighted adjacency, iterates with damping factor 0.85, computes influence metrics (Gini, entropy, top-10% share). | +| 3 | `computeCompositeScore` (global) | Single-address composite score without `fromAddress`. Runs EigenTrust + AgentRank, normalizes against network max, redistributes transitive trust weight. | +| 4 | `computeCompositeScore` (personalized) | Single-address composite with `fromAddress`. Same as above plus personalized trust path query, direct trust check, and path aggregation. | +| 5 | `findTrustPaths` | 3-hop path traversal between two addresses. Parses Neo4j path objects, calculates per-hop trust (stake * predicate weight * decay), sorts by strength. | +| 6 | `simulateSybilAttack` | Full sybil simulation with 10 injected nodes and 40 collusion edges. Runs EigenTrust + AgentRank twice (baseline + attack), calculates resistance metrics, guarantees cleanup. | + +--- + +## Target Thresholds + +| Metric | Threshold | Rationale | +|--------|-----------|-----------| +| Average duration | < 3000ms | MCP tool responses should feel interactive. 3 seconds is the upper bound for a single tool call before users perceive delay. | + +All 6 benchmarks must pass for the suite to succeed. Any single benchmark +exceeding the 3-second average threshold causes a test failure. + +### Scaling Considerations + +The 3-second target applies to the 100-node / 500-edge test graph. For +production graphs: + +- EigenTrust and AgentRank scale as O(iterations * edges). A 10,000-node + graph with 50,000 edges should converge within seconds on modern hardware. +- Pathfinding depends on `maxHops` and graph connectivity. The 1-10 hop + clamp and 1,000-path limit prevent runaway queries. +- Sybil simulation runs the global algorithms twice. Budget 2x the + single-computation time. +- Composite scoring caches global results for 5 minutes. After the initial + computation, per-address lookups are O(1). + +--- + +## Output Format + +Each benchmark produces a result object: + +```typescript +{ + operation: string, // Human-readable operation name + runs: number, // Number of executions (default 5) + avgMs: number, // Mean duration across all runs + minMs: number, // Fastest run + maxMs: number, // Slowest run (often the first due to JIT warmup) + passed: boolean // true if avgMs < 3000 +} +``` + +The suite prints a formatted summary table after all benchmarks complete: + +``` +BENCHMARK RESULTS SUMMARY +------------------------------------------------------------------------------------------------ +Operation Runs Avg (ms) Min (ms) Max (ms) Status +------------------------------------------------------------------------------------------------ +computeEigenTrust 5 10.67 3.61 25.95 PASS +computeAgentRank 5 2.32 1.24 4.26 PASS +compositeScore (global) 5 2.42 0.04 11.89 PASS +compositeScore (personalized) 5 3.24 0.37 14.12 PASS +findTrustPaths (3 hops) 5 0.32 0.20 0.44 PASS +simulateSybilAttack (10 nodes) 5 14.84 12.25 20.15 PASS +------------------------------------------------------------------------------------------------ +All 6 benchmarks passed (target: < 3000ms avg) +------------------------------------------------------------------------------------------------ +``` + +--- + +## Adding New Benchmarks + +To add a benchmark for a new operation: + +1. Import the function in `src/__tests__/benchmarks.test.ts` +2. Add any new query patterns to the `queryRouter` function if the operation + uses Neo4j queries not already handled +3. Add a test case using the `benchmark()` helper: + +```typescript +it('myNewOperation -- description', async () => { + const result = await benchmark('myNewOperation', () => + myNewOperation(args) + ); + expect(result.passed).toBe(true); + expect(result.avgMs).toBeLessThan(TARGET_MS); +}); +``` + +The result is automatically collected and included in the summary table.