125 lines
4.9 KiB
Markdown
125 lines
4.9 KiB
Markdown
|
|
# Setup: Azure AD app registration for app-only SharePoint access
|
|||
|
|
|
|||
|
|
This skill authenticates as an **application** (no user sign-in) using the OAuth2
|
|||
|
|
client-credentials flow. You create an Entra (Azure AD) app registration once,
|
|||
|
|
grant it permission to SharePoint, and hand the skill three secrets.
|
|||
|
|
|
|||
|
|
You need an Entra **admin** (or someone who is) to grant admin consent and, for
|
|||
|
|
the least-privilege option, to grant the app access to specific sites.
|
|||
|
|
|
|||
|
|
## 1. Register the application
|
|||
|
|
|
|||
|
|
1. Go to <https://entra.microsoft.com> → **Identity** → **Applications** →
|
|||
|
|
**App registrations** → **New registration**.
|
|||
|
|
2. Name it something recognizable, e.g. `claude-sharepoint-lists`.
|
|||
|
|
3. Supported account types: **Accounts in this organizational directory only**
|
|||
|
|
(single tenant) is correct for app-only.
|
|||
|
|
4. Leave **Redirect URI** blank — client-credentials doesn't use one.
|
|||
|
|
5. **Register**.
|
|||
|
|
|
|||
|
|
On the app's **Overview** page, copy:
|
|||
|
|
- **Application (client) ID** → `SP_CLIENT_ID`
|
|||
|
|
- **Directory (tenant) ID** → `SP_TENANT_ID`
|
|||
|
|
|
|||
|
|
## 2. Create a client secret
|
|||
|
|
|
|||
|
|
1. App → **Certificates & secrets** → **Client secrets** → **New client secret**.
|
|||
|
|
2. Set an expiry (e.g. 6–12 months — note when it expires; you'll have to rotate).
|
|||
|
|
3. Copy the secret **Value** immediately (it's only shown once) → `SP_CLIENT_SECRET`.
|
|||
|
|
|
|||
|
|
> Certificates are more secure than secrets for production. This skill uses a
|
|||
|
|
> secret for simplicity; swapping to a certificate is a future enhancement.
|
|||
|
|
|
|||
|
|
## 3. Grant a Microsoft Graph application permission
|
|||
|
|
|
|||
|
|
App → **API permissions** → **Add a permission** → **Microsoft Graph** →
|
|||
|
|
**Application permissions**. Pick **one** of:
|
|||
|
|
|
|||
|
|
| Permission | Scope | Use when |
|
|||
|
|
|---|---|---|
|
|||
|
|
| **`Sites.Selected`** | Only sites an admin explicitly grants (see step 4) | **Preferred.** Least privilege — the app can touch only the sites you allow. |
|
|||
|
|
| `Sites.ReadWrite.All` | Read/write items in **all** site collections | Simpler, but broad. Use only if `Sites.Selected` isn't workable. |
|
|||
|
|
|
|||
|
|
Then click **Grant admin consent for <tenant>** and confirm the status shows
|
|||
|
|
a green check. Without consent, every call returns `401`/`403`.
|
|||
|
|
|
|||
|
|
## 4. (Only for `Sites.Selected`) Grant the app access to a site
|
|||
|
|
|
|||
|
|
`Sites.Selected` grants nothing until an admin authorizes the app on each site.
|
|||
|
|
Do this per site you want the skill to use. Two ways:
|
|||
|
|
|
|||
|
|
**Option A — PnP PowerShell (simplest for admins):**
|
|||
|
|
|
|||
|
|
```powershell
|
|||
|
|
Install-Module PnP.PowerShell -Scope CurrentUser # once
|
|||
|
|
Connect-PnPOnline -Url "https://contoso.sharepoint.com/sites/Marketing" -Interactive
|
|||
|
|
Grant-PnPAzureADAppSitePermission `
|
|||
|
|
-AppId "<SP_CLIENT_ID>" `
|
|||
|
|
-DisplayName "claude-sharepoint-lists" `
|
|||
|
|
-Site "https://contoso.sharepoint.com/sites/Marketing" `
|
|||
|
|
-Permissions Write # Read | Write | Manage | FullControl
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**Option B — Microsoft Graph REST** (e.g. via Graph Explorer signed in as an
|
|||
|
|
admin with `Sites.FullControl.All` delegated). First get the site id, then grant:
|
|||
|
|
|
|||
|
|
```http
|
|||
|
|
GET https://graph.microsoft.com/v1.0/sites/contoso.sharepoint.com:/sites/Marketing
|
|||
|
|
POST https://graph.microsoft.com/v1.0/sites/{siteId}/permissions
|
|||
|
|
Content-Type: application/json
|
|||
|
|
|
|||
|
|
{
|
|||
|
|
"roles": ["write"],
|
|||
|
|
"grantedToIdentities": [
|
|||
|
|
{ "application": { "id": "<SP_CLIENT_ID>", "displayName": "claude-sharepoint-lists" } }
|
|||
|
|
]
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Use `read` instead of `write` for read-only access to that site.
|
|||
|
|
|
|||
|
|
## 5. Give the skill the credentials
|
|||
|
|
|
|||
|
|
Copy `.env.example` to `.env` in the skill directory and fill in the three
|
|||
|
|
values (and optionally `SP_SITE_URL`). The CLI loads `.env` automatically, and
|
|||
|
|
`.env` is git-ignored so secrets aren't committed. Alternatively, export them as
|
|||
|
|
environment variables.
|
|||
|
|
|
|||
|
|
## 6. Verify
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
node scripts/sp.mjs test --site "https://contoso.sharepoint.com/sites/Marketing"
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Expected: `{ "ok": true, "siteId": "...", "listCount": N, "lists": [...] }`.
|
|||
|
|
|
|||
|
|
Troubleshooting:
|
|||
|
|
- **Token request failed (401)** — wrong tenant id, client id, or secret (or the
|
|||
|
|
secret expired). Re-copy from the portal.
|
|||
|
|
- **403 accessDenied** — admin consent not granted (step 3), or with
|
|||
|
|
`Sites.Selected` the site wasn't granted to the app (step 4).
|
|||
|
|
- **Works for `test` (no site) but 403 with `--site`** — classic `Sites.Selected`
|
|||
|
|
signal: token is fine, site grant is missing.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Note: graduating to an MCP server
|
|||
|
|
|
|||
|
|
When you want this available across all sessions as native tools rather than a
|
|||
|
|
CLI, build a small MCP server that imports `scripts/lib/graph.mjs`:
|
|||
|
|
|
|||
|
|
```js
|
|||
|
|
import { SharePointListsClient } from "./graph.mjs";
|
|||
|
|
const client = new SharePointListsClient({
|
|||
|
|
tenantId: process.env.SP_TENANT_ID,
|
|||
|
|
clientId: process.env.SP_CLIENT_ID,
|
|||
|
|
clientSecret: process.env.SP_CLIENT_SECRET,
|
|||
|
|
});
|
|||
|
|
// Expose client.listItems / getItem / createItem / updateItem / deleteItem
|
|||
|
|
// as MCP tools. The Graph + auth logic is already done.
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
The client is dependency-free and stateless apart from an in-memory token cache,
|
|||
|
|
so it drops straight into an MCP server (`@modelcontextprotocol/sdk`) or any
|
|||
|
|
other host.
|