Claude Code skill for SharePoint Lists CRUD via Microsoft Graph (app-only auth): reusable graph.mjs client, sp.mjs CLI, and setup + API reference docs. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
4.9 KiB
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
- Go to https://entra.microsoft.com → Identity → Applications → App registrations → New registration.
- Name it something recognizable, e.g.
claude-sharepoint-lists. - Supported account types: Accounts in this organizational directory only (single tenant) is correct for app-only.
- Leave Redirect URI blank — client-credentials doesn't use one.
- 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
- App → Certificates & secrets → Client secrets → New client secret.
- Set an expiry (e.g. 6–12 months — note when it expires; you'll have to rotate).
- 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):
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:
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
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.Selectedthe site wasn't granted to the app (step 4). - Works for
test(no site) but 403 with--site— classicSites.Selectedsignal: 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:
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.