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>
164 lines
6.6 KiB
Markdown
164 lines
6.6 KiB
Markdown
---
|
|
name: sharepoint-lists
|
|
description: >-
|
|
Read and write Microsoft SharePoint Lists via the Microsoft Graph API using
|
|
app-only (client-credentials) authentication. Use this skill whenever the user
|
|
wants to query, search, list, create, add, update, edit, or delete items in a
|
|
SharePoint List, inspect a list's columns, or enumerate the lists in a
|
|
SharePoint site — even if they just refer to "our SharePoint", "the list",
|
|
"the tracker", or a specific list/site name rather than saying "SharePoint
|
|
List" explicitly. Also use it for bulk operations over list items (export,
|
|
reporting, batch updates) and for connectivity/credential troubleshooting
|
|
against SharePoint. This is for SharePoint *Lists* (structured rows/columns),
|
|
not document libraries or files — for documents use the SharePoint search
|
|
connector instead.
|
|
---
|
|
|
|
# SharePoint Lists
|
|
|
|
Connect to Microsoft SharePoint Lists and perform full CRUD (create, read,
|
|
update, delete) on list items through the Microsoft Graph API. Authentication is
|
|
**app-only** (OAuth2 client credentials) — no interactive login.
|
|
|
|
All operations go through one CLI, `scripts/sp.mjs`, which wraps the reusable
|
|
core client in `scripts/lib/graph.mjs`. Run it with Node (already available):
|
|
|
|
```bash
|
|
node scripts/sp.mjs <command> [flags]
|
|
```
|
|
|
|
> Paths above are relative to this skill's directory. From elsewhere, use the
|
|
> absolute path, e.g. `node "<skill-dir>/scripts/sp.mjs" ...`.
|
|
|
|
## Prerequisites (one-time)
|
|
|
|
The skill needs three credentials from an Azure AD (Entra) **app registration**
|
|
with an *application* permission for SharePoint and admin consent granted:
|
|
|
|
- `SP_TENANT_ID` — Directory (tenant) ID
|
|
- `SP_CLIENT_ID` — Application (client) ID
|
|
- `SP_CLIENT_SECRET` — a client secret value
|
|
|
|
Set them as environment variables, or copy `.env.example` to `.env` in this
|
|
skill's directory and fill it in (the CLI loads `.env` automatically; `.env` is
|
|
git-ignored). Optionally set `SP_SITE_URL` to a default site so `--site` can be
|
|
omitted.
|
|
|
|
**If credentials are not yet set up**, do not guess — point the user to
|
|
[references/setup.md](references/setup.md), which is a step-by-step walkthrough
|
|
of creating the app registration, choosing least-privilege permissions
|
|
(`Sites.Selected` is strongly preferred over `Sites.ReadWrite.All`), and granting
|
|
consent. Then come back and run `test`.
|
|
|
|
## First step in any task: verify connectivity
|
|
|
|
Before doing real work, confirm auth works. This fails fast with a clear message
|
|
if credentials or permissions are wrong:
|
|
|
|
```bash
|
|
node scripts/sp.mjs test --site "https://contoso.sharepoint.com/sites/Marketing"
|
|
```
|
|
|
|
A successful response includes the resolved site id and the names of the lists
|
|
in that site.
|
|
|
|
## Identifying a site and a list
|
|
|
|
- **Site** is given by URL: `https://{tenant}.sharepoint.com/sites/{SiteName}`.
|
|
The client resolves it to a Graph site id for you. A bare hostname means the
|
|
root site.
|
|
- **List** can be given by display name (e.g. `"Project Tracker"`), internal
|
|
name, or GUID — the client resolves names to ids and, on a typo, lists the
|
|
available names so you can correct it.
|
|
|
|
When unsure what lists or columns exist, discover first rather than guessing:
|
|
|
|
```bash
|
|
node scripts/sp.mjs lists --site "<SITE_URL>"
|
|
node scripts/sp.mjs columns --site "<SITE_URL>" --list "Project Tracker"
|
|
```
|
|
|
|
`columns` is important before writing: SharePoint **internal** column names
|
|
often differ from their display names (a column shown as "Due Date" may be
|
|
`DueDate` or even `OData__x0044_ue...`). Always write to internal names. See
|
|
[references/graph-api.md](references/graph-api.md) for field-type formats
|
|
(person, lookup, choice, date, etc.) and OData query details.
|
|
|
|
To write a **person column**, first resolve the person's email to the numeric
|
|
LookupId the column stores, then write `<Column>LookupId`:
|
|
|
|
```bash
|
|
node scripts/sp.mjs person --site "<SITE_URL>" --email user@domain.com
|
|
# → {"lookupId": 53, ...}; then: --fields '{"ProjectManagerLookupId": 53}'
|
|
```
|
|
|
|
## Reading items
|
|
|
|
```bash
|
|
# Everything (follows pagination):
|
|
node scripts/sp.mjs items --site "<SITE_URL>" --list "Project Tracker" --all
|
|
|
|
# Filtered + sorted + projected:
|
|
node scripts/sp.mjs items --site "<SITE_URL>" --list "Project Tracker" \
|
|
--filter "fields/Status eq 'Open'" \
|
|
--select "Title,Status,AssignedTo" \
|
|
--orderby "fields/DueDate asc" --top 50
|
|
|
|
# One item by id:
|
|
node scripts/sp.mjs get --site "<SITE_URL>" --list "Project Tracker" --id 42
|
|
```
|
|
|
|
Items come back simplified as `{ id, webUrl, createdDateTime, lastModifiedDateTime, fields }`.
|
|
|
|
## Writing items
|
|
|
|
`--fields` takes a JSON object of internal column name → value. It also accepts
|
|
`@file.json` or `-` (read JSON from stdin), which is cleaner for large or
|
|
quote-heavy payloads.
|
|
|
|
```bash
|
|
# Create
|
|
node scripts/sp.mjs create --site "<SITE_URL>" --list "Project Tracker" \
|
|
--fields '{"Title":"Migrate database","Status":"Open","Priority":2}'
|
|
|
|
# Update (partial — only the fields you pass change)
|
|
node scripts/sp.mjs update --site "<SITE_URL>" --list "Project Tracker" \
|
|
--id 42 --fields '{"Status":"Done"}'
|
|
|
|
# Delete
|
|
node scripts/sp.mjs delete --site "<SITE_URL>" --list "Project Tracker" --id 42
|
|
```
|
|
|
|
**Deletes are irreversible (items go to the site Recycle Bin). Confirm the item
|
|
id with the user before deleting**, and prefer showing them the item via `get`
|
|
first. For bulk deletes, confirm the full set once, then proceed.
|
|
|
|
## Bulk operations
|
|
|
|
For exports or batch edits, fetch with `--all`, transform the JSON however the
|
|
task needs (jq, a quick script), then loop the relevant `create`/`update`/
|
|
`delete` calls. Keep concurrency modest — Graph throttles with HTTP 429; the
|
|
client already retries with backoff, but don't fan out hundreds of parallel
|
|
writes.
|
|
|
|
## Handling errors
|
|
|
|
The CLI prints a JSON error to stderr and exits non-zero. Common cases:
|
|
|
|
- `401` / token failure → bad client id/secret or tenant; re-check `.env`.
|
|
- `403` (`accessDenied`) → the app lacks permission on this site. With
|
|
`Sites.Selected`, the site must be explicitly granted to the app — see
|
|
[references/setup.md](references/setup.md).
|
|
- `404` on a list → wrong name; run `lists` to see valid names.
|
|
- `400` on write → usually a wrong internal field name or value format; run
|
|
`columns` and check [references/graph-api.md](references/graph-api.md).
|
|
|
|
## Extending to an MCP server
|
|
|
|
The core logic is isolated in `scripts/lib/graph.mjs` (the
|
|
`SharePointListsClient` class, zero dependencies). To graduate this into a
|
|
standalone MCP server later, import that class and expose its methods
|
|
(`listItems`, `getItem`, `createItem`, `updateItem`, `deleteItem`, etc.) as MCP
|
|
tools — no rewrite of the Graph logic needed. See the note at the bottom of
|
|
[references/setup.md](references/setup.md).
|