Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions modules/ROOT/nav.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
**** xref:connect:managed/netsuite.adoc[NetSuite]
**** xref:connect:managed/openapi.adoc[OpenAPI]
**** xref:connect:managed/ramp.adoc[Ramp]
**** xref:connect:managed/sharepoint.adoc[SharePoint]
**** xref:connect:managed/slack.adoc[Slack]
**** xref:connect:managed/sql.adoc[SQL]
**** xref:connect:managed/workday.adoc[Workday]
Expand Down
4 changes: 4 additions & 0 deletions modules/connect/pages/managed/managed-catalog.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,10 @@ If any of these answers are "no," prefer xref:connect:register-remote.adoc[a sel
|Query, create, update, and delete Salesforce CRM records using SOQL and the REST API, and run and inspect saved Salesforce reports.
|

|*SharePoint*
|Read and write SharePoint sites, document libraries, lists, and files through the Microsoft Graph API.
|xref:connect:managed/sharepoint.adoc[See the deep-dive]

|*Text Chunker*
|Split and chunk text for RAG and LLM ingestion pipelines.
|
Expand Down
209 changes: 209 additions & 0 deletions modules/connect/pages/managed/sharepoint.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
= SharePoint Managed MCP Server
:description: Let agents read and write SharePoint sites, document libraries, lists, and files through the Microsoft Graph API, using each end-user's own Microsoft identity through user-delegated OAuth.
:page-topic-type: how-to
:personas: agent_builder, platform_engineer
:learning-objective-1: Register a Microsoft Entra app and an OAuth Provider for the SharePoint managed MCP server
:learning-objective-2: Create the SharePoint managed MCP server with the correct Microsoft Graph scopes
:learning-objective-3: Authorize a user and run the SharePoint tools from the Inspector or an agent

The *SharePoint* managed MCP server lets agents read and write SharePoint sites, document libraries, lists, and files through the https://learn.microsoft.com/en-us/graph/api/resources/sharepoint[Microsoft Graph API^]. Each agent caller authenticates against Microsoft with their own identity through user-delegated OAuth, so every call stays bounded by the signed-in user's own SharePoint permissions: the server never holds workspace-wide access.

After reading this page, you will be able to:

* [ ] {learning-objective-1}
* [ ] {learning-objective-2}
* [ ] {learning-objective-3}

[NOTE]
====
The SharePoint managed MCP server is in early access and is gated behind the `alpha` feature gate. It appears in the marketplace picker and is creatable only on deployments where early-access features are enabled. Contact Redpanda support if you don't see SharePoint in the picker.
====

== What this MCP server does

The SharePoint managed type wraps the Microsoft Graph API (`https://graph.microsoft.com/v1.0`) and authenticates per-user through the gateway's OAuth token vault. It exposes eight tools across sites, document libraries, lists, and files:

[cols="1,2"]
|===
|Tool |Description

|`list_sites`
|Search for SharePoint sites. The search term passes straight to Graph's `/sites?search=` endpoint. An empty search returns no results, so pass `*` to list every site or a name fragment to narrow the search.

|`get_site`
|Fetch a single site by ID or hostname path.

|`list_drive_items`
|Browse a site's default document library. Lists the library root by default; pass `folder_id` to descend into a folder.

|`get_file_content`
|Read the content of a file in a site's document library.

|`upload_file`
|Upload a file to a site's document library. Requires the `Sites.ReadWrite.All` scope.

|`search_content`
|Search content across a site by keyword.

|`list_lists`
|List the SharePoint lists on a site.

|`list_list_items`
|List the items in a SharePoint list.
|===

The file tools operate on the site's own drive (`/sites/{id}/drive/...`), never on a user's personal OneDrive.

== Prerequisites

Before you create the server, make sure you have:

* Access to a Microsoft SharePoint tenant.
* Permission to register an app in the https://entra.microsoft.com[Microsoft Entra admin center^] (or an Entra admin who can register one for you).
* A way to write the OAuth client secret into the ADP secret store.
* For most tenants, a tenant admin who can grant admin consent for the Microsoft Graph scopes. See <<grant-admin-consent,Grant admin consent>>.
* Familiarity with xref:connect:user-delegated-oauth.adoc[User-delegated OAuth].

== Register the Microsoft Entra app

In the Microsoft Entra admin center, go to *App registrations > New registration* and create an app:

. Give the app a descriptive name, for example `Redpanda AI Gateway - SharePoint MCP`.
. Set *Supported account types* to single tenant.
. Set the *Redirect URI* to platform *Web* with the gateway's OAuth callback for your dataplane:
+
[source,no-highlight]
----
https://aigw.<dataplane-id>.clusters.rdpa.co/oauth/v1/callback
----
+
Replace `<dataplane-id>` with your dataplane identifier. The callback path is `/oauth/v1/callback`.
. After registering, note the *Application (client) ID* and *Directory (tenant) ID* from the app's Overview page. You need both when you configure the OAuth Provider.

[TIP]
====
Single-tenant apps should use the tenant-specific Microsoft OAuth endpoints (`https://login.microsoftonline.com/<tenant-id>/oauth2/v2.0/...`), not the `/common/` endpoints, which can return `AADSTS` errors for single-tenant apps.
====

== Add Microsoft Graph permissions

In the app's *API permissions*, select *Add a permission > Microsoft Graph > Delegated permissions*, then add:

[cols="1,2"]
|===
|Scope |Why

|`Sites.ReadWrite.All`
|Read and write everything the server touches: sites, document libraries, files, lists, and `upload_file`. For read-only deployments, use `Sites.Read.All` instead, which drops `upload_file`.

|`offline_access`
|Issues refresh tokens so connections stay valid over time.
|===

The `openid`, `profile`, `email`, and `User.Read` scopes come from the OAuth Provider's standard scopes.

[IMPORTANT]
====
Use the *Microsoft Graph* permissions, not the legacy *Office 365 SharePoint Online* API in the picker. The server calls Microsoft Graph only.

Use the `Sites.*` scopes, not `Files.*`. Every endpoint the server calls is under `/sites/{id}/...`, which `Sites.ReadWrite.All` authorizes. `Files.ReadWrite.All` would additionally grant access to users' personal OneDrive, which the server never uses.
====

== Create a client secret

In the app's *Certificates & secrets*, select *New client secret*, give it a description, choose an expiry, and select *Add*. Copy the secret value immediately, because Microsoft shows it only once.

Store the secret in the ADP secret store under an `UPPER_SNAKE_CASE` key, for example `SHAREPOINT_CLIENT_SECRET`. The OAuth Provider references the secret by name, so the plaintext never enters the MCP server configuration.

[[grant-admin-consent]]
== Grant admin consent

The SharePoint Graph scopes are high-privilege delegated scopes, so most tenants require a tenant admin to consent before any user can connect. A Global Administrator, Privileged Role Administrator, or Cloud Application Administrator opens the app, selects *API permissions*, then selects *Grant admin consent for `<org>`*.

Admin consent does not escalate access: delegated scopes always stay bounded by the signed-in user's own SharePoint permissions. Admin consent approves the app to request the scopes so that individual users aren't each prompted. Without it, a non-admin user sees a "Need admin approval" message on the consent screen.

== Configure the OAuth Provider

Register an OAuth Provider in ADP that points at Microsoft's tenant-specific endpoints and references the client secret. See xref:connect:oauth-providers.adoc[Configure an OAuth Provider]. Use these values:

* *Authorization endpoint*: `https://login.microsoftonline.com/<tenant-id>/oauth2/v2.0/authorize`
* *Token endpoint*: `https://login.microsoftonline.com/<tenant-id>/oauth2/v2.0/token`
* *Client ID*: the Application (client) ID from the Entra app.
* *Client secret reference*: the secret-store key, for example `SHAREPOINT_CLIENT_SECRET`.
* *Token auth method*: client secret in the POST body, with PKCE enabled (matches the Microsoft preset).
* *Scopes*: `openid`, `email`, `profile`, `User.Read`, `Sites.ReadWrite.All`, `offline_access`.

== Create the server

Create a new SharePoint MCP server in the Agentic Data Plane UI:

. Open *MCP Servers > Create Server*.
. Pick *SharePoint* from the marketplace picker.
. Fill in the identity fields (`name`, `description`).
. Under *Auth*, select *User-delegated OAuth* and pick the SharePoint OAuth Provider you configured.
. Set *Required scopes* to `Sites.ReadWrite.All` (or `Sites.Read.All` for a read-only server).
. Click *Create*.

The `required_scopes` value is enforced against each user's connection. A connection with insufficient scopes returns `scope_upgrade_required`, and the user re-consents with the higher scope.

=== Configure from the CLI

[source,bash]
----
rpk ai mcp create --name sharepoint \
--description "SharePoint MCP over Microsoft Graph with per-user OAuth" \
--managed-config '{
"@type": "type.googleapis.com/redpanda.mcps.sharepoint.v1.SharePointMCPConfig",
"user_oauth": { "provider_name": "sharepoint", "required_scopes": ["Sites.ReadWrite.All"] }
}'
----

