Microsoft Graph scopes — what a deployment can add
The Microsoft connection is one delegated Graph grant per person (EaGraphAuth): it already owns
consent, the encrypted refresh token and the token refresh. A feature that needs another part of Graph
asks for one more scope on that grant. It does not need a package, a second app registration or a
second connection (maintainer, 2026-11-04).
Adding a scope
Set the key in the deployment configuration. Entries are separated by spaces, commas or semicolons:
Authentication:Microsoft:AdditionalGraphScopes = Files.Read.All Sites.Read.All
As an environment variable it is Authentication__Microsoft__AdditionalGraphScopes. A short name and
https://graph.microsoft.com/Files.Read.All are the same scope. An instance that sets nothing requests
exactly what it did before.
The key belongs to the instance's operator, not to the person at the consent screen, because the token it produces is the one every agent tool and script of that person uses.
🚨 Adding a scope asks every person on the instance to reconnect once, and until they do, their mail,
calendar and Teams connection reads as not connected. Entra refuses to redeem a refresh token for
scopes nobody consented to, so the connection shows the reconnect link (/auth/ea/connect) rather than
failing on every call. Plan the change. Removing a scope disturbs nobody: a refresh may ask for a
subset of what was consented.
Tenant policy still applies. A scope that needs an administrator's approval shows "Need admin approval" until a tenant administrator has consented once. The grant stays delegated: it never reaches further than the connected account's own permissions.
🚨 Try a new value on a non-production instance first. Only the shape of a name is checked, not
that Graph has such a permission: Microsoft publishes no list a portal could validate against, and a
copy here would refuse the next real permission. A misspelt Files.Read.Al passes, every existing
grant then reads as not connected, and the reconnect fails at Entra until the key is corrected.
What is refused
These are left out and logged as an error naming the key, because one bad entry would otherwise fail the whole token request and take the mailbox down with it:
- a scope of another resource: one token serves one resource;
.default, which cannot be mixed with named scopes;offline_accessand the OpenID scopes, which the connection owns already;- anything that is not a dotted permission name.
Reading SharePoint
Do not replicate. SharePoint already indexes its documents, so copying them into the mesh or building a second index adds a store that drifts. Search there, and read the file live:
POST https://graph.microsoft.com/v1.0/search/query
{ "requests": [ { "entityTypes": ["driveItem"], "query": { "queryString": "annual report 2026" } } ] }
GET https://graph.microsoft.com/v1.0/drives/{driveId}/items/{itemId}/content
The token comes from IEaGraphAuth.GetAccessToken(user).
The Executive Assistant's tools SearchSharePoint and ReadSharePointFile (ExecutiveAssistantPlugin,
module MeshWeaver.Mail.MicrosoftGraph) wrap exactly these two calls. The search returns names, drive
and item ids, links, sizes and snippets. ListSharePointFolder adds the one thing a search cannot
answer, everything in a folder: GET /drives/{driveId}/items/{itemId}/children, under the same scopes,
up to 200 direct entries with moreEntries true when the folder holds more. The read hands the model the file's text — a PDF, Word or
Excel file through the platform's text extractor, a text file decoded, any other binary file described
and not read — up to 20 MB, the listed size deciding before the stream is opened. A partly scanned PDF
is handed over as incomplete, naming the pages no OCR read. The read tool is [HiddenTool]: the chat
client drops a hidden tool's call and result instead of forwarding them to the thread's tool-call log,
on the agent's own round and on a round it received by hand-off alike, so a document's text reaches
the model within the round and never lands in the mesh. A 403 from Graph is answered with the two
scopes, this key and the reconnect link, because that is the operator's change, not the user's.
Writing to SharePoint: create-only, behind a switch
Writing is a separate decision from reading, and it is off unless the deployment turns it on (Plugins#4982, option 1: create-only, off by default):
SharePoint:AgentWrite = Write
As an environment variable it is SharePoint__AgentWrite. The switch does two things, and with any
other value (unset, true, 1, a typo) it does neither:
- The connection asks for
Files.ReadWrite.All.EaGraphAuth.RequestedScopesadds it throughEaGraphScopes.SwitchedScopes, so the operator does not list it inAdditionalGraphScopes(listing it as well requests it once). Like any added scope, every person on the instance reconnects once. - The Executive Assistant is handed
CreateSharePointFile(MailAgentOptions.SharePointWriteMode). Off, the tool is not in the model's toolset at all, and the method refuses if reached directly.
CreateSharePointFile(driveId, folderItemId, name, text | sourcePath) creates ONE new file and answers
with its link:
GET https://graph.microsoft.com/v1.0/drives/{driveId}/items/{folderId}:/{name}
PUT https://graph.microsoft.com/v1.0/drives/{driveId}/items/{folderId}:/{name}:/content?@microsoft.graph.conflictBehavior=fail
🔒 It never replaces a file. The by-name lookup refuses a name the folder already holds before a byte
is sent, and the upload repeats the guarantee at Graph: conflictBehavior=fail answers 409 for a name
that appeared in between, which the tool reports as "nothing was written". A wrong file is therefore
always a new file the person can delete. Overwrite would be irreversible and is not offered; it waits for
a per-write approval (an activity the person signs, as in Governed mail).
The content is either text (written as UTF-8) or sourcePath, a file already in the mesh
({space}/content/{subpath}/{file}), read server-side for the caller with the content route's own
access check, so binary bytes never pass through the model. One PUT, up to 4 MB. The grant is delegated:
a folder the person cannot write answers 403 at Graph, and the tool names the scope and the switch. The
tool is not hidden: a write outside the mesh is shown in the thread. Where the switch is on, the Mail
accounts view tells the person, in their language, that the assistant may create files and why Microsoft
asks for consent again.
What was not established
- No live search against a tenant has been run through this connection, so the scopes
/search/queryneeds fordriveItemare taken from Microsoft's documentation, not measured. - The two tools have not been run against a tenant either; their tests script Graph at the HTTP
boundary (
ExecutiveAssistantSharePointTests). CreateSharePointFilehas not been run against a tenant; its tests script Graph the same way (ExecutiveAssistantSharePointWriteTests). Whether a tenant's policy makesFiles.ReadWrite.Allneed an administrator's consent was not measured: Microsoft documents it as user-consentable, and tenant policy can override that.