Files
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

5.1 KiB
Raw Permalink Blame History

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.comIdentityApplicationsApp registrationsNew 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) IDSP_CLIENT_ID
  • Directory (tenant) IDSP_TENANT_ID

2. Create a client secret

  1. App → Certificates & secretsClient secretsNew 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 permissionsAdd a permissionMicrosoft GraphApplication 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.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:

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.