== Authorize a user and test

The SharePoint server uses per-user OAuth, so the first call from a user who hasn't connected yet returns an authorization prompt:

. Open the *Inspector* tab.
. Run a tool that requires the user's identity, for example `list_sites` with the argument `{"search": "*"}`.
. The first call returns `OAuthConnectionRequired` with a Microsoft `authorize_url`. The Inspector surfaces it as a consent prompt.
. Open the authorize URL in a browser signed in as the user, approve the Microsoft consent screen, and let Microsoft redirect back to the gateway callback. The token lands in the vault keyed to the user's identity.
. Re-run `list_sites`. It now returns the sites the user can see.

[TIP]
====
`list_sites` passes the search term straight to Graph's `/sites?search=` endpoint, which returns nothing for an empty search. Pass `*` to list every site, or a name fragment to narrow the search.
====

== Troubleshooting

[cols="1,2"]
|===
|Symptom |What to check

|SharePoint isn't in the marketplace picker
|The server is gated behind the `alpha` feature gate. Confirm that early-access features are enabled on your deployment, or contact Redpanda support.

|`OAuthConnectionRequired`
|First call from a user with no stored token. The user completes Microsoft's OAuth consent flow, the token lands in the vault, and subsequent calls reuse it. See xref:connect:user-delegated-oauth.adoc[User-delegated OAuth].

