{
  "markdown": "# DecisionMatrix MCP\n\nA transparent, **100% deterministic** [Model Context Protocol (MCP)](https://modelcontextprotocol.io)\nserver that gives LLM agents a reliable **multi-criteria decision analysis (MCDA)** engine.\n\nAgents are great at gathering options but unreliable at *weighing* them: they lose\nprecision, apply inconsistent weights, and can't show their work. DecisionMatrix\noffloads the scoring to an exact, explainable engine. You provide **options** and\n**weighted criteria** (plus a score matrix); it returns a fully **scored, ranked, and\nexplained** result — with per-criterion breakdowns, the methodology used, the weights\napplied, and a plain-language explanation.\n\nEvery number flows through [`decimal.js`](https://github.com/MikeMcl/decimal.js) at\n40-digit precision (**never floats**), so identical inputs always produce\n**byte-identical output**. The server is **stateless** — no database, no sessions.\n\n## 🌐 Live hosted server (free, no install)\n\nA public remote MCP server runs on Cloudflare's edge — point any Streamable-HTTP\nMCP client at it:\n\n```\nhttps://decisionmatrix-mcp.pages.dev/mcp\n```\n\n```json\n{ \"mcpServers\": { \"decisionmatrix\": {\n    \"type\": \"http\", \"url\": \"https://decisionmatrix-mcp.pages.dev/mcp\" } } }\n```\n\nIt runs in **open mode** on the free tier (no key, 15 calls/day per IP). Paid plans\n(**Starter $12/mo · 5,000/day**, **Pro $39/mo · 50,000/day**) are live via Stripe\nCheckout — buy a plan, get an API key instantly, and send it as `X-API-Key`. Self-host\nfor unlimited calls with no keys. Landing page + pricing: <https://decisionmatrix-mcp.pages.dev>.\n\n---\n\n## What it does\n\nSix tools, all returning a uniform, agent-parseable envelope:\n\n| Tool | Purpose |\n|------|---------|\n| `create_decision` | **Main tool.** Rank options against weighted criteria → winner, full ranking, per-criterion breakdowns, methodology, weights, and a plain-language explanation. |\n| `score_options` | Return the full normalized scored matrix when scores are supplied separately. |\n| `sensitivity_analysis` | Sweep each criterion's weight ±X% and report how robust the winner is (and where it flips). |\n| `compare_two` | Head-to-head comparison of exactly two options with per-criterion win counts. |\n| `list_methods` | Discovery: available scoring methods and when to use each. |\n| `health_check` | Version, status, and capabilities. |\n\n### Scoring methods\n\n| method | model | normalization | notes |\n|--------|-------|---------------|-------|\n| `weighted_sum` *(default)* | Simple Additive Weighting (SAW) | min-max per criterion | Most transparent; additive contributions. Handles negatives. |\n| `weighted_product` | Weighted Product Model (WPM) | ratio (x/max, min/x) | Punishes any single weak criterion; **requires scores > 0**. |\n| `topsis` | Closeness to ideal solution | vector (Euclidean) | 0–1 closeness coefficient; robust with many criteria. |\n\nEach criterion has a **direction**: `benefit` (higher is better — quality, speed) or\n`cost` (lower is better — price, latency, risk). Weights are **relative**; they are\nnormalized to sum to 1 internally.\n\n### Consistent response envelope\n\nEvery **successful** response contains: `status`, `method`, `winner`, `ranking`\n(with per-criterion `breakdown`), `methodology`, `weights_used`, `inputs_used`,\n`notes`, and a natural-language `explanation`.\n\n```json\n{\n  \"status\": \"success\",\n  \"method\": \"weighted_sum\",\n  \"winner\": { \"option\": \"Gamma\", \"score\": 0.666667, \"score_exact\": \"0.666667\", \"rank\": 1, \"tie\": false, \"tied_with\": [] },\n  \"ranking\": [\n    { \"rank\": 1, \"option\": \"Gamma\", \"score\": 0.666667, \"score_exact\": \"0.666667\",\n      \"breakdown\": [\n        { \"criterion\": \"Price\", \"direction\": \"cost\", \"weight\": 0.5, \"weight_raw\": \"3\",\n          \"raw_score\": \"900\", \"normalized_score\": 1, \"weighted_contribution\": 0.5 }\n      ] }\n  ],\n  \"methodology\": {\n    \"method\": \"weighted_sum\",\n    \"name\": \"Weighted Sum Model (Simple Additive Weighting)\",\n    \"normalization\": \"min-max per criterion (best value -> 1, worst -> 0)\",\n    \"score_range\": \"0 to 1 (higher is better)\",\n    \"weighting\": \"Criteria weights are normalized to sum to 1; only their relative sizes matter.\",\n    \"deterministic\": true\n  },\n  \"weights_used\": [ { \"criterion\": \"Price\", \"direction\": \"cost\", \"weight_input\": \"3\", \"weight_normalized\": 0.5 } ],\n  \"inputs_used\": { \"options\": [\"Alpha\",\"Beta\",\"Gamma\"], \"method\": \"weighted_sum\", \"option_count\": 3, \"criterion_count\": 3 },\n  \"notes\": [ \"Scores are normalized within this option set; they express relative standing, not an absolute grade.\" ],\n  \"explanation\": \"Using the Weighted Sum Model, 'Gamma' ranks #1 with a score of 0.666667, ahead of 'Alpha' (0.527778) by 26.32% ...\"\n}\n```\n\n**Errors never cross the tool boundary as exceptions** — they come back as a\nstructured, actionable envelope:\n\n```json\n{\n  \"status\": \"error\",\n  \"error\": {\n    \"type\": \"incomplete_scores\",\n    \"message\": \"Missing 1 score(s) in the options x criteria matrix.\",\n    \"hint\": \"Provide a score for every option and criterion. Missing: Beta / Weight.\"\n  }\n}\n```\n\n> **Design note — exact numbers:** `score` is a deterministically-rounded number (6 dp)\n> for easy consumption; `score_exact` / `raw_score` are full-precision **strings** so no\n> precision is lost in JSON. Rankings are computed on the exact values, with input order\n> as a stable tie-break.\n\n---\n\n## Project structure\n\n```\ndecisionmatrix-mcp/\n├── worker-src/\n│   ├── index.mjs        # Cloudflare Pages Function (_worker.js): MCP over Streamable HTTP + billing routes\n│   ├── engine.mjs       # The deterministic MCDA engine: 3 methods + 6 tools + validation\n│   └── billing.mjs      # Stripe Checkout + KV-backed API keys, quota metering, webhook\n├── site/\n│   ├── index.html       # Static landing / pricing / docs page\n│   └── _worker.js        # Built bundle (esbuild output; git-ignored)\n├── tests/\n│   └── engine.test.mjs  # 21 core scoring-logic tests (node --test)\n├── examples/\n│   └── agent_example.mjs # End-to-end MCP client demo over HTTP\n├── package.json         # build / deploy / dev / test scripts\n├── wrangler.toml        # Cloudflare Pages config\n├── .env.example         # Optional auth/rate-limit env reference\n├── LICENSE              # MIT\n└── README.md\n```\n\n**Separation of concerns:** `engine.mjs` is pure and transport-agnostic (import it\ndirectly in tests or any Node/Deno/edge runtime); `index.mjs` only handles the MCP\nJSON-RPC wiring, HTTP, CORS, and the auth/metering seam.\n\n---\n\n## Requirements\n\n* Node **18+** (for the build, tests, and local dev). Only two dev/runtime deps:\n  `decimal.js` (math) and `esbuild` (bundler).\n* A Cloudflare account (free tier is fine) to deploy the hosted version.\n\n---\n\n## Run it locally\n\n```bash\ngit clone <your-fork> decisionmatrix-mcp && cd decisionmatrix-mcp\nnpm install\n\n# Run the test suite (no server needed)\nnpm test\n\n# Serve the MCP endpoint locally via Wrangler (builds + runs Pages dev)\nnpm run dev          # -> http://127.0.0.1:8788/mcp\n\n# Try the end-to-end client demo (hosted by default, or pass a local URL)\nnode examples/agent_example.mjs\nnode examples/agent_example.mjs http://127.0.0.1:8788\n```\n\nQuick manual call:\n\n```bash\ncurl -s http://127.0.0.1:8788/mcp \\\n  -H 'content-type: application/json' \\\n  -H 'accept: application/json, text/event-stream' \\\n  -d '{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"tools/call\",\"params\":{\n        \"name\":\"list_methods\",\"arguments\":{}}}'\n```\n\n---\n\n## Install via npm (stdio, no hosting)\n\nRun the server locally over stdio with a single command — nothing to deploy:\n\n```bash\nnpx -y decisionmatrix-mcp\n```\n\nClaude Desktop / any stdio MCP client (`claude_desktop_config.json`):\n\n```json\n{ \"mcpServers\": { \"decisionmatrix\": { \"command\": \"npx\", \"args\": [\"-y\", \"decisionmatrix-mcp\"] } } }\n```\n\nThis is the same deterministic engine as the hosted server, running on your machine.\n\n## Client configuration\n\n### Cursor — `~/.cursor/mcp.json`\n```json\n{ \"mcpServers\": { \"decisionmatrix\": {\n    \"url\": \"https://decisionmatrix-mcp.pages.dev/mcp\" } } }\n```\n\n### Claude Desktop — `claude_desktop_config.json`\nClaude Desktop launches stdio servers, so bridge to the HTTP endpoint with `mcp-remote`:\n```json\n{ \"mcpServers\": { \"decisionmatrix\": {\n    \"command\": \"npx\", \"args\": [\"-y\", \"mcp-remote\", \"https://decisionmatrix-mcp.pages.dev/mcp\"] } } }\n```\n\n### VS Code — `.vscode/mcp.json`\n```json\n{ \"servers\": { \"decisionmatrix\": {\n    \"type\": \"http\", \"url\": \"https://decisionmatrix-mcp.pages.dev/mcp\" } } }\n```\n\n### Any Streamable-HTTP MCP client\nPoint it at `https://decisionmatrix-mcp.pages.dev/mcp` (or your self-hosted URL). If\nyou enable auth, add `X-API-Key` (or `Authorization: Bearer <key>`) in the client's\n`headers`.\n\n---\n\n## Tools & parameters\n\n### `create_decision(options, criteria, scores, method=\"weighted_sum\")`\n- **options** — array of names (`[\"Vendor A\",\"Vendor B\"]`) or objects\n  (`[{\"name\":\"Vendor A\",\"scores\":{...}}]`). Minimum 2, names unique.\n- **criteria** — array of `{ \"name\", \"weight\" (>=0), \"direction\": \"benefit\"|\"cost\" }`.\n  At least one weight must be > 0.\n- **scores** — the option×criterion matrix. Accepted shapes:\n  - object map: `{ \"Vendor A\": { \"Price\": 100, \"Quality\": 8 }, ... }`\n  - array: `[ { \"option\": \"Vendor A\", \"scores\": { ... } }, ... ]`\n  - inline on each option object.\n- **method** — `weighted_sum` (default) · `weighted_product` · `topsis` (aliases like\n  `saw`, `wpm`, `ideal` also resolve).\n\n### `score_options(options, criteria, scores, method)`\nSame inputs as `create_decision`; returns the full **scored matrix** (per-option,\nper-criterion normalized scores + totals) without the winner narrative.\n\n### `sensitivity_analysis(options, criteria, scores, method, variation=0.2, steps=10)`\nSweeps each criterion's weight from `-variation` to `+variation` (fractional, e.g.\n`0.2` = ±20%) in `steps` increments (2–100), renormalizing the others, and recomputes\nthe winner each time. Returns a `robustness_score` (share of scenarios the baseline\nwinner stays #1), the `fragile_criteria`, and per-criterion flip points.\n\n### `compare_two(option_a, option_b, criteria, scores, method)`\nHead-to-head between exactly two options (pass `option_a`/`option_b` names, or a\n2-element `options` array). Returns the winner, score `margin`, `criteria_wins`, and a\n`per_criterion` breakdown showing which option each criterion `favours`.\n\n### `list_methods()` / `health_check()`\nDiscovery + status. No parameters.\n\n---\n\n## Example tool-call payloads\n\nChoose a laptop (price & weight are **cost** criteria):\n```json\n{ \"name\": \"create_decision\", \"arguments\": {\n  \"options\": [\"Alpha\", \"Beta\", \"Gamma\"],\n  \"criteria\": [\n    { \"name\": \"Price\",   \"weight\": 3, \"direction\": \"cost\" },\n    { \"name\": \"Battery\", \"weight\": 2, \"direction\": \"benefit\" },\n    { \"name\": \"Weight\",  \"weight\": 1, \"direction\": \"cost\" }\n  ],\n  \"scores\": {\n    \"Alpha\": { \"Price\": 1000, \"Battery\": 8,  \"Weight\": 1.5 },\n    \"Beta\":  { \"Price\": 1200, \"Battery\": 12, \"Weight\": 1.8 },\n    \"Gamma\": { \"Price\": 900,  \"Battery\": 6,  \"Weight\": 1.2 }\n  }\n} }\n```\n\nTest how robust the winner is:\n```json\n{ \"name\": \"sensitivity_analysis\", \"arguments\": {\n  \"options\": [\"Alpha\", \"Beta\", \"Gamma\"],\n  \"criteria\": [\n    { \"name\": \"Price\", \"weight\": 3, \"direction\": \"cost\" },\n    { \"name\": \"Battery\", \"weight\": 2 }\n  ],\n  \"scores\": { \"Alpha\": {\"Price\":1000,\"Battery\":8}, \"Beta\": {\"Price\":1200,\"Battery\":12}, \"Gamma\": {\"Price\":900,\"Battery\":6} },\n  \"variation\": 0.3, \"steps\": 8\n} }\n```\n\nHead-to-head:\n```json\n{ \"name\": \"compare_two\", \"arguments\": {\n  \"option_a\": \"Alpha\", \"option_b\": \"Beta\",\n  \"criteria\": [ { \"name\": \"Price\", \"weight\": 3, \"direction\": \"cost\" }, { \"name\": \"Battery\", \"weight\": 2 } ],\n  \"scores\": { \"Alpha\": {\"Price\":1000,\"Battery\":8}, \"Beta\": {\"Price\":1200,\"Battery\":12} }\n} }\n```\n\n---\n\n## Deploy on Cloudflare Pages\n\nSame pattern as PrecisionCalc — one build step bundles `worker-src/` into\n`site/_worker.js` (Pages \"advanced mode\" Function), then Wrangler deploys the `site/`\ndirectory.\n\n```bash\nnpm install\nnpx wrangler login          # once\n\n# Build + deploy in one shot\nnpm run deploy              # esbuild -> site/_worker.js, then wrangler pages deploy\n```\n\nOr wire it to Git: create a Pages project, set the **build command** to `npm run build`\nand the **output directory** to `site`. Every push deploys automatically. The\n`compatibility_date` and project name live in `wrangler.toml`.\n\nTo run **fully free / private**, you need **no bindings, secrets, or env vars** — the\nscoring engine is stateless and the server fails open (free tier, quota disabled).\n\n### Enabling billing (already live on the hosted server)\n\nThe hosted server uses these — replicate them for your own paid deployment:\n\n1. **KV namespace** for API keys + daily usage counters, bound as `DECISIONMATRIX_KV`\n   in `wrangler.toml`.\n2. **Stripe products/prices** (subscription) — put the price IDs in `[vars]`\n   (`PRICE_STARTER`, `PRICE_PRO`) and the daily limits (`FREE_DAILY`, `STARTER_DAILY`,\n   `PRO_DAILY`).\n3. **Stripe secrets** (never in the repo):\n   ```bash\n   wrangler pages secret put STRIPE_SECRET_KEY     --project-name decisionmatrix-mcp\n   wrangler pages secret put STRIPE_WEBHOOK_SECRET  --project-name decisionmatrix-mcp\n   ```\n4. **Webhook** → create a Stripe webhook endpoint at `https://<your-domain>/webhook`\n   for `customer.subscription.updated` + `customer.subscription.deleted`.\n\nRoutes wired up: `/checkout?plan=starter|pro` → Stripe Checkout, `/success` provisions\nand shows the API key (idempotent), `/portal` opens the Stripe billing portal,\n`/webhook` handles subscription lifecycle (revoke/restore), `/metrics` reports usage.\n\n---\n\n## Auth & rate limiting\n\nThe hosted server enforces tiered quotas in `worker-src/billing.mjs`:\n\n* **Identity** — `identify()` reads `X-API-Key` / `Authorization: Bearer`, looks the key\n  up in KV, and falls back to per-IP free tier.\n* **Quota** — `consumeQuota()` is a KV daily counter (resets 00:00 UTC); the single\n  gating point in `handleRpc` where `method === \"tools/call\"`.\n* **Paywall response** — over-quota / invalid / revoked keys get a structured `upsell`\n  envelope with pricing + checkout URLs (agents can read and act on it).\n* **Usage metering** — in-memory counters at `/metrics`.\n\nDecisionMatrix has **no paid-only tools** — every tool works on every tier; paid plans\nonly raise the daily quota. To make a tool paid-only, add its name to `PAID_ONLY_TOOLS`\nin `index.mjs`. Because the engine is pure and stateless, none of this touches the\nscoring logic.\n\n---\n\n## Design decisions & assumptions\n\n* **Deterministic by construction.** 40-digit decimal math, `ROUND_HALF_UP`\n  everywhere, and stable input-order tie-breaking. No floats, no randomness, no clocks\n  in the result.\n* **Normalization is per-criterion and direction-aware.** `weighted_sum` uses min-max\n  (best→1, worst→0); if a criterion is identical across all options it's treated as\n  neutral (normalized to 1) and noted. `weighted_product` uses ratio normalization and\n  requires strictly positive scores (clear error otherwise). `topsis` uses vector\n  normalization and ranks by closeness to the ideal/anti-ideal.\n* **Weights are relative** — normalized to sum to 1, so `[3,2,1]` and `[30,20,10]`\n  give identical results.\n* **Scores are relative to the option set** — they measure standing *within the\n  provided alternatives*, not an absolute grade. This is stated in `notes`.\n* **Errors are data, not exceptions** — every tool returns `status:\"error\"` with a\n  machine `type` and an actionable `hint`. Validation covers duplicate names, missing\n  cells (listing exactly which), non-numeric scores, bad weights/directions, and\n  unknown methods.\n* **Stateless & side-effect-free** — trivially cacheable, horizontally scalable, and\n  safe to run anywhere (Cloudflare, Node, Deno, Bun).\n\n---\n\n## Testing\n\n```bash\nnpm test          # node --test tests/*.test.mjs  (21 tests, no network)\n```\n\nThe suite pins the hand-verifiable `weighted_sum` arithmetic, checks determinism,\nweight-relativity, direction handling, ties, all three methods, `compare_two`,\n`sensitivity_analysis`, the multiple score-input shapes, and every error path.\n\n---\n\n## Roadmap (post-MVP)\n\n1. More methods: AHP (pairwise weight elicitation), ELECTRE, PROMETHEE, Borda count.\n2. Group decisions: aggregate multiple stakeholders' weight/score sets.\n3. Monte-Carlo sensitivity (perturb all weights jointly) alongside one-at-a-time.\n4. Per-key usage dashboard + Redis/Durable-Object quotas for stronger consistency.\n5. Published npm package + a hosted multi-tenant tier.\n\n## License\n\nMIT — see [LICENSE](./LICENSE).\n",
  "bytes": 16528,
  "sha": "a4cf9c16b4d8c7cdc883668f4baa2a3f83a98a439ecf0408a04f35fd7b69e44d",
  "repo_slug": "inity13/decisionmatrix-mcp",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_inity13_decisionmatrix_mcp_0d92dfc2/readme"
}