Ironwright MCP
Least-privilege SharePoint reconciliation for AI agents. It answers what should be in a document library and whether it actually is.
- TypeScript
- Microsoft 365
Problem
Recurring client deliverables live in a SharePoint document library, and the record of what was promised lives in an operational tracking workbook. Nobody can answer whether the two agree without opening both and reading them side by side. The obvious fix, pointing an AI assistant at the tenant, means handing it broad Graph access and trusting it to improvise the right calls.
Approach
An MCP server built from composite, server-orchestrated tools rather than one tool per Graph call. A pure reconciliation engine diffs the workbook's expected state against the library's actual contents. The identity is an Entra confidential client scoped with Sites.Selected, never tenant-wide. The write tools, which are designed and not yet built, will be plan-then-execute, dry run by default, with a reversal log written before anything is applied.
Outcome
v0.1.0 alpha. The reconciliation engine and the first wired read tool are complete and fixture-tested, and the per-tenant setup is verified end to end against a live tenant. The other read tools and all of the write tools are still unbuilt, and the tool layer has not run against a live tenant yet. That is the next gate.
Ironwright MCP lets an AI assistant answer a question an MSP gets asked constantly and cannot answer quickly: is this client's document library actually in the state we promised it would be in?
It reads an operational tracking workbook as the expected state, reads the document library as the actual state, and returns a structured gap report. The assistant calls tools. It does not improvise Graph operations.
Most SharePoint MCP servers are a Graph wrapper
Every SharePoint MCP server I surveyed in 2026, first party and community alike, was a thin per-call wrapper: one tool, one Graph call. That shape looks reasonable and reproduces the exact problems that make SharePoint automation painful in the first place. Recursive per-folder enumeration to walk a tree. ID-based navigation that breaks the moment an item is moved. The recursion cost paid again on every question.
Ironwright MCP is composite instead. Tools are server-orchestrated, path-native rather than ID-native, and backed by a real reconciliation engine. Inventory comes from a delta query with a persisted delta link, not a recursive walk, so the second question about a library costs a fraction of the first.
The reconciliation engine is pure, with no I/O at all, and the boundary is enforced by lint rather than by discipline. That is what let the engine be built and tested against fixtures before any tenant access existed, and it is the structural opposite of the wrapper pattern.
Least privilege is the architecture, not a setting
The server's identity is an Entra confidential client scoped through
Sites.Selected, granted per site. It never holds tenant-wide
Sites.ReadWrite.All, including transiently during setup.
That last part is not a detail. A live setup run proved Graph refuses a delegated token on the per-site grant endpoint even for a Global Administrator, and the obvious workaround, granting the server's own application full control for a moment, leaves a tenant-wide credential alive for the life of an already issued token. A separate grant-issuing application does that job instead, so the server's identity is never over-privileged at any point in its life.
This boundary matters more with an AI assistant in front of it than it would otherwise, because agent platform governance is typically applied per server rather than per tool. Scoping is the real control.
The workbook read uses a non-persistent Excel session. The flag that would make a session write-capable is simply never set, so the read-only guarantee is structural rather than a matter of nobody calling the wrong method.
How writes will work
The write tools are designed and not yet built. This is the shape they will ship in, and these properties are part of the design rather than something bolted on after the fact.
Every mutating tool is split in two. A generate_ tool returns a reviewable,
persisted plan. A separate execute_ tool applies it. Dry run is the hard
default and a live run needs an explicit confirmation flag.
Before applying anything, the execute path writes a reversal log. That log is itself a valid plan, so rollback runs through the same code path as the original operation rather than a second implementation that gets less testing and is trusted more.
Writes carry ETag preconditions, so a change made by someone else between the plan and its execution fails the write instead of silently overwriting them.
The tool set
Ten tools in three kinds. Reads answer questions. Plan generators write a reviewable plan and are honestly annotated as not read-only, because writing a plan is a write. Writes apply one. One read tool is wired today. The rest are registered stubs carrying their real schemas and annotations, which is how the contract test holds a tool to the standard before it does anything.
- Read. Inventory a tree via delta. Propose a workbook mapping for human review. Reconcile expected against actual, which is the centerpiece. Serve operational questions from the state index. Validate a client's structure against the profile taxonomy.
- Plan. Generate a rename plan. Generate a move plan.
- Write. Execute a rename plan. Execute a move plan. Replicate a template, validating the source client before replicating it.
Every tool carries a real input schema with constraints and worked examples, an output schema, all four annotations set explicitly, and a description whose prose states its safety status. A contract test enforces all of that across the whole registry, so a tool added later inherits every check rather than being trusted to remember.
Configuration, not code
Taxonomy, filename standard, workbook mapping, and per-client cadence are declarative profile data. One server instance runs per customer tenant, and profiles are deployment configuration that is never committed.
Where it stands
v0.1.0, alpha, and honest about it.
The per-tenant setup is verified end to end against a live tenant, including a rollback that left nothing behind. The reconciliation engine is complete and pinned by a golden report over synthetic fixtures covering every artifact state, both kinds of orphan, duplicate resolution, and a client who owes nothing. The Graph layer, the Excel session, the workbook reader, and the first fully wired read tool are all complete and covered by fakes.
What has not happened is a run of that tool layer against a real tenant. No Graph call, no Excel call, and no table storage call has been made by it outside a test. Live verification is the next gate, and nothing here should be read as claiming it has already been cleared.
Roadmap
The reconciliation engine and the first wired read tool are complete and fixture-tested; live-tenant verification is the next gate.
Full roadmap →