Minerva MCP — Microsoft 365 & Azure
Microsoft 365 & Azure admin: Graph, ARM, and the PowerShell cmdlets Graph does not expose.
Open source Open in the app JSON README (API)
About
Microsoft 365 & Azure admin: Graph, ARM, and the PowerShell cmdlets Graph does not expose.
Details
- Kind
- MCP servers
- Topic
- Cloud & DevOps
- Publisher
- houssemmak
- Origin
- official
- Category
- ferramentas
- Transport
- local
- Version
- 0.4.2
- Stars
- 1
- Last push
- 2026-07-29T18:36:11Z
- Repository state
- ativo
- Language
- TypeScript
- License
- MIT
- Added
- 2026-08-29 03:01:57
- Updated
- 2026-08-29 03:01:57
- Origin id
io.github.HoussemMak/minerva-mcp
README
# Minerva MCP
**Microsoft 365 and Azure administration for AI assistants — including the parts Microsoft Graph cannot reach.**
[](https://github.com/HoussemMak/minerva-mcp/actions/workflows/ci.yml)
[](LICENSE)
[](https://modelcontextprotocol.io/specification/2026-07-28)
An [MCP](https://modelcontextprotocol.io) server that lets Claude, Copilot, VS Code
or any MCP client query and administer a Microsoft 365 tenant in plain language.
---
## Why another Microsoft 365 MCP server?
There are already good ones, and this is not trying to replace them.
| | [Lokka](https://github.com/merill/lokka) | [ms-365-mcp-server](https://github.com/softeria/ms-365-mcp-server) | **Minerva MCP** |
|---|:---:|:---:|:---:|
| Microsoft Graph | ✅ | ✅ 300+ tools | ✅ generic call |
| Azure Resource Manager | ✅ | — | ✅ |
| **Exchange Online cmdlets** | — | — | ✅ |
| **SharePoint / PnP cmdlets** | — | — | ✅ |
| **Teams Admin cmdlets** | — | — | ✅ |
| **Purview / compliance cmdlets** | — | — | ✅ |
| Risk classification per action | — | — | ✅ |
| Human approval before writes | — | read-only switch | ✅ native to the protocol |
| MCP 2026-07-28 (stateless) | — | — | ✅ |
**If you only need Graph, use Lokka or ms-365-mcp-server.** They are excellent,
more mature, and more widely used. Lokka in particular is written by a Microsoft
Entra product manager.
Reach for this one when you hit the wall everyone hits eventually: **a large part
of Microsoft 365 administration has no Graph API.** Mailbox permissions,
transport rules, message trace, retention policies, sensitivity labels, Teams
calling policies, SharePoint term stores, tenant-wide Exchange settings — those
live in PowerShell and nowhere else. This server exposes them, with a
risk classification and a confirmation step in front of anything that writes.
---
## How it fits together
```mermaid
flowchart LR
A["AI client<br/>Claude · VS Code · Copilot Studio"]
B["minerva-mcp"]
C{"Risk<br/>classifier"}
D["Human<br/>confirmation"]
G["Microsoft Graph<br/>Azure ARM · Search"]
P["PowerShell 7<br/>Exchange · PnP · Teams · Purview"]
M["Microsoft<br/>authorizes the token"]
A -->|"stdio or HTTP"| B
B --> C
C -->|"read"| G
C -->|"write"| D
D -->|"signed mandate"| G
D -->|"signed mandate"| P
C -->|"read"| P
G --> M
P --> M
```
Reads go straight through. Writes stop at a classifier, come back to you as an
`input_required` describing the action and its consequences, and only run once
you confirm. Microsoft still has the final word: the server forwards a token, it
never grants access.
## Install
Nothing to clone. Add it to your client and sign in on first use.
### Claude Desktop
`claude_desktop_config.json`:
```json
{
"mcpServers": {
"minerva": {
"command": "npx",
"args": ["-y", "minerva-mcp", "--stdio"]
}
}
}
```
### VS Code
`.vscode/mcp.json`:
```json
{
"servers": {
"minerva": {
"type": "stdio",
"command": "npx",
"args": ["-y", "minerva-mcp", "--stdio"]
}
}
}
```
More, including read-only and app-only setups, in [`examples/`](examples/).
On the first tool call the server prints a device-code prompt **on stderr**:
open the link, enter the code, done. No app registration needed to try it — it
uses the public Microsoft Graph PowerShell client by default.
**Requirements:** Node.js 20+. For the PowerShell tools, [PowerShell 7+](https://aka.ms/powershell)
and the modules you intend to use (`ExchangeOnlineManagement`, `PnP.PowerShell`,
`MicrosoftTeams`).
---
## What you can ask
> *"Which mailboxes have full access delegated to someone outside their department?"*
> → `execute_exchange_online` with `Get-MailboxPermission`, no Graph equivalent exists.
> *"Show me every conditional access policy that does not require MFA, and our Secure Score."*
> → `generate_security_report` + `microsoft_graph_call`.
> *"Create a shared mailbox for invoicing and give the finance team access."*
> → classified as a write: the server describes the action and its consequences,
> and waits for your confirmation before doing anything.
---
## Tools
| Tool | What it does | Graph equivalent? |
|---|---|:---:|
| `microsoft_graph_call` | Any Microsoft Graph call (v1.0 or beta) | — |
| `azure_arm_call` | Any Azure Resource Manager call | — |
| `microsoft_search` | Unified Microsoft Search | — |
| `get_tenant_context` | Organisation, licences, Secure Score | — |
| `generate_security_report` | Tenant security posture | — |
| `execute_exchange_online` | Exchange Online cmdlets | ❌ none |
| `execute_pnp_sharepoint` | PnP PowerShell (SharePoint) cmdlets | ❌ none |
| `execute_teams_admin` | Teams Admin cmdlets | ❌ none |
| `execute_purview_compliance` | Purview / compliance cmdlets | ❌ none |
Restrict the surface with `--tools=microsoft_graph_call,get_tenant_context`, or
forbid every write with `--read-only`.
---
## Approval, and why it is not optional
A model reads untrusted content — an email subject, a site name, a document —
and decides which tool to call. Prompt injection is not hypothetical, and the
tools here can delete mailboxes.
So writes do not simply execute. Every action is classified `auto`, `confirmed`
or `critical` from the HTTP verb and path (or the PowerShell verb). Anything
above `auto` makes the server answer `input_required` under
[MCP's Multi Round-Trip Requests](https://modelcontextprotocol.io/specification/2026-07-28/basic/patterns/mrtr):
your client shows what is about to happen and its consequences, you confirm, and
only then does it run.
The confirmation is a signed mandate (HMAC) bound to the caller, the method, and
a digest of the arguments, valid five minutes. A mandate obtained to delete
`/users/1` cannot be replayed on `/users/999`, and cannot be presented by anyone
else.
If your client does not support elicitation, nothing is silently executed: the
server returns an error saying the action needs confirmation.
---
## Options
Every flag has an environment variable equivalent.
### Transport
| Flag | Env | Default | |
|---|---|---|---|
| `--stdio` | `MINERVA_TRANSPORT=stdio` | HTTP | JSON-RPC over stdin/stdout |
| `--port=<n>` | `MCP_SERVER_PORT` | `3001` | HTTP listen port |
In stdio mode **all logs go to stderr** — stdout belongs to the protocol.
### Authentication
| Flag | Env | |
|---|---|---|
| `--auth=<mode>` | `MINERVA_AUTH_MODE` | `bearer`, `device-code`, `client-secret` |
| `--tenant=<id>` | `MINERVA_TENANT_ID`, `AZURE_TENANT_ID` | Entra tenant |
| `--client-id=<id>` | `MINERVA_CLIENT_ID`, `AZURE_CLIENT_ID` | Application |
| `--client-secret=<s>` | `AZURE_CLIENT_SECRET` | App-only secret |
Defaults: `bearer` over HTTP (the caller supplies the token), `device-code` over
stdio, `client-secret` as soon as a secret is present. Each resource gets its own
token: Graph, ARM, Exchange, Purview, SharePoint and Teams do not share an
audience.
### Scope
| Flag | Env | |
|---|---|---|
| `--read-only` | `MINERVA_READ_ONLY` | Refuse every write |
| `--tools=<a,b,c>` | `MINERVA_TOOLS` | Register only these tools |
| `--ps=<mode>` | `MINERVA_PS_EXECUTOR` | `auto`, `redis`, `local`, `none` |
| `--sharepoint-url=<url>` | `MINERVA_SHAREPOINT_URL` | Required by the PnP tool locally |
| `--allowed-origins=<a,b>` | `MINERVA_ALLOWED_ORIGINS` | Browser origins allowed on `/mcp` |
| `--redis-url=<url>` | `REDIS_URL` | Queue for an external PowerShell runner |
| — | `MINERVA_MRTR_SECRET` | Signing key for approval mandates |
| — | `MINERVA_PWSH_PATH` | Path to `pwsh` |
| — | `MINERVA_REQUIRE_VERIFIED_TOKENS` | Refuse tokens whose signature cannot be verified |
`--read-only` reuses the same risk classifier as the approval gate, so it covers
PowerShell cmdlets too, not just HTTP verbs.
---
## Protocol support
Speaks **MCP 2026-07-28**, and still serves handshake-era clients until
**2027-07-28**.
| | |
|---|---|
| Stateless core (per-request `_meta`, no `initialize`, no `Mcp-Session-Id`) | ✅ |
| `server/discover` | ✅ |
| Multi Round-Trip Requests (`input_required`) | ✅ — this is the approval mechanism |
| `MCP-Protocol-Version` / `Mcp-Method` / `Mcp-Name` headers, base64 sentinel | ✅ validated against the body |
| `ttlMs` / `cacheScope` on lists | ✅ always `private` |
| Version negotiation (`-32022` listing supported versions) | ✅ |
| `Origin` validation (anti DNS-rebinding) | ✅ |
| GET/DELETE on the MCP endpoint | ✅ `405`, removed by this revision |
| Handshake era (`initialize`) | ✅ until 2027-07-28 |
One endpoint serves both generations; the request body decides which. Both read
the **same tool registry**, and a test compares them against a frozen contract so
they cannot drift.
---
## Security
Read [SECURITY.md](SECURITY.md) before deploying. The three things that matter:
1. **This server grants nothing.** It forwards a Microsoft token; Microsoft
authorizes. A restricted token gives a restricted server.
2. **Graph tokens cannot be verified by a third party** — they carry a `nonce`
header that only Graph can check. Their `tid`/`oid` claims are therefore
self-reported. The server validates them structurally, flags them
`verified: false`, and binds approval mandates to a **fingerprint of the token
itself** rather than to the claims alone. A token that *should* have verified
and did not is rejected.
3. **Local PowerShell mode isolates nothing.** Cmdlets run with the current
user's rights. Do not expose it to a caller you do not trust.
Cmdlet names and parameters never reach the script as code: they travel through
environment variables, the name is validated against a strict `Verb-Noun`
pattern, and the call goes through `Get-Command` plus the call operator.
Report a vulnerability privately — see [SECURITY.md](SECURITY.md).
---
## What this does not do
- **No process isolation** in local mode.
- **No multi-tenant brokering** — one server, one identity.
- **No signed audit ledger.** Audit events are JSON on stderr, not a signed,
offline-verifiable record.
Those three belong to [Minerva-IA](https://github.com/HoussemMak), the commercial
platform this server was extracted from. Everything in this repository works
without it.
---
## Development
```bash
npm install
npm run dev:stdio # stdio, hot reload
npm run verify # typecheck + tests + secret scan
npm run build && node scripts/smoke-stdio.mjs
```
Tests need no credentials and touch no network.
Pull requests are closed until 2027-07-31 — see [CONTRIBUTING.md](CONTRIBUTING.md)
for why, and for what is welcome in the meantime (bug reports very much are).
## Licence
MIT — see [LICENSE](LICENSE).