{
  "markdown": "# ScenarioSim MCP\n\nA transparent, **100% deterministic** [Model Context Protocol (MCP)](https://modelcontextprotocol.io)\nserver that gives LLM agents a reliable **what-if / scenario simulation** engine.\n\nAgents are good at describing a plan but unreliable at *projecting* it: they drift on\nmulti-period arithmetic, mishandle compounding, and can't show their work. ScenarioSim\noffloads the simulation to an exact, explainable engine. You provide **assumptions**\n(growth rates, churn, pricing, costs, starting metrics, a time horizon); it returns\n**projected outcomes over time**, **key metrics**, the exact **assumptions used**,\nplus **sensitivity analysis** and **break-even** solving — each with a plain-language\nexplanation.\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,\nno clocks or randomness in the result.\n\nThis is the third product in a suite built to the same engineering standard as\n**PrecisionCalc MCP** (deterministic high-precision finance/business math) and\n**DecisionMatrix MCP** (transparent multi-criteria decision analysis): identical\nproject structure, output philosophy, and Cloudflare Pages deployment.\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://scenariosim-mcp.pages.dev/mcp\n```\n\n```json\n{ \"mcpServers\": { \"scenariosim\": {\n    \"type\": \"http\", \"url\": \"https://scenariosim-mcp.pages.dev/mcp\" } } }\n```\n\nIt runs in **open mode** on the free tier (no key, 20 calls/day per IP). Paid plans\n(**Starter $12/mo · 5,000/day**, **Pro $39/mo · 50,000/day**) are available 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://scenariosim-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| `run_scenario` | **Main tool.** Project a pre-built template or a free-form model over time → per-period `projections`, headline `key_results`, the `assumptions_used`, `methodology`, `notes`, and a plain-language `explanation`. |\n| `sensitivity_analysis` | Vary one or more inputs (one-at-a-time) and report the impact on a target metric — with an elasticity estimate, the output range, and a ranking of the most influential inputs. |\n| `break_even` | Solve for the input value required to make a target metric hit a target value (deterministic bisection). |\n| `compare_scenarios` | Run 2–3 scenarios side-by-side with deltas vs a baseline and an optional winner. |\n| `list_templates` | Discovery: every template with its inputs (defaults + units) and available outputs. |\n| `health_check` | Version, status, and capabilities. |\n\n### Scenario templates\n\n| id | models | primary output |\n|----|--------|----------------|\n| `saas_growth` | subscribers + MRR/ARR from acquisition (with its own growth) and churn | `ending_mrr` |\n| `pricing_change` | revenue/profit impact of a price change via price elasticity | `cumulative_profit_after` |\n| `churn_impact` | retention erosion + revenue lost vs a no-churn baseline | `cumulative_revenue_lost` |\n| `cost_reduction` | profit + margin impact of cutting costs | `cumulative_savings` |\n| `hiring_plan` | headcount, fully-loaded payroll, revenue capacity | `cumulative_payroll` |\n| `cash_runway` | cash balance forward + months-to-zero runway | `runway_periods` |\n| `unit_economics` | LTV, LTV:CAC, CAC payback, per-customer margin curve | `ltv_cac_ratio` |\n| `marketing_funnel` | visitors → leads → customers → revenue | `total_revenue` |\n| `compound_growth` | generic single-metric compound/linear projection | `ending_value` |\n| `custom` | free-form: any number of independently-growing metrics | *(first metric)* |\n\nEvery template accepts `horizon` (number of periods, 1–1200) and `period_label`\n(`day`/`week`/`month`/`quarter`/`year`, which also sets annualization). Inputs you don't\nprovide fall back to documented defaults; unknown inputs are ignored and reported in `notes`.\nCall `list_templates` for the full input/output catalog.\n\n### Consistent response envelope\n\nEvery **successful** response contains: `status`, `scenario`, `period_label`, `horizon`,\n`key_results` (+ `key_results_detail` with units and full-precision `value_exact`),\n`projections`, `assumptions_used`, `methodology`, `notes`, and a natural-language\n`explanation`.\n\n```json\n{\n  \"status\": \"success\",\n  \"scenario\": \"saas_growth\",\n  \"period_label\": \"month\",\n  \"horizon\": 12,\n  \"key_results\": {\n    \"ending_customers\": 449.7, \"ending_mrr\": 26982.1, \"ending_arr\": 323785.2,\n    \"total_churned_customers\": 82.4, \"cumulative_revenue\": 232104.6\n  },\n  \"projections\": [\n    { \"period\": 0, \"customers\": 200, \"mrr\": 12000, \"new_customers\": 0, \"churned_customers\": 0 },\n    { \"period\": 1, \"customers\": 234, \"mrr\": 14040, \"new_customers\": 40, \"churned_customers\": 6 }\n  ],\n  \"assumptions_used\": {\n    \"template\": \"saas_growth\", \"starting_customers\": \"200\", \"new_customers_per_period\": \"40\",\n    \"acquisition_growth_rate\": \"0\", \"churn_rate\": \"0.03\", \"arpu\": \"60\",\n    \"horizon\": 12, \"period_label\": \"month\"\n  },\n  \"methodology\": {\n    \"model\": \"SaaS Growth\",\n    \"primary_output\": \"ending_mrr\",\n    \"precision\": \"decimal.js (40 significant digits)\",\n    \"deterministic\": true,\n    \"period_convention\": \"Period 0 is the starting state; periods 1..12 are projected. 12 month(s) per year.\"\n  },\n  \"notes\": [\"Churn is applied to the prior period's base before new customers are added.\"],\n  \"explanation\": \"Starting from 200 customers and adding 40 per month (churn 3%), after 12 months you reach ...\"\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\": \"unknown_template\",\n    \"message\": \"Unknown scenario template 'saaas'.\",\n    \"hint\": \"Available templates: saas_growth, pricing_change, churn_impact, cost_reduction, hiring_plan, cash_runway, unit_economics, marketing_funnel, compound_growth. Call list_templates for details ...\"\n  }\n}\n```\n\n> **Design note — exact numbers:** headline numbers in `key_results` are\n> deterministically rounded (6 dp) for easy consumption; `key_results_detail[].value_exact`\n> and `assumptions_used` carry full-precision **strings** so no precision is lost in JSON.\n> All internal math is exact 40-digit decimal.\n\n---\n\n## Project structure\n\n```\nscenariosim-mcp/\n├── worker-src/\n│   ├── index.mjs        # Cloudflare Pages Function (_worker.js): MCP over Streamable HTTP + billing routes\n│   ├── engine.mjs       # The deterministic simulation engine: 9 templates + 6 tools + solver + validation\n│   └── billing.mjs      # Stripe Checkout + KV-backed API keys, quota metering, webhook\n├── server.mjs           # Local stdio MCP server (same engine, no network/state)\n├── site/\n│   ├── index.html       # Static landing / pricing / docs page\n│   ├── mcp.json         # Machine-readable connection manifest\n│   ├── llms.txt         # LLM-friendly summary\n│   └── _worker.js       # Built bundle (esbuild output; git-ignored)\n├── tests/\n│   └── engine.test.mjs  # 29 core simulation-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; `server.mjs` re-uses the same\nengine over stdio.\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> scenariosim-mcp && cd scenariosim-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# Or run the dependency-light stdio server directly\nnode server.mjs\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_templates\",\"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 scenariosim-mcp\n```\n\nClaude Desktop / any stdio MCP client (`claude_desktop_config.json`):\n\n```json\n{ \"mcpServers\": { \"scenariosim\": { \"command\": \"npx\", \"args\": [\"-y\", \"scenariosim-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\": { \"scenariosim\": {\n    \"url\": \"https://scenariosim-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\": { \"scenariosim\": {\n    \"command\": \"npx\", \"args\": [\"-y\", \"mcp-remote\", \"https://scenariosim-mcp.pages.dev/mcp\"] } } }\n```\n\n### VS Code — `.vscode/mcp.json`\n```json\n{ \"servers\": { \"scenariosim\": {\n    \"type\": \"http\", \"url\": \"https://scenariosim-mcp.pages.dev/mcp\" } } }\n```\n\n### Windsurf — `~/.codeium/windsurf/mcp_config.json`\n```json\n{ \"mcpServers\": { \"scenariosim\": {\n    \"serverUrl\": \"https://scenariosim-mcp.pages.dev/mcp\" } } }\n```\n\n### Any Streamable-HTTP MCP client\nPoint it at `https://scenariosim-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### `run_scenario(template?, inputs?, metrics?, horizon?, period_label?)`\n- **template** — one of the template ids above (aliases like `saas`, `pricing`, `runway`,\n  `ltv`, `funnel` also resolve). Omit it (or pass `\"custom\"`) to run a free-form model.\n- **inputs** — the assumptions object for the template, e.g.\n  `{ \"churn_rate\": 0.03, \"arpu\": 60 }`. Also accepted as `assumptions`, or spread at the\n  top level. Missing keys use documented defaults.\n- **metrics** — *(custom mode)* array of `{ name, start, growth_rate?, mode? }` where\n  `mode` is `\"compound\"` (default, `x·(1+r)ⁿ`) or `\"linear\"` (`x·(1+r·n)`).\n- **horizon** — number of periods to project (1–1200). Default per template (usually 12).\n- **period_label** — `day`/`week`/`month`/`quarter`/`year` (default `month`).\n\n### `sensitivity_analysis(template, variable|variables, target_metric?, variation?, steps?, values?, min?, max?, inputs?, horizon?)`\nSweeps each listed input across a range (default ±`variation`=0.2 around the baseline,\n`steps`=5) while all others stay at baseline, recomputing `target_metric` (defaults to the\ntemplate's primary output) at each point. Returns per-variable `sweep` rows, an\n`elasticity_estimate`, the `output_range`, and a `most_influential` ranking. You can also\ngive explicit `values: [...]` or a `min`/`max` grid instead of `variation`.\n\n### `break_even(template, solve_for, target_metric?, target_value, bounds?, inputs?, horizon?)`\nSolves for the value of `solve_for` (an input name) that makes `target_metric` equal\n`target_value`, via deterministic **bisection** with automatic bracket expansion. Returns\n`required_input`, `change_from_baseline`, `achieved_metric`, and `residual`. Assumes the\nmetric is monotonic in the solved input over the search range; if the target can't be\nbracketed it returns a clean `no_solution` error with the achievable range. Pass explicit\n`bounds: [lo, hi]` to constrain (or fix) the search.\n\n### `compare_scenarios(scenarios, compare_metric?, goal?, horizon?, include_projections?)`\nRuns 2–3 `scenarios` (`{ name?, template, inputs }`, or `{ name?, metrics }` for custom)\nand aligns their `key_results`, differencing each against the first (baseline). Pass\n`compare_metric` + `goal` (`max` default | `min`) to rank and pick a `winner`. Set a shared\n`horizon` at the top level, or per-scenario.\n\n### `list_templates()` / `health_check()`\nDiscovery + status. No parameters.\n\n---\n\n## Example tool-call payloads\n\nProject 12 months of SaaS growth:\n```json\n{ \"name\": \"run_scenario\", \"arguments\": {\n  \"template\": \"saas_growth\",\n  \"inputs\": { \"starting_customers\": 200, \"new_customers_per_period\": 40,\n              \"acquisition_growth_rate\": 0.05, \"churn_rate\": 0.03, \"arpu\": 60 },\n  \"horizon\": 12, \"period_label\": \"month\"\n} }\n```\n\nWhich lever moves ending MRR the most?\n```json\n{ \"name\": \"sensitivity_analysis\", \"arguments\": {\n  \"template\": \"saas_growth\",\n  \"inputs\": { \"starting_customers\": 200, \"new_customers_per_period\": 40, \"churn_rate\": 0.03, \"arpu\": 60 },\n  \"variables\": [ { \"name\": \"churn_rate\", \"variation\": 0.5 },\n                 { \"name\": \"arpu\", \"variation\": 0.3 },\n                 { \"name\": \"new_customers_per_period\", \"variation\": 0.5 } ],\n  \"target_metric\": \"ending_mrr\", \"horizon\": 12\n} }\n```\n\nWhat churn keeps 90% of customers after a year?\n```json\n{ \"name\": \"break_even\", \"arguments\": {\n  \"template\": \"churn_impact\",\n  \"inputs\": { \"starting_customers\": 1000, \"arpu\": 60, \"new_customers_per_period\": 0 },\n  \"solve_for\": \"churn_rate\", \"target_metric\": \"retention_pct\",\n  \"target_value\": 0.9, \"horizon\": 12\n} }\n```\n→ `required_input ≈ 0.008742` (about 0.87%/month).\n\nCompare growth strategies:\n```json\n{ \"name\": \"compare_scenarios\", \"arguments\": {\n  \"scenarios\": [\n    { \"name\": \"Base\",           \"template\": \"saas_growth\", \"inputs\": { \"churn_rate\": 0.04, \"new_customers_per_period\": 30 } },\n    { \"name\": \"Aggressive\",     \"template\": \"saas_growth\", \"inputs\": { \"churn_rate\": 0.04, \"new_customers_per_period\": 60 } },\n    { \"name\": \"RetentionFocus\", \"template\": \"saas_growth\", \"inputs\": { \"churn_rate\": 0.015, \"new_customers_per_period\": 30 } }\n  ],\n  \"compare_metric\": \"ending_mrr\", \"goal\": \"max\", \"horizon\": 12\n} }\n```\n\nFree-form (custom) model:\n```json\n{ \"name\": \"run_scenario\", \"arguments\": {\n  \"metrics\": [\n    { \"name\": \"revenue\", \"start\": 10000, \"growth_rate\": 0.08, \"mode\": \"compound\" },\n    { \"name\": \"headcount\", \"start\": 12, \"growth_rate\": 0.05, \"mode\": \"linear\" }\n  ],\n  \"horizon\": 12\n} }\n```\n\n---\n\n## Deploy on Cloudflare Pages\n\nSame pattern as PrecisionCalc / DecisionMatrix — one build step bundles `worker-src/`\ninto `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\nsimulation engine is stateless and the server fails open (free tier, quota disabled).\n\n### Enabling billing (optional)\n\nReplicate these for a paid deployment:\n\n1. **KV namespace** for API keys + daily usage counters, bound as `SCENARIOSIM_KV`\n   in `wrangler.toml` (`wrangler kv namespace create SCENARIOSIM_KV`).\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 scenariosim-mcp\n   wrangler pages secret put STRIPE_WEBHOOK_SECRET  --project-name scenariosim-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. *(To add JWT/mTLS/per-org keys, change\n  `extractKey` + `identify` only — the engine and transport are untouched.)*\n* **Quota** — `consumeQuota()` is a KV daily counter (resets 00:00 UTC); the single\n  gating point in `handleRpc` where `method === \"tools/call\"`. *(Swap for a\n  sliding-window / token-bucket in a Durable Object or Redis for per-minute limits — see\n  the `NOTE (rate limiting)` comment.)*\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\nScenarioSim 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\nsimulation logic.\n\n---\n\n## Design decisions & assumptions\n\n* **Deterministic by construction.** 40-digit decimal math, `ROUND_HALF_UP` everywhere,\n  period-by-period iteration (not `float**n`), and no clocks/randomness in results.\n* **Period 0 is the starting state**; periods `1..horizon` are projected. `period_label`\n  sets the annualization factor (`month` → 12/yr, etc.), which is used for ARR/payroll.\n* **Assumptions are echoed back in full** (`assumptions_used`) with defaults filled in, so\n  a caller always knows exactly what was simulated.\n* **Counts stay fractional** for precision (e.g. 233.6 customers); round to integers in\n  your presentation layer if needed. This is stated in `notes`.\n* **Elasticity/growth models are intentionally simple and transparent** (constant\n  elasticity, constant per-period rates). They're honest first-order estimates, not\n  econometric forecasts — the methodology block says so.\n* **break_even uses bisection** with automatic bracket expansion and a fixed iteration\n  budget → deterministic. It assumes monotonicity of the metric in the solved input over\n  the range; non-monotonic/ratio metrics (with poles) return a clean `no_solution` rather\n  than a wrong root. `sensitivity_analysis`/`break_even` operate on named templates (not\n  the free-form `custom` model) and say so if misused.\n* **Errors are data, not exceptions** — every tool returns `status:\"error\"` with a machine\n  `type` and an actionable `hint`. Validation covers unknown templates/inputs/metrics,\n  non-numeric values, bad horizons/period labels, unreachable targets, and more.\n* **Stateless & side-effect-free** — trivially cacheable, horizontally scalable, and safe\n  to run anywhere (Cloudflare, Node, Deno, Bun).\n\n---\n\n## Testing\n\n```bash\nnpm test          # node --test tests/*.test.mjs  (29 tests, no network)\n```\n\nThe suite pins hand-verifiable arithmetic (compound growth, LTV/CAC, elasticity, runway),\nchecks determinism, the multiple assumption-input shapes, period-label annualization,\ncustom free-form models, the sensitivity sweep + influence ranking, the break-even solver\n(including the unreachable-target path), scenario comparison with `goal=min`, and every\nerror path.\n\n---\n\n## Roadmap (post-MVP)\n\n1. More templates: LBO/DCF, inventory & cash-conversion cycle, ad-spend ROAS, cohort retention.\n2. Monte-Carlo mode: distributions on inputs → confidence bands on outcomes (seeded, still deterministic).\n3. Multi-variable (grid) sensitivity and tornado charts alongside one-at-a-time.\n4. Break-even on the free-form `custom` model and on multiple simultaneous inputs.\n5. Per-key usage dashboard + Durable-Object quotas for stronger consistency.\n\n## License\n\nMIT — see [LICENSE](./LICENSE).\n",
  "bytes": 20546,
  "sha": "3cc50e8f6e10d3b89cf6c669db22356f2904487912a9f29aaf050c2d8bb9e2b2",
  "repo_slug": "inity13/scenariosim-mcp",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_inity13_scenariosim_mcp_ff05f47c/readme"
}