# Reported issues for ms-graph-mcp

Pod holds 16 of 21 problems reported by people outside the maintainer team. Issues filed by the project's own owners, members and collaborators are excluded entirely — a maintainer's release checklist is not a warning to a prospective user.

Back to [ms-graph-mcp](/mcp/ms-graph-mcp).

## Most discussed

### Consolidate onto a single HTTP stack (httpx vs httpx2)

MCP SDK 2.0 depends on `httpx2`, a distribution separate from `httpx`. Both are currently
installed: the SDK uses `httpx2`, `client.py` uses `httpx`.

Deliberately left out of the SDK 2.0 migration — an HTTP-stack swap and a protocol migration
are two failure domains and did not belong in one change.

Spike first: confirm the `httpx2` API surface covers what `client.py` uses (streaming reads,
timeouts, `verify=`, `Retry-After` handling) before committing to the move.

[Read the thread](https://github.com/nitin27may/ms-graph-mcp/issues/25) · 2026-08-05 · open · 1 comment

### Dockerfile and GHCR publishing

Multi-stage build (uv build stage -> slim runtime), non-root user, `HEALTHCHECK` against
`/health`, resource-limit guidance.

Add the `ghcr` job to `release.yml`, which currently ends with a comment explaining why it is
absent: referencing a Dockerfile that does not exist would fail the first tagged release.

Note GitHub Packages has no Python registry — GHCR is the container registry, and PyPI remains
the only place the wheel can live.

[Read the thread](https://github.com/nitin27may/ms-graph-mcp/issues/24) · 2026-08-05 · closed · 1 comment

### Client configuration documentation

`docs/clients.md`, one section per client, all pointing at the same server:

- **VS Code** — `.vscode/mcp.json`, both tiers. Local: `type: "stdio"` with `uvx`. Hosted:
  `type: "http"` with an `oauth` block carrying `clientId`, using the `inputs` array and
  `${input:...}` substitution so no secret is ever hardcoded.
- **Claude Code** — `claude mcp add`, plus the Entra DCR caveat for the hosted tier
- **Claude Desktop** — `claude_desktop_config.json`, stdio
- **MCP Inspector** — as the neutral c

[Read the thread](https://github.com/nitin27may/ms-graph-mcp/issues/23) · 2026-08-05 · closed · 1 comment

### Testing and debugging documentation

`docs/testing.md` — the existing pytest patterns (contract tests, the `entra/` RS256
fixtures, `asyncio_mode=auto`, the in-process `mcp.Client` conformance suite) plus **MCP
Inspector**, the reference tool. Node 22.19+, three clients behind one binary: web, `--cli`,
`--tui`. Include an Inspector CLI `tools/list` smoke check for CI — it exercises a real client
and catches protocol regressions pytest cannot.

`docs/debugging.md` — reading `[Graph]` client logs, the OTEL spans, the structured error

[Read the thread](https://github.com/nitin27may/ms-graph-mcp/issues/22) · 2026-08-05 · closed · 1 comment

### Consolidate meetings.py onto client.py (14 raw httpx clients)

`meetings.py` holds 14 hand-rolled `httpx.AsyncClient` instances — by far the largest
concentration left. Every one bypasses the OTEL span, the `[Graph]` error logging and the TLS
toggle, so transcript retrieval is the least observable part of the server despite being its
most-used feature.

`graph_post_no_content` and `graph_post_raw` have already removed the legitimate reasons for
an inline client. Anything genuinely not expressible through `client.py` earns a helper there
instead.

Being hand

[Read the thread](https://github.com/nitin27may/ms-graph-mcp/issues/21) · 2026-08-05 · closed · 1 comment

### OAuth resource server: PRM, WWW-Authenticate, audience binding

Implement the MCP authorization spec so **any** conforming client can authenticate without
client-specific configuration — not just one client's convention.

- `GET /.well-known/oauth-protected-resource` (RFC 9728) — unauthenticated, like `/health`
- **401 with `WWW-Authenticate: Bearer resource_metadata="...", scope="..."`**. Today auth.py
  returns a bare JSON error, which gives a client nothing to discover from. This one header is
  what lets a spec-compliant client self-configure.
- **403 wi

[Read the thread](https://github.com/nitin27may/ms-graph-mcp/issues/20) · 2026-08-05 · closed · 1 comment

### Contacts tools

The user's personal address book is not reachable.

- `people_list_contacts` — `GET /me/contacts` (READ)
- `people_search_contacts` (READ)
- `people_create_contact` — `POST /me/contacts` (WRITE)

**Not a duplicate of `people_search`, and the descriptions must say so.** `/me/people` is the
relevance-ranked People API over colleagues the user interacts with. `/me/contacts` is the
user's saved address book, and it includes external contacts who are not in the tenant
directory at all — invisible to 

[Read the thread](https://github.com/nitin27may/ms-graph-mcp/issues/18) · 2026-08-05 · closed · 1 comment

### OneNote page read tools

The server can write a OneNote page but cannot read one back — `notes_create_page` exists
with no counterpart.

- `notes_list_pages` — `GET /me/onenote/sections/{id}/pages` (READ)
- `notes_get_page_content` — `GET /me/onenote/pages/{id}/content` (READ)

Page content is HTML, not JSON, so it uses `client.py:graph_get_text` — the same helper the
transcript VTT path already uses. Requires `Notes.Read`.

[Read the thread](https://github.com/nitin27may/ms-graph-mcp/issues/17) · 2026-08-05 · closed · 1 comment

## Most recent

### Make the HTTP transport a spec-conformant OAuth resource server (agent → MCP → Graph OBO)

## Summary

Make the HTTP transport a spec-conformant OAuth 2.1 resource server, so an agent acting for a
signed-in user can call this server and **the server mints its own Microsoft Graph token via
on-behalf-of**. The user's Graph token is never handed around.

Most of this flow already exists behind `GRAPH_MCP_DOES_OBO=true`. It is off by default, and the
default posture is the problem.

## The problem

With `GRAPH_MCP_DOES_OBO=false` (today's default) the server accepts a token whose `aud` is

[Read the thread](https://github.com/nitin27may/ms-graph-mcp/issues/29) · 2026-08-05 · open · 0 comments

### Replace X-Write-Scope with a 403 insufficient_scope challenge

Split out of #20, which delivered the discovery half (RFC 9728 metadata + `WWW-Authenticate` on 401). This is the remaining piece, deliberately deferred because it is a breaking change to a working gate and had no business riding along with the change that introduced discovery.

## What

The write tier is currently opted into with `X-Write-Scope: true`. A custom header only works for a client that has been told about it, which is the opposite of the point of #20 — a spec-compliant client should 

[Read the thread](https://github.com/nitin27may/ms-graph-mcp/issues/26) · 2026-08-05 · open · 0 comments

### Toolset profiles: startup default with per-connection narrowing

Deferred by decision until the tool surface is complete, then decided on measured evidence
rather than a guess.

Design already settled:
- `GRAPH_MCP_TOOLSETS` selects namespaces at startup; an `X-Toolsets` header or `?toolsets=`
  query parameter may narrow further but **never widen** — the startup value is the ceiling
- Namespaces follow permission-scope families, which the Phase B renames already established,
  so filtering is a prefix match
- Tool visibility is a context-efficiency lever, no

[Read the thread](https://github.com/nitin27may/ms-graph-mcp/issues/19) · 2026-08-05 · closed · 0 comments

### Unified search tool over POST /search/query

One endpoint searches messages, events, driveItems, lists, listItems, sites and people, plus
Copilot-connector external data that nothing else in Graph reaches. Highest coverage per tool
available.

- `search_query` — entity types selectable by the caller
- `search_sharepoint_content` — narrowed to `driveItem` / `listItem` / `site`

Note `chat_search_messages` already calls `/search/query` for `chatMessage`, so this
generalises an existing pattern rather than introducing one.

[Read the thread](https://github.com/nitin27may/ms-graph-mcp/issues/16) · 2026-08-05 · closed · 1 comment

### Teams chat tools: 1:1 and group chats

Teams coverage looks complete but is channel-shaped. 1:1 and group chats — where most Teams
conversation actually happens — are entirely absent.

- `chat_list` — `GET /me/chats` (READ)
- `chat_list_messages` — `GET /chats/{id}/messages` (READ)
- `chat_send_message` — `POST /chats/{id}/messages` (WRITE)
- `chat_list_members` — `GET /chats/{id}/members` (READ)

`Chat.Read` and `ChatMessage.Send` are already in the permission set, so this is purely
unimplemented.

**Gotcha:** Microsoft imposes a on

[Read the thread](https://github.com/nitin27may/ms-graph-mcp/issues/15) · 2026-08-05 · closed · 1 comment

### Mail action tools: reply, reply-all, forward, mark read

`mail_send` exists but `reply` does not, which is backwards for an assistant — replying
in-thread is both more common and safer than composing a new message to addresses the model
chose.

- `mail_reply`, `mail_reply_all`, `mail_forward` (WRITE, `Mail.Send`)
- `mail_mark_read` (WRITE, `Mail.ReadWrite`)

All four are action endpoints returning 202 with an empty body, so they use
`client.py:graph_post_no_content`.

[Read the thread](https://github.com/nitin27may/ms-graph-mcp/issues/14) · 2026-08-05 · closed · 1 comment

### Task write tools: To Do and Planner

"Mark it done" is the most-used task verb and is missing on both platforms.

- `tasks_complete_todo`, `tasks_update_todo`
- `tasks_create_planner`, `tasks_update_planner`, `tasks_complete_planner`

**Planner writes are the fiddly part.** Every `POST`/`PATCH`/`DELETE` requires `If-Match`
carrying the current `@odata.etag` and returns 409/412 on conflict, so each tool is
read-then-write with a retry. `client.py:graph_patch` already accepts `extra_headers`, and
`errors.py:conflict()` is the respons

[Read the thread](https://github.com/nitin27may/ms-graph-mcp/issues/13) · 2026-08-05 · closed · 0 comments

### Calendar write and scheduling tools

Booking is the largest functional hole in the surface — the calendar is readable but nothing
can be scheduled.

- `calendar_create_event` (WRITE) — `POST /me/events`
- `calendar_update_event` (WRITE) — `PATCH /me/events/{id}`
- `calendar_cancel_event` (WRITE, `destructiveHint: true`) — notifies attendees
- `calendar_respond_to_event` (WRITE) — accept / decline / tentative
- `calendar_find_meeting_times` (**READ**) — `POST /me/findMeetingTimes`
- `calendar_get_free_busy` (**READ**) — `POST /me/ca

[Read the thread](https://github.com/nitin27may/ms-graph-mcp/issues/12) · 2026-08-05 · closed · 0 comments

The remaining reports are on [the project's issue tracker](https://github.com/nitin27may/ms-graph-mcp/issues).
