{
  "markdown": "# breedsim-mcp\n\n**Breeding-scheme simulation over MCP — returns distributions, never a single stochastic run.**\n\nDrives [AlphaSimR](https://github.com/gaynorr/AlphaSimR) so an agent can ask what a selection\nprogramme would actually gain, with one structural rule: **a single simulation run is not a\nresult, and this API will not return one.**\n\nMeasured on AlphaSimR 2.1.0 — five seeds of an identical three-cycle programme gave mean\ngenetic gain `[1.151, 1.841, 1.424, 1.429, 1.473]`: **sd 0.247** on the very number being\nreported. Quoting one run to three decimals reports noise with the authority of a measurement.\nSo `run_program` enforces a replicate floor and returns per-cycle mean, sd and confidence\ninterval. There is no flag that collapses it to a point estimate.\n\n> Unofficial. Not affiliated with, endorsed by, or sponsored by the AlphaSimR authors, the\n> University of Edinburgh, or the R Foundation. See [NOTICE](NOTICE).\n\n<!-- mcp-name: io.github.musharna/breedsim-mcp -->\n\n## Status\n\n[![ci](https://github.com/musharna/breedsim-mcp/actions/workflows/ci.yml/badge.svg)](https://github.com/musharna/breedsim-mcp/actions/workflows/ci.yml)\n[![PyPI](https://img.shields.io/pypi/v/breedsim-mcp)](https://pypi.org/project/breedsim-mcp/)\n[![python](https://img.shields.io/pypi/pyversions/breedsim-mcp)](https://pypi.org/project/breedsim-mcp/)\n[![license](https://img.shields.io/pypi/l/breedsim-mcp)](LICENSE)\n[![Glama](https://glama.ai/mcp/servers/musharna/breedsim-mcp/badges/score.svg)](https://glama.ai/mcp/servers/musharna/breedsim-mcp)\n[![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.21713210.svg)](https://doi.org/10.5281/zenodo.21713210)\n\nOn PyPI — the badge above is the released version, so it cannot go stale the way\na number typed here would. Genomic selection included. 5 tools, 57 tests against\nreal AlphaSimR, and 20 mutation checks all confirmed red\n([docs/MUTATION-CHECKS.md](docs/MUTATION-CHECKS.md)). The simulation is also checked against the\nbreeder's equation `R = h²S` rather than only against itself\n([docs/EVAL.md](docs/EVAL.md)). CI installs R and compiles\nAlphaSimR, so the suite runs against the real engine on Python 3.11, 3.12 and 3.13 —\nnot against a mock.\n\nRequires `mcp` 2.x.\n\n## Install tax — read this first\n\nHeavier than `uv pip install`, and the reasons are not negotiable:\n\n- **R ≥ 4.3** with a shared library (`libR.so`)\n- **AlphaSimR** — an Rcpp/RcppArmadillo compile, minutes not seconds\n- **`libtirpc-dev`** — rpy2 fails to link without it (`cannot find -ltirpc`)\n- **rpy2 pinned `<3.6`** — 3.6 binds `R_getVar`, which needs R ≥ 4.4\n\n```bash\nsudo apt-get install -y r-base r-base-dev libtirpc-dev\nR -e 'install.packages(\"AlphaSimR\", repos=\"https://cloud.r-project.org\")'\nuv add breedsim-mcp\n```\n\nIf you build against a **conda** Python, rpy2 will fail to load `libR.so` with\n`GLIBCXX_3.4.30 not found` — conda ships an older `libstdc++` than system `libicuuc`\nrequires. Use a system or uv-managed interpreter.\n\n## Configure your MCP client\n\nThe server speaks stdio; the installed console script is `breedsim-mcp`.\n\n**Claude Code**\n\n```bash\nclaude mcp add breedsim -- breedsim-mcp\n```\n\n**Claude Desktop** — add to `claude_desktop_config.json`:\n\n```json\n{\n  \"mcpServers\": {\n    \"breedsim\": {\n      \"command\": \"breedsim-mcp\"\n    }\n  }\n}\n```\n\nIf the executable is not on your `PATH`, or AlphaSimR lives in a user library, invoke it\nthrough uv and pass the library path:\n\n```json\n{\n  \"mcpServers\": {\n    \"breedsim\": {\n      \"command\": \"uv\",\n      \"args\": [\"run\", \"--directory\", \"/path/to/breedsim-mcp\", \"breedsim-mcp\"],\n      \"env\": { \"R_LIBS_USER\": \"/home/you/R/library\" }\n    }\n  }\n}\n```\n\nVerify with `list_methods()`, which reports the engine versions **and whether this process\ncan currently produce reproducible results**.\n\n## Tools\n\n| tool                                                           | returns                                                           |\n| -------------------------------------------------------------- | ----------------------------------------------------------------- |\n| `list_methods()`                                               | engine versions, generators, selection methods, replicate floor   |\n| `found_population(generator, seed, n_ind, n_snp_per_chr, ...)` | `session_id`, founder provenance, `reproducible`, measured **LD** |\n| `run_program(session_id, cycles, replicates, ...)`             | per-cycle **distributions** — mean, sd, 95% CI                    |\n| `compare_programs(session_id, a_n_select, b_n_select, ...)`    | the **paired difference** between two programmes, with a CI       |\n| `describe_session(session_id)`                                 | provenance, trait architecture, cycles run                        |\n\nTypical loop: `found_population` → `run_program` → read the CI and the warnings.\nComparing two schemes: `found_population` → `compare_programs` → read `difference`.\nBoth run tools take `selection_method=\"phenotypic\"` or `\"genomic\"`.\n\n### Species\n\n`species` applies only to `runMacs`, which carries demographic histories for exactly\nfour: **`GENERIC`, `CATTLE`, `WHEAT`, `MAIZE`** (read out of `body(runMacs)`, not the\ndocs). Anything else is refused here rather than failing inside R. Casing does not\nmatter — AlphaSimR upper-cases it, so this does too.\n\nNote the scope that implies: **two plants and an animal.** Despite the default of\n`MAIZE`, this is not a plant-only simulator.\n\n### Limits\n\nEvery size parameter has a ceiling, reported by `list_methods()` under `limits` so a\ncaller can size a request rather than discover the bound by being refused. R runs as a\nsingle interpreter here and tool calls are serialised, so one oversized call blocks every\nother call until it finishes — there is no second worker. The caps are set where a call\nstops being slow and starts being an outage. For genuinely large jobs, drive AlphaSimR\ndirectly rather than through this server.\n\n### What `run_program` returns\n\nVerbatim, for `cycles=2, replicates=10`, abridged to one cycle:\n\n```json\n{\n  \"session_id\": \"bs-...\",\n  \"replicates\": 10,\n  \"cycles\": [\n    {\n      \"cycle\": 2,\n      \"genetic_gain\": {\n        \"mean\": 1.5941703785773211,\n        \"sd\": 0.1414620049281876,\n        \"ci_low\": 1.4929815869737013,\n        \"ci_high\": 1.695359170180941,\n        \"n\": 10\n      },\n      \"genetic_variance\": {\n        \"mean\": 0.5149269761002959,\n        \"sd\": 0.15228915685789685,\n        \"ci_low\": 0.4059934446929936,\n        \"ci_high\": 0.6238605075075982,\n        \"n\": 10\n      }\n    }\n  ],\n  \"reproducible\": true,\n  \"recipe\": {\n    \"generator\": \"quickHaplo\",\n    \"seed\": 1,\n    \"n_select\": 10,\n    \"n_cross\": 60,\n    \"base_seed\": 1000\n  },\n  \"warnings\": []\n}\n```\n\nThere is no `value` field anywhere. Intervals use **t** critical values rather than a normal\n1.96, because at n = 5–10 the normal understates the interval — the wrong direction to be\nwrong in when the interval exists to be honest.\n\n### Comparing two programmes\n\nDo **not** call `run_program` twice and compare the means. Use `compare_programs`, which\npairs the two arms on the same seeds — replicate _i_ of A and replicate _i_ of B start from\nidentical founders under an identical seed — and differences them **within** each pair, so\nthe shared luck of that seed cancels instead of being counted twice.\n\nRead `difference` and `favours`. `favours` is `null` when the interval contains zero, which\nmeans the two programmes are not distinguishable at that replicate count; the larger mean is\nthen not the better programme.\n\nHere is why the pairing earns its keep. Verbatim, selecting 12 of 100 against 18 of 100,\nfinal cycle of two, ten replicates:\n\n```json\n{\n  \"programs\": {\n    \"a\": { \"label\": \"A\", \"n_select\": 12, \"n_cross\": 100 },\n    \"b\": { \"label\": \"B\", \"n_select\": 18, \"n_cross\": 100 }\n  },\n  \"cycles\": [\n    {\n      \"cycle\": 2,\n      \"a_genetic_gain\": {\n        \"mean\": 2.046227841067686,\n        \"ci_low\": 1.9001469542399823,\n        \"ci_high\": 2.1923087278953903,\n        \"n\": 10\n      },\n      \"b_genetic_gain\": {\n        \"mean\": 1.730473406465538,\n        \"ci_low\": 1.5520780571733739,\n        \"ci_high\": 1.9088687557577022,\n        \"n\": 10\n      },\n      \"difference\": {\n        \"mean\": 0.31575443460214814,\n        \"ci_low\": 0.10028439648886733,\n        \"ci_high\": 0.5312244727154289,\n        \"n\": 10\n      }\n    }\n  ],\n  \"favours\": \"a\",\n  \"intervals_overlap\": true,\n  \"warnings\": [{ \"code\": \"overlap_but_different\", \"message\": \"...\" }]\n}\n```\n\n**The two per-programme intervals overlap** — A spans 1.900–2.192, B spans 1.552–1.909 — so\nreading them side by side says \"no difference\". The paired difference says otherwise:\n`[+0.100, +0.531]`, entirely above zero. Pairing cancels the seed-to-seed variation that\nmade both individual intervals wide, so it resolves a contrast that eyeballing the overlap\ncannot. That is what `overlap_but_different` is for.\n\n**Two overlapping confidence intervals do not imply no difference.** This is the single\neasiest way to get a breeding comparison wrong, and it is why the tool reports a difference\nrather than two numbers.\n\n### Genomic selection — and the trap under it\n\n`selection_method=\"genomic\"` fits RRBLUP to the marker genotypes each cycle and\nselects on the estimated breeding value instead of the phenotype. It needs a SNP\nchip, which is a **founding** decision:\n\n```python\nfound_population(generator=\"runMacs\", n_snp_per_chr=50)  # note the generator\nrun_program(session_id, selection_method=\"genomic\")\n```\n\nNote the generator, because this is where genomic selection goes quietly wrong.\nMarkers predict a trait only through **linkage disequilibrium** with the causal\nloci — that is the whole mechanism. And `quickHaplo`, the default generator and\nthe only reproducible one, **has none**:\n\n| generator    | mean \\|r\\| adjacent SNP | mean \\|r\\| distant pairs | ratio    | out-of-sample accuracy |\n| ------------ | ----------------------- | ------------------------ | -------- | ---------------------- |\n| `quickHaplo` | 0.0444                  | 0.0462                   | **0.96** | 0.097                  |\n| `runMacs`    | 0.1979                  | 0.0495                   | **4.00** | 0.351                  |\n\nAdjacent markers in `quickHaplo` are no more correlated than randomly chosen\ndistant ones — it samples haplotypes with no coalescent history, so there is no\nlinkage to learn from. Every `found_population` call with a chip therefore returns\na measured `linkage_disequilibrium` block, and a `no_linkage_disequilibrium`\nwarning when the ratio says the markers are uninformative.\n\n**So on this engine, reproducibility and genomic realism cannot be had at the same\ntime.** `quickHaplo` reproduces and cannot support genomic selection; `runMacs`\nsupports it and does not reproduce in a long-lived process. That is a real\nconstraint of the underlying simulator, and the server states it rather than\nletting you find it as a wrong answer.\n\n#### Why the guard measures LD instead of accuracy\n\nThe obvious alternative — fit the model, warn if accuracy is poor — cannot do the\njob. Measured at 20 replicates, 200 individuals, three cycles:\n\n| selection | `quickHaplo` (LD ratio 1.00) | `runMacs` (LD ratio 3.7)  |\n| --------- | ---------------------------- | ------------------------- |\n| 10% kept  | 0.104 → 0.159 → **0.208**    | 0.105 → 0.130 → 0.167     |\n| 50% kept  | 0.162 → 0.207 → 0.226        | 0.217 → 0.257 → **0.253** |\n\nA population with **no linkage disequilibrium at all reaches 0.208**, and at 10%\nselection it beats the population that has real LD. Accuracy also climbs every\ncycle in both. Neither is a paradox: out-of-sample accuracy in a closed population\nconflates LD with the causal loci and plain **relatedness** between training and\ntarget individuals. As descendants of a few selected parents fill the population,\nmarkers predict by tracking pedigree — and `quickHaplo`'s mutually uncorrelated\nmarkers tag pedigree _more_ efficiently than `runMacs`' markers, which are partly\nredundant with each other precisely because they are in LD.\n\nAn accuracy threshold would therefore wave through the exact population it claimed\nto catch. Only the LD measurement discriminates, so that is what gates the warning.\nAccuracy is still reported on every genomic cycle, measured **out-of-sample** on\nprogeny the model never saw — reporting its in-sample fit instead would have read\n0.448 where the truth was 0.097. Read it as a property of the model in front of\nyou, not as proof that genomic selection is working for the reason you assume.\n\n### Warnings\n\n| code                           | meaning                                                                  |\n| ------------------------------ | ------------------------------------------------------------------------ |\n| `nondeterministic_founders`    | founders came from `runMacs`; a repeat call will differ                  |\n| `no_linkage_disequilibrium`    | founder markers carry no linkage; genomic prediction cannot work here    |\n| `prediction_accuracy_low`      | the marker model is not predicting — gain came from drift, not selection |\n| `difference_indistinguishable` | the paired difference interval contains zero — no winner at this n       |\n| `overlap_but_different`        | the per-arm intervals overlap but the paired difference resolves         |\n| `threads_not_pinned`           | `OMP_NUM_THREADS != 1`, so the same seed will not reproduce              |\n| `replicates_too_few`           | the CI is wide relative to the effect — too wide to support a comparison |\n| `variance_exhausted`           | genetic variance has collapsed; a still-rising mean is a plateau         |\n\nNone of these withhold results. Outputs are always distributions, so you can already see when\nan answer is too noisy to use — they explain rather than refuse.\n\n## Reproducibility\n\nTwo independent things break it, and both were measured:\n\n| source                | symptom                                         | fix                                          |\n| --------------------- | ----------------------------------------------- | -------------------------------------------- |\n| `runMacs` founder RNG | a **repeat** seeded call in one session differs | use `quickHaplo`                             |\n| OpenMP in selection   | same seed, fixed founders → 2.397 vs 2.125      | `OMP_NUM_THREADS=1` **before R initialises** |\n\nThe `runMacs` case is subtler than \"it ignores the seed\". Its MaCS RNG is seeded **once per R\nsession** and advances across calls, so the same call _sequence_ reproduces in a fresh process\nwhile a repeat call inside one session does not. Since this server is a long-lived process,\nthe repeat case is the one you hit — hence `reproducible: false`.\n\nThe server pins threads at import, before rpy2 loads, and reports whether it succeeded. With\nfounders fixed and threads pinned, seed 7 reproduces exactly: `meanG=2.01451853`, twice.\n\n## Limitations\n\nMulti-trait selection is supported: pass `h2` as a list to build several traits and\n`index_weights` to select on a weighted index. `compare_programs` remains single-trait — one\npaired verdict needs one criterion, and with several traits that criterion is the index, which\nAlphaSimR does not report a gain for.\n\nNo G×E, no optimal contribution selection, no crossing-block optimisation, no genotype-matrix\nexport. Genomic selection is RRBLUP only — the `RRBLUP_D`, `_GCA` and `_SCA` variants are not\nexposed, because they estimate dominance and combining-ability effects that an additive-only\n`addTraitA` architecture does not contain.\n\nThe `prediction_accuracy_low` and `no_linkage_disequilibrium` warnings are advisory: like every\nother warning here, they explain rather than refuse.\n\nSessions are in-memory and capped (8, LRU); they do not survive a restart.\n\n## Licence\n\n**GPL-3.0-or-later** — because this imports rpy2, which is GPLv2+. AlphaSimR itself is MIT;\nthe licence here is about rpy2, not about AlphaSimR. See [NOTICE](NOTICE).\n\n## More\n\n- [CHANGELOG.md](CHANGELOG.md) — what changed, and why\n- [docs/MUTATION-CHECKS.md](docs/MUTATION-CHECKS.md) — every guard disabled on purpose, and\n  the test that went red for it\n",
  "bytes": 16118,
  "sha": "864ad2f407ae1890475a3c6c73b0bba8f250cbaa7f043f3dcf17a0dfc15e5f75",
  "repo_slug": "musharna/breedsim-mcp",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_musharna_breedsim_mcp_a442020a/readme"
}