Tags: Relational databasesAIDesktopCommand line
The reply on the ticket was two lines long. “We don’t grant Sites.Read.All to scripts. Closing.” You had asked for an app registration so a LangChain agent could answer questions about the Supplier Contracts list on the Procurement site, and the Entra admin said no without asking for a meeting.
We have sat on both sides of that ticket. The analyst wants to type “which contracts renew before the end of March, and who owns each one?” and get an answer from the list the procurement team already keeps up to date. The admin sees a new tool, written by someone outside IT, asking for a permission that reads every site in the tenant. Both of them are being reasonable, and the way through is to change what gets asked for. What we have seen work is one narrow read grant to a replication tool, then a database login for each AI tool that wants the data.
Why tenant admins refuse SharePoint app registrations
Start with what the request said. Microsoft’s Graph permissions reference gives the application permission Sites.Read.All the display text “Read items in all site collections”. Its description reads “Allows the app to read documents and list items in all site collections without a signed in user.” That covers the HR site, the board papers, every project site and, somewhere in there, the Supplier Contracts list you wanted. An admin who approves it is trusting your script with all of them, and trusting whoever ends up holding its client secret.
The tool itself gives the admin nothing to narrow it with. LangChain’s SharePointLoader is set up with a client id and secret from an app registration plus a document library id, and it reads the files in that library. LlamaIndex’s SharePointReader has the same shape: client id, client secret and tenant id, then a folder to load. Neither one reads list items, so for a contracts list you would have been writing Graph calls by hand on top of the loader anyway. Either way the boundary is the Graph permission, and the permission the setup guides start from is tenant-wide.
Then there is the question every admin expects. Six months after approval, someone from data protection asks what the agent read. Entra’s sign-in logs show that the app got tokens. Graph activity logs, in tenants that have switched them on, show request URLs. Turning either into a list of which supplier records went to a language model on which day is slow work, and it falls to the admin who said yes. For the scope problem at least, Microsoft offers a narrower kind of permission, though few AI tools mention it in their setup steps.
Sites.Selected and other Selected scopes for one site or list
Microsoft Learn’s page “Overview of Selected permissions in OneDrive and SharePoint”
(learn.microsoft.com/en-us/graph/permissions-selected-overview, checked 20 October
2026) lists four Selected scopes. The two that matter here are Sites.Selected, which
works at the level of one site collection, and Lists.SelectedOperations.Selected, which
works on one list. The page is clear about how little consent alone gives: “an
application consented for Lists.SelectedOperations.Selected would initially have no
access.”
Access needs three steps, and Microsoft spells them out. The application is consented
in Entra for the Selected scope. Someone then grants it a role on a specific site or list
with a POST to that resource’s permissions endpoint. Finally the application’s token
has to carry the scope. “If any of the three steps are missed, the application doesn’t
have access.” For an admin this is the property that matters. Consent alone opens
nothing, and every site or list becomes a separate decision the admin can see and undo.
You could ask for Sites.Selected for the LangChain loader, and some admins will agree. In our experience the loaders’ setup guides are written for the tenant-wide scopes, and whether a given loader behaves under a Selected grant is something you discover by testing it. After that you repeat the conversation for the n8n flow and for every tool that arrives later, each with its own secret to rotate and its own grant to track.
The one grant SPSync needs
SPSync reads SharePoint through one multi-tenant Entra app that we own, so you consent to
it and nobody in your tenant registers anything. To try it, you sign in with delegated
access (Sites.Read.All, User.Read and offline_access), and SPSync sees only what
you can already see. Any user can consent to that for themselves unless the tenant
requires admin consent for every app. Delegated sync stops when your refresh token
expires after about 90 days idle, so for a schedule that runs overnight you ask the
admin for application access.
That request is small, and the desktop app writes it for you. On the Connect screen the permissions wizard produces an admin permissions pack, in Markdown or PDF, with your tenant, the app’s client id and the sites and lists you picked already filled in. It offers the tightest options first:
Lists.SelectedOperations.Selectedon each list SPSync replicates, read only. The app can touch those lists and nothing else on the site. Microsoft and the pack both warn that this breaks the list’s permission inheritance.Sites.Selectedon each site, read only. The app reads every list and library on that site and nothing on any other site, and inheritance stays as it is.
Tenant-wide Sites.Read.All is the fallback when neither fits, and our admin permissions guide calls it the grant security teams push back on. For the procurement agent we would pick Sites.Selected on the Procurement site. The admin does two things. They open the admin-consent link in the pack and approve the application permission on SPSync’s app. Then they grant the site, with one line of PnP PowerShell:
Grant-PnPEntraIDAppSitePermission -AppId "<spsync-client-id>" -DisplayName "SPSync" ` -Site https://contoso.sharepoint.com/sites/procurement -Permissions ReadThe Graph equivalent is a POST to /sites/{site-id}/permissions with
"roles": ["read"]. If the person running the wizard already holds
Sites.FullControl.All, or Sites.Selected with Full Control on that site, SPSync makes
the grant with that person’s sign-in. If not, it prints the command for the admin to run.
The longer walk-through, with the certificate, the list-level grant and the CLI flags, is
on Connect to SharePoint.
What the admin sees on the consent screen
The consent prompt names the app and lists the permission in Microsoft’s own words. For Sites.Selected the display text is “Access selected site collections”, and the description under it reads “Allow the application to access a subset of site collections without a signed in user. The specific site collections and the permissions granted will be configured in SharePoint Online.” The list-level scope shows “Access selected Lists without a signed in user.” Both require admin consent. We checked these strings against the Microsoft Graph permissions reference on 20 October 2026.
Point the admin at that description, because it answers the first objection. After
consent the app can read nothing. Access exists on the sites the admin grants, each grant
can be listed with GET /sites/{site-id}/permissions or
Get-PnPAzureADAppSitePermission, and any one of them can be removed with a DELETE or
Revoke-PnPAzureADAppSitePermission without contacting us. Withdrawing the consent in
Entra cuts off every granted site at once.
What SPSync never asks for
There is no write role in any grant SPSync requests. It does not ask for
Sites.ReadWrite.All or Sites.FullControl.All, and it asks for nothing outside SharePoint
except User.Read for the signed-in person in delegated mode. It holds no permission for
mail or for Teams chats. On the granted sites it reads the lists you picked, plus the
hidden User Information List it needs to put names to person columns. The product has no
call that writes to SharePoint at all.
One thing to tell the admin up front is that the read role on a site is wider than what SPSync uses. The Selected overview defines read as “Read the metadata and contents of the resource”, so a Sites.Selected read grant would permit file downloads on that site. SPSync replicates list items and library metadata and never fetches file bytes. When the site also holds documents the admin wants kept out of any app’s reach, a list-level grant on Supplier Contracts alone is the tighter answer, at the cost of broken inheritance on that one list.
How do you give each AI tool its own database role?
After the first sync the list is a table in your database, for example
sharepoint.proc_supplier_contracts in Postgres, one row per item. The Supplier lookup
becomes supplier_id and supplier_value, the Owner person column becomes
owner_user_id joined to a shared sp_users table, and a deleted item keeps its row
with sp_deleted_at set. From here every AI tool is a database login, created by
whoever runs the database, with no ticket to Entra at all.
-- As the account SPSync writes with, so the view can read the tableCREATE SCHEMA agent;CREATE VIEW agent.contracts ASSELECT c.sp_item_id AS contract_id, c.title AS contract, c.supplier_value AS supplier, c.category, c.renewal_date, c.annual_value, u.display_name AS owner, c.sp_synced_at AS synced_atFROM sharepoint.proc_supplier_contracts cLEFT JOIN sharepoint.sp_users u ON u.id = c.owner_user_idWHERE c.sp_deleted_at IS NULL;
-- One login per tool, each holding SELECT on the view and nothing elseCREATE ROLE langchain_agent LOGIN PASSWORD 'change-me';CREATE ROLE n8n_renewals LOGIN PASSWORD 'change-me';GRANT USAGE ON SCHEMA agent TO langchain_agent, n8n_renewals;GRANT SELECT ON agent.contracts TO langchain_agent, n8n_renewals;ALTER ROLE langchain_agent SET default_transaction_read_only = on;Neither role can see the sharepoint schema or any other list, and no view exposes
owner email addresses. When the facilities buyers’ agent should see facilities contracts
only, give it its own view with WHERE category = 'Facilities', or switch on Postgres
row-level security with a policy per role. On SQL Server the same idea is GRANT SELECT ON SCHEMA::agent to a database user plus a security policy where rows need filtering.
Revoking a tool takes one statement, ALTER ROLE n8n_renewals NOLOGIN to refuse its
next connection or a REVOKE on the view. That changes nothing in Entra or SharePoint
and touches no other tool. Auditing also moves to a place your DBA already watches. The
pgaudit extension can log every SELECT a given role runs (set pgaudit.log to read
for that role), and SQL Server and Azure SQL do the same with a database audit
specification on SELECT ON SCHEMA::agent BY langchain_agent. The log records the query
text and the time. It does not record the rows returned, though with a view this narrow
the query says most of what a data protection officer needs.
LangChain, LlamaIndex, Copilot Studio and n8n on a SharePoint replica
The right-hand column assumes the Postgres replica and the roles above. On SQL Server the connection strings and drivers change, and the grants work the same way.
| Tool | Reading SharePoint directly | Reading the replica |
|---|---|---|
| LangChain | SharePointLoader, configured from an app registration’s client id and secret, reading files from one document library | SQLDatabase from a connection string for langchain_agent |
| LlamaIndex | SharePointReader, configured with client id, client secret and tenant id, loading files from a folder | SQLDatabase with a text-to-SQL query engine, on its own login |
| Copilot Studio | SharePoint knowledge that runs as each signed-in user, with Microsoft’s documented limits on list knowledge | With the replica in Azure SQL, the Azure SQL real-time knowledge connector (preview at the time of writing), where each user signs in to the database |
| n8n | The SharePoint node’s OAuth2 credential, which on a self-hosted instance means your own Entra app registration | The Postgres node with a Postgres credential for n8n_renewals |
| A Postgres MCP server | Cannot read SharePoint; you would need a separate SharePoint MCP server with its own Graph permission | A connection string for a read-only role |
Copilot Studio is the honest exception. It does not need an app registration of your
own, because it reads SharePoint as each user, so the admin conversation is different.
Its limits are about counting and joining, and
why Copilot cannot count your rows
covers them. For Claude Desktop or another MCP client, point any Postgres MCP server at
the replica with the read-only role, or run spsync mcp serve, SPSync’s own read-only
MCP server over the replica. An MCP server for SharePoint data that queries SQL
walks through that setup end to end.
What the replica does not cover
SPSync does not copy SharePoint item permissions into the replica. The table holds items and their columns, and nothing about who was allowed to open each item. The agent sees what its database role is granted, so scoping happens through the lists you replicate and the views and row-level security policies you write. If the Supplier Contracts list hides some rows from some people with item-level permissions, rebuild that rule as a policy before an agent reads the table. The custom agent governance resource puts these controls next to Copilot’s own for a security review.
File contents are not replicated either. A document library arrives as metadata, one row per document with its content type and custom columns, and the text of each contract PDF stays in SharePoint. An agent that has to read the contracts themselves still needs a route to the files, and that route needs a Graph permission of its own. Say so to the admin at the start, so that a second request later is no surprise.
The replica is also as current as its last sync. After the first full load each run
reads only what changed, so on a short schedule the table usually trails the list by a
few minutes. Put synced_at in every view and ask the agent to quote it.
To set it up, start from the database you already run, SharePoint to PostgreSQL or SharePoint to SQL Server, and send your admin the admin permissions guide along with the pack the wizard generates. Giving agents SQL tools over SharePoint data covers the editions, and licensing explains the per-installation, per-destination-family licence.