Part of: AI and search over SharePoint data

Agents on SharePoint data without a Graph app registration

Your request for a Graph app registration for an AI loader was refused. One read-only Sites.Selected grant to a replica, then a database role per tool, is easier to approve.

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:

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:

Terminal window
Grant-PnPEntraIDAppSitePermission -AppId "<spsync-client-id>" -DisplayName "SPSync" `
-Site https://contoso.sharepoint.com/sites/procurement -Permissions Read

The 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.

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 table
CREATE SCHEMA agent;
CREATE VIEW agent.contracts AS
SELECT 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_at
FROM sharepoint.proc_supplier_contracts c
LEFT JOIN sharepoint.sp_users u ON u.id = c.owner_user_id
WHERE c.sp_deleted_at IS NULL;
-- One login per tool, each holding SELECT on the view and nothing else
CREATE 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.

ToolReading SharePoint directlyReading the replica
LangChainSharePointLoader, configured from an app registration’s client id and secret, reading files from one document librarySQLDatabase from a connection string for langchain_agent
LlamaIndexSharePointReader, configured with client id, client secret and tenant id, loading files from a folderSQLDatabase with a text-to-SQL query engine, on its own login
Copilot StudioSharePoint knowledge that runs as each signed-in user, with Microsoft’s documented limits on list knowledgeWith 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
n8nThe SharePoint node’s OAuth2 credential, which on a self-hosted instance means your own Entra app registrationThe Postgres node with a Postgres credential for n8n_renewals
A Postgres MCP serverCannot read SharePoint; you would need a separate SharePoint MCP server with its own Graph permissionA 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.

Book a call

Book a call to talk through your use case, see further walkthroughs or demos, and set up a trial in your own environment.