{
  "markdown": "# Horizun Project MCP\n\n[![build and test](https://github.com/HorizunGroup/horizun-msproject-mcp/actions/workflows/ci.yml/badge.svg)](https://github.com/HorizunGroup/horizun-msproject-mcp/actions/workflows/ci.yml)\n\n**An MCP server for Microsoft Project that needs neither Java nor Microsoft Project — and tells you\nthe truth about what it wrote.**\n\n<!-- mcp-name: io.github.HorizunGroup/horizun-msproject-mcp -->\n<!-- The registry verifies ownership by finding that name in the published package's README.\n     It has to travel inside the NuGet package, which is why it lives here rather than in a\n     metadata file. -->\n\n\nPoint any MCP client at a `.mpp`, a Primavera `.xer`, or an MSPDI `.xml` and ask real questions:\nwhere the critical path runs, which resources are overbooked, whether the schedule would survive a\nDCMA audit, what a two-week slip actually does to the finish date. Then write changes back — and\nknow which ones landed, because every one is re-read from the model before it is reported.\n\n```bash\ndotnet tool install -g HorizunMsProjectMcp\n```\n\nThat is the whole install. No JVM. No Microsoft Project. No licence. It reads and schedules on\nits own.\n\n[![NuGet](https://img.shields.io/nuget/v/HorizunMsProjectMcp.svg)](https://www.nuget.org/packages/HorizunMsProjectMcp)\n\n---\n\n## Why this one\n\nThere are a handful of Microsoft Project MCP servers. Every one of them requires Java, or a\nlicensed Microsoft Project install, or a paid JDBC driver — and on the machine this was built on,\n**none of them would start**. Seven things here are not available anywhere else:\n\n| | |\n|---|---|\n| **Zero prerequisites** | A single .NET binary. MPXJ is compiled to .NET through IKVM, so there is no JVM anywhere in the picture, and no Microsoft Project either. |\n| **Verified writes** | Microsoft Project silently ignores writes all the time — auto-scheduled dates, hard constraints, summary rollups, calculated costs. Nothing here is reported as applied until it has been read back out of the model and matched. |\n| **DCMA 14-point assessment** | The industry standard for judging whether a schedule can be run on. The Primavera servers implement it; none of the Microsoft Project ones do. Check 12 genuinely injects a 600-day delay and measures what moves. |\n| **It recovers logic nobody linked** | A schedule laid out correctly on the bar chart but never linked is the most common defect there is: DCMA flags it, nothing fixes it. The dates already state the order — this reads it back out, verifies the same order holds across every repetition, and proposes the missing links. |\n| **Reprogramming that is measured** | Recovery options are applied to a copy of the schedule, rescheduled, and reported with the finish date they actually produce. An option that recovers nothing says so instead of being offered as advice. |\n| **It learns from your past projects** | Point it at finished schedules and it reports what each activity really took and what usually precedes it, then drafts a new programme from that — one task per unit, sequenced the way the trades actually followed each other. |\n| **A BIM bridge** | Tie schedule tasks to model elements by code, turn measured quantities into duration proposals, and emit the per-element dates that drive 4D in Navisworks and the progress dashboard in Power BI. Nobody else does this at all. |\n\n---\n\n## The 25 tools\n\n**Session** — `project_health` · `project_open` · `project_save`\n\n`project_health` is a doctor, not a ping. It detects Microsoft Project, tests whether its COM server\n*actually starts*, and when it does not, hands back the HRESULT diagnosis and the repair steps. That\nis not hypothetical: this machine hit `CO_E_SERVER_EXEC_FAILURE` with a perfectly valid registration,\nand step one of the repair path it emits is what fixed it.\n\n**Reading** — `project_info` · `tasks_query` · `links_query` · `resources_query` · `timephased_query`\n\nFiltered, paged, field-selectable. Tasks are addressed by their stable `uid`; the row `id` is display\nonly, because it shifts the moment a task is inserted and addressing by it edits the wrong task.\n\n**Analysis** — `schedule_analyze` · `schedule_qa` · `baseline_compare`\n\nComputed server-side, so the agent asks a question instead of pulling two thousand tasks into\ncontext. Critical path, float distribution, driving path, day-by-day overallocation, DCMA-14, and\nfull earned value (BCWS/BCWP/ACWP, SPI, CPI, EAC, TCPI) — denominated in cost where the schedule\ncarries costs, in work hours where it carries hours, and weighted by duration where it carries\nneither, which is most of them. The report says which.\n\n**Writing** — `tasks_write` · `links_write` · `resources_write` · `calendars_write` · `schedule_update`\n\nBatched, typed, verified. Cycles are refused before they are applied, with the offending chain named.\nTwo things this backend cannot do are not offered: reordering a task within the outline, and editing\na calendar's weekly working-hours pattern. Asking for either gets a refusal that names it and says\nwhere to do it instead — an operation that half-works is worse than one that is absent.\n\n**Planning** — `schedule_recovery` · `schedule_target` · `schedule_sequence` · `schedule_learn` · `schedule_generate`\n\nReprogramming, measured rather than asserted. `schedule_recovery` finds what is late, ranks it by\nhow much of the schedule sits behind it, then tries each recovery lever — removing lag on the\ndriving chain, overlapping hand-offs, compressing the longest critical tasks — on a throwaway copy\nand reports the finish date each one genuinely produces. `schedule_target` tests a date you have\nbeen handed and names the work the network does not hold in place. `schedule_sequence` recovers the\nlogic a schedule is missing by reading the order its own dates already state — the planner laid the\nwork out correctly and never linked it, and that decision is recoverable. `schedule_learn` mines finished\nschedules for how long each activity actually takes and what usually comes before it;\n`schedule_generate` turns that into a first draft, one task per apartment or floor, sequenced the\nway the history says the trades follow each other.\n\nFeed `schedule_learn` a model export alongside the schedules and it measures **productivity** —\nwhat a crew actually got through in a day — by joining quantities to tasks on the shared code.\n`schedule_generate` then sizes durations from the quantities of the new project rather than\ncopying a remembered duration, because the rate is what carries between projects and the quantity\nis what changes. Give one export per schedule, in the same order: rates are measured per project,\nand quantities totalled across projects would inflate every one of them.\n\nA draft can only be as well sequenced as the schedules it learned from. Where the sources link each\nactivity to itself unit after unit but never to the trades around it, both tools say so and name the\nnumber: the library reports how many activities learned a predecessor other than themselves, and the\ndraft reports how many trades it left with nothing scheduled before them.\n\n**Interop** — `project_export` · `project_import` · `bim_link` · `bim_sync`\n\nCSV, JSON, MSPDI, Primavera XER and PMXML, native `.mpp`, and a shaped Power BI dataset. Imports\nplan before they write.\n\n### What it costs to have loaded\n\nThe 25 tools present about **8,400 tokens** of schema, in every prompt, for as long as the server is\nconnected. That is the honest price of the surface and it is worth knowing before choosing to carry\nit. It is also why the surface stayed at 25: the largest alternative ships 79 tools, and past a\npoint an agent cannot hold the surface in its head well enough to choose correctly within it.\n\n---\n\n## The two contracts\n\n**Nothing is applied until it is verified.**\n\n```jsonc\n{\n  \"applied\": 12,              // operations that fully succeeded, re-read from the model\n  \"fieldsVerified\": 31,\n  \"rejected\": [{\n    \"uid\": 45, \"field\": \"finish\",\n    \"requested\": \"2026-09-10\", \"actual\": \"2026-09-14\",\n    \"reason\": \"'finish' is calculated from the task's duration and its predecessors. Change the\n               duration or the logic, or set a constraint, rather than writing the date.\"\n  }],\n  \"impact\": {\n    \"tasksMoved\": 312,\n    \"projectFinishBefore\": \"2027-03-14\", \"projectFinishAfter\": \"2027-03-28\",\n    \"criticalPathChanged\": true, \"newNegativeFloat\": 8\n  },\n  \"verifiedBy\": \"reread\"\n}\n```\n\n**A dry run is a real simulation.** `dryRun: true` deep-copies the schedule, applies the batch,\nreschedules it with the critical-path engine, measures the difference, and throws the copy away.\nThe impact numbers are observed, not predicted.\n\n---\n\n## Capability honesty\n\n`project_health` publishes a capability matrix, and the tools honour it. Two things stay `false` on\nthe file backend and **refuse rather than approximate**:\n\n- **`write_native_mpp`** — no library can author the binary format. Where Microsoft Project is\n  installed, the save is delegated to it and you get a genuine `.mpp`; where it is not, you get\n  MSPDI and an explanation.\n- **`level_resources`** — Microsoft Project's levelling heuristic is unpublished. Any imitation\n  would be a different answer wearing the same name.\n\nSaving over a baseline that already holds data is refused too, unless you ask for it explicitly.\nA baseline is the record of the original plan that every variance is measured against, and it\ncannot be recovered from the file afterwards.\n\nEverything else — scheduling, recalculation, dry-run simulation, rescheduling incomplete work, the\nDCMA Critical Path Test — is served by this server's own critical-path engine and works on both\nbackends.\n\n## The critical-path engine — and what it is not\n\nMPXJ reads and writes schedule files but does not *schedule* them: a task created through it has no\ndates at all. So there is a real CPM engine here — forward pass, backward pass, total and free\nfloat, critical flags — honouring relationship types, lag, constraints, deadlines, actual dates, and\nthe working calendar per task (six-day site weeks, night shifts, exceptions you add with\n`calendars_write`). Without it, dates, float, earned value and every impact figure would be empty on\nany schedule this server authored.\n\n> ### ⚠️ It is not Microsoft Project's scheduler\n>\n> **This engine reproduces Microsoft Project exactly on schedules built through this server. It does\n> not reproduce it on real imported schedules.** Measured against four production construction\n> files, recalculating reproduced Microsoft Project's own start dates on 100% of tasks in one\n> schedule, 69% in another, and around 23% in two more — where most of the remainder moved by a week\n> or more.\n>\n> Microsoft Project's scheduler has behaviours this engine does not implement: task types\n> (fixed units, duration or work), effort-driven scheduling, resource calendars driving dates,\n> manually scheduled tasks, split tasks, and elapsed durations. On a schedule that uses them, our\n> dates will differ.\n>\n> **So imported schedules are never silently rescheduled.** Open a `.mpp` and its dates stay exactly\n> as Microsoft Project computed them; a write reports what it changed and says plainly that dates\n> were not recalculated. If you want this engine's dates instead, ask for them explicitly with\n> `schedule_update op='recalculate'` — which warns you first, and after which the document is ours\n> rather than Project's.\n>\n> Reading, querying, analysis, DCMA-14 and earned value all run on Microsoft Project's own dates and\n> are unaffected. A dry run measures its impact against this engine on both sides, so the movement\n> it reports is caused by your change rather than by the two engines disagreeing.\n>\n> If Microsoft Project is installed, `project_health` reports the COM backend and you can hand the\n> file back to Project itself for a native save.\n\n---\n\n## Wiring it to a client\n\nClaude Desktop, Claude Code, Cursor, VS Code — anything that speaks MCP over stdio:\n\n```jsonc\n{\n  \"mcpServers\": {\n    \"horizun-msproject\": {\n      \"command\": \"horizun-msproject-mcp\"\n    }\n  }\n}\n```\n\nRegistry name: `io.github.horizungroup/horizun-msproject-mcp` (see [`server.json`](server.json)).\n\nCall `project_health` first in every session — it tells you which backend you are on and what it\ncan do.\n\n## Build and test from source\n\n```bash\ncd src/HorizunMsProjectMcp && dotnet build && cd ../..\npython tools/acceptance-test.py   # 65 checks, all 20 tools end to end\npython tools/scheduler-test.py    # 45 checks, critical-path engine correctness\npython tools/planning-test.py     # 52 checks, reprogramming and learning\npython tools/robustness-test.py   # 34 checks, concurrency and hostile input\npython tools/smoke-test.py        # 13 checks, environment and capabilities\n```\n\n**209 checks**, driven over real JSON-RPC against the running server, on Windows and on\nLinux. The Linux job is the evidence for the headline claim: it runs on a machine with no\nJVM and no Microsoft Project.\n\nThe acceptance suite builds a construction schedule from nothing and asserts the contracts above:\nthat a dry run commits nothing, that a cycle is refused before it is applied, that a write to an\nunknown uid is rejected rather than ignored, and that the DCMA Critical Path Test moves the finish\ndate by exactly the delay injected into it.\n\nThe scheduler suite is the one that earns trust in the dates. It covers start-to-start,\nfinish-to-finish and start-to-finish logic, positive and negative lag, hard and soft constraints,\ndeadlines producing negative float, calendar exceptions actually pushing the schedule out, and a\nWBS hierarchy with summary rollup, that an imported schedule is never silently rescheduled, and\nfull round trips through Primavera XER and PMXML and through a real binary `.mpp` — the last\nwritten by Microsoft Project itself, read back by MPXJ, with dates, milestone flags, budget codes\nand dependencies all intact.\n\nBeyond the suites, the server has been driven through a planner's full working cycle on two\nproduction construction schedules — a 5,985-task programme and a 170 MB, 1,937-task one — opening,\nauditing, baselining, recording progress, measuring earned value and exporting the Power BI\ndataset, and read against files of up to 7,000 tasks.\n\nThe robustness suite is the one that matters for trusting this in a real client: forty writes in\nflight at once on the same document, hostile paths, absurd page sizes, non-latin names. MPXJ's\nobject model is not thread-safe and an MCP client is free to pipeline calls — unguarded, sixty\nconcurrent writes all failed and left the document unusable. Every document now has its own lock,\nreentrant because several tools are built on others.\n\nA schedule stays in memory until it is closed, and a 170 MB one costs around 400 MB. Reading many\nretires the oldest document that has nothing unsaved; when every open document has unsaved work,\nopening another is refused rather than discarding any of it. `project_health` reports what is open\nand what it is costing.\n\nRebuild the installable package with `dotnet pack -c Release`.\n\n---\n\n## Formats\n\n**Reads** `.mpp` `.mpt` `.mpx` MSPDI `.xml` · Primavera `.xer` `.pmxml` · Asta `.pp` · Planner ·\nGanttProject and more, through [MPXJ](https://www.mpxj.org/).\n\n**Writes** MSPDI `.xml` (Microsoft Project opens it natively) · `.mpx` · Primavera `.xer` and\n`.pmxml` · Planner · SDEF · JSON · CSV · Power BI dataset · native `.mpp` where Microsoft Project\nis installed.\n\n## Layout\n\n```\nsrc/HorizunMsProjectMcp/\n  Diagnostics/   COM detection and the environment doctor\n  Backends/      MPXJ I/O, the COM bridge, sessions and fingerprints\n  Analysis/      critical-path engine, working calendar, DCMA-14, earned value\n  Writes/        the verified-write engine\n  Bim/           element matching and the 4D bridge\n  Tools/         the 20 MCP tools\ntools/\n  acceptance-test.py   end-to-end across all 20 tools, 65 checks\n  scheduler-test.py    engine correctness, format round trips, safety guards, 45 checks\n  planning-test.py     recovery, sequencing, target dates, learning, generation, 52 checks\n  robustness-test.py   concurrency, malformed input, resource limits, 34 checks\n  smoke-test.py        environment and capability matrix, 13 checks\n```\n\nDesign rationale and the market benchmark that motivated it: [DESIGN-TOOL-SURFACE.md](DESIGN-TOOL-SURFACE.md)\nand [BENCHMARK-MCP-MSPROJECT.md](BENCHMARK-MCP-MSPROJECT.md).\n\n## Licence\n\nMIT.\n",
  "bytes": 16415,
  "sha": "790a1feb09bba096bcfd74166ca9e3a8a34ab55de124061da2bafbf118ec6db4",
  "repo_slug": "horizungroup/horizun-msproject-mcp",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_horizungroup_horizun_msproject_043235f1/readme"
}