|"Need admin approval" on the Microsoft consent screen
|A tenant admin hasn't granted admin consent for the Graph scopes. See xref:connect:managed/sharepoint.adoc#grant-admin-consent[Grant admin consent].

|`scope_upgrade_required`
|The server's `required_scopes` was extended after users had already consented. Users re-consent with the higher scope.

|`AADSTS` errors during authorization
|A single-tenant app is using the `/common/` OAuth endpoints. Switch the OAuth Provider to the tenant-specific endpoints (`https://login.microsoftonline.com/<tenant-id>/oauth2/v2.0/...`).

|`list_sites` returns no sites
|An empty search returns nothing. Pass `*` to list every site, or a name fragment.

|`upload_file` fails with a permissions error
|`upload_file` requires the `Sites.ReadWrite.All` scope. A read-only server configured with `Sites.Read.All` cannot upload.
|===

== Related topics

* xref:connect:oauth-providers.adoc[Configure an OAuth Provider]
* xref:connect:user-delegated-oauth.adoc[User-delegated OAuth]
* xref:connect:create-server.adoc[Create an MCP Server]
* xref:connect:test-tools.adoc[Test a server's tools]
12 changes: 6 additions & 6 deletions modules/control/pages/budgets.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ After completing these steps, you will be able to:

A *budget* caps LLM spend over a recurring period. When an agent's spend for the period reaches the budget's hard limit, AI Gateway rejects that agent's next LLM request with `HTTP 429` until the period resets. A separate warning threshold fires before the cap is reached, so you can react before an agent is cut off.

ADP enforces budgets per agent. In a budget, an _agent_ is its service-account email: the same identity that appears as `user_id` in spend data.
ADP enforces budgets per agent. In a budget, an _agent_ is its service-account email: the same identity that appears as `agent_id` in spend data.

=== How a budget works

Expand Down Expand Up @@ -140,7 +140,7 @@ You don't view spend on this page. The *LLM Providers* page, dashboard, transcri
|Aggregated spend by *provider*, *model*, *user*, *agent*, or *provider type*, available through `GetSpendingBreakdown` for programmatic access. The dashboard's provider-breakdown widget covers the provider dimension.
|===

Every breakdown and time-series query reads from the same `SpendingFilter` shape: a time range plus optional `provider_name`, `model_id`, `user_id`, `agent_id`, or `organization_id` filters. Combine filters to scope a query (for example, "all spend on Anthropic for user `alice` in April"). You can break results down by provider, model, user, agent, or provider type; `organization_id` is a filter only, not a breakdown dimension.
Every breakdown and time-series query reads from the same `SpendingFilter` shape: a time range plus optional `provider_name`, `model_id`, `user_email`, `agent_id`, or `organization_id` filters. Combine filters to scope a query (for example, "all spend on Anthropic for user `alice` in April"). You can break results down by provider, model, user, agent, or provider type; `organization_id` is a filter only, not a breakdown dimension.

For more expressive queries, `SpendingFilter` also accepts an AIP-160 `filter` expression that lets you combine and negate dimensions in a single string (for example, `provider_name="anthropic" AND model_id!="claude-sonnet-4-6"`). The convenience fields and the `filter` expression compose; populate one or both. The server compiles the result to a single SQL `WHERE` clause, which avoids per-dimension fan-out queries.

Expand All @@ -149,7 +149,7 @@ For more expressive queries, `SpendingFilter` also accepts an AIP-160 `filter` e
`total_tokens` is derived server-side from input + output + cache tokens. You don't need to compute it client-side, and the value is consistent across all four `SpendingService` methods.
====

`user_id` and `organization_id` are populated automatically from the request's authenticated identity (the caller's email and organization), so spend is attributed without any setup on your part.
`user_email` and `organization_id` are populated automatically from the request's authenticated identity (the caller's email and organization), so spend is attributed without any setup on your part.

[[query-spend-programmatically]]
== Query spend programmatically
Expand Down Expand Up @@ -179,8 +179,8 @@ For more expressive queries, `SpendingFilter` also accepts an AIP-160 `filter` e
|`model_id`
|Restrict to one model identifier (`claude-sonnet-4-6`, `gpt-5.2`, and so on).

|`user_id`
|Restrict to one identified user. Anonymous traffic is excluded.
|`user_email`
|Restrict to one identified user, matched on the caller's email. Anonymous traffic is excluded.

|`agent_id`
|Restrict to one agent: the service-account email recorded on the on-behalf-of path. Leave it empty to match every row, including direct user calls; set it to scope spend to a single agent.
Expand Down Expand Up @@ -276,7 +276,7 @@ For the per-evaluator cost model and how it interacts with the dashboard's spend

== Multi-tenant viewing patterns

The `SpendingFilter` exposes `organization_id` and `user_id`, so every dashboard query and every API call can scope to a single tenant or user. Use this to:
The `SpendingFilter` exposes `organization_id` and `user_email`, so every dashboard query and every API call can scope to a single tenant or user. Use this to:

* See per-tenant spend in the dashboard's provider-breakdown view.
* Pull per-user cost reports through `GetSpendingBreakdown`.
Expand Down