Person/group columns store a numeric LookupId into each site's hidden User Information List, and SPO's ensureUser needs certificate auth that client secrets can't provide — so resolve by querying the hidden list directly via Graph (item id IS the LookupId). New resolvePersonByEmail() in graph.mjs (server-side EMail/UserName filter with a paged case-insensitive scan fallback), a 'person' CLI command, and a sharepoint_resolve_person MCP tool (9 tools now). Verified live against known ground truth (LookupIds 53 and 136) plus the not-found path.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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>
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>