cd /news/developer-tools/giving-an-ai-assistant-read-only-acc… · home topics developer-tools article
[ARTICLE · art-64276] src=dev.to ↗ pub= topic=developer-tools verified=true sentiment=· neutral

Giving an AI assistant read-only access to Microsoft Loop — without breaking permissions

A developer built an open-source MCP server called loop-reader-mcp to give AI assistants read-only access to Microsoft Loop pages without breaking permissions. The solution splits discovery and retrieval across two identities: user-level OBO tokens for search and app-only identity for content retrieval, gated by a per-user cache to prevent permission flattening.

read4 min views1 publishedJul 18, 2026

I wanted my AI assistant to read my team's Microsoft Loop pages — summarize a

workspace, pull the latest OKRs into a draft, answer "what did we decide about

X." Simple ask. It turned into a genuinely interesting engineering problem, and

I ended up building a small open-source MCP server to solve it. Here's the

challenge, the solution, and the traps along the way.

The result is open source (MIT): ** loop-reader-mcp**.

Two walls, right at the start.

Wall 1: Microsoft Loop has no content API. As of mid-2026, there's no

Graph endpoint to read or write a Loop page's content — no "get page," no

"list workspace." Loop is positioned against Notion and Confluence, but you

can't programmatically get your own content out in a structured way. The 2026

Loop roadmap is about governance, not a content API.

Wall 2: where Loop actually stores things. Loop workspace pages live in

SharePoint Embedded (SPE) containers, and Loop components from Teams/Outlook

live as .loop

files in OneDrive. There is a documented trick: Microsoft

Graph can convert a .loop

file to HTML on the fly with

GET /drives/{id}/items/{id}/content?format=html

. So reading is possible

through the file layer even without a Loop API.

But then the real problem showed up — the one worth writing about.

SharePoint Embedded does not accept delegated (per-user) tokens for content

downloads. Only an app-only identity can fetch the bytes.

If you build the naive thing — a service that uses its app identity to read

Loop — you've created a permission-flattening machine. The app can read

everything, so anyone who can talk to your service can read any Loop page in

the tenant, regardless of what they personally have access to. That's a data

leak with extra steps. Unacceptable.

So the question became: how do you let a service read content with an app identity, while guaranteeing each user only sees what they're allowed to see?

The insight that cracked it: Graph Search is security-trimmed for delegated callers, even for SPE content. Search respects the user's permissions; only

So I split the two operations across two identities:

Discovery runs as the user. When the assistant searches Loop, the server

exchanges the user's token via the OAuth On-Behalf-Of (OBO) flow and

calls Graph Search as that user. Microsoft trims the results to exactly

what they can access. The server records the (driveId, itemId)

of every

hit in a short-lived, per-user cache.

Retrieval is gated by that discovery. When the assistant asks to read a

page, the server refuses unless that exact (driveId, itemId)

pair is in

this user's cache — i.e. unless they personally just discovered it via

their own trimmed search. Only then does it use the app identity to fetch

and convert the bytes.

search  -- OBO (user identity) --> Graph Search  -> results trimmed by Microsoft
                                                  -> (driveId,itemId) cached per user
read    -- is this pair in the caller's cache? --> no  -> refuse (no Graph call)
                                                   yes -> app identity -> ?format=html

The authorization decision is Microsoft's, not mine. A user can't discover

a page they can't access (search runs as them), and can't read a page they

didn't discover. The app identity is just a retrieval mechanism for bytes the

user already proved they can see. Permission flattening solved.

I exposed this as a remote Model Context Protocol server with three

read-only tools: loop_search

, loop_list_components

, and loop_get_page

.

Read-only isn't a policy toggle — the Graph client only permits GET

and

POST /search/query

, so there's structurally no way to write. (Good, because

overwriting a .loop

file with anything else corrupts it, and there's no

supported write API anyway.)

For auth, I initially tried to put a platform "easy auth" gateway in front of

the server. Big mistake — the gateway intercepted the OAuth handshake and

the MCP client could never discover where to log in. The fix was to make the

server its own OAuth resource server: it publishes the discovery documents

(/.well-known/oauth-protected-resource

and /.well-known/oauth-authorization-server

)

pointing clients at Microsoft Entra, and it validates the incoming token itself

(signature via Entra's JWKS, audience, issuer, expiry). No gateway, no

interception, and access is still fully gated because Entra only issues tokens

to users assigned to the app.

(driveId, itemId)

pairs, not just the item IDThe code is on GitHub under MIT: ** github.com/DenizV/loop-reader-mcp**. The README covers the

SECURITY.md

documents the model andIt's community code, not a certified product — review it and run a dependency

scan before you point it at real data. But if you've been wanting to let an

assistant read your Loop content without handing it the keys to the whole

tenant, this pattern works.

If you build on it or find a sharper approach, I'd love to hear it.

── more in #developer-tools 4 stories · sorted by recency
── more on @microsoft loop 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
Live at https://your-agent.zahid.host
Get free account → Pricing
from €0/mo · no card required
LIVE [news/giving-an-ai-assista…] indexed:0 read:4min 2026-07-18 ·