Files
Claude-SharepointLists/.claude/skills/sharepoint-lists/references/setup.md
ang3l12 7d41c1ca15 Warn before the client secret expires (SP_SECRET_EXPIRES)
Azure client secrets lapse silently into opaque 401s (AADSTS7000222). Add a self-reported expiry date (SP_SECRET_EXPIRES=YYYY-MM-DD) and a shared secretExpiryStatus() helper in graph.mjs; surface warnings <30 days out via the CLI (stderr on every command + test output), MCP server startup log, /health, and the sharepoint_test tool. Documented in both .env.examples, setup.md, and READMEs.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-08 15:13:48 -06:00

128 lines
5.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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. 612 months — note when it expires; you'll have to rotate).
3. Copy the secret **Value** immediately (it's only shown once) → `SP_CLIENT_SECRET`.
4. Record the expiry date in `.env` as `SP_SECRET_EXPIRES=YYYY-MM-DD` — the CLI
and MCP server use it to warn you 30 days ahead instead of failing cold with
a 401 (AADSTS7000222) when the secret lapses.
> 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 &lt;tenant&gt;** 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.