mcp-onedrive-sharepoint
# MCP OneDrive/SharePoint Server
[](https://github.com/ftaricano/mcp-onedrive-sharepoint/actions/workflows/ci.yml)
[](LICENSE)
[](https://nodejs.org)
[](https://modelcontextprotocol.io)
[](https://www.typescriptlang.org)
Give AI agents (over MCP) and shell scripts (through the `ods` CLI) access to OneDrive and SharePoint document libraries via Microsoft Graph, with app-only authentication and a small default tool set.

<sub>Real CLI output against a mocked Microsoft Graph with fictional data (the Acme site on `contoso.sharepoint.com`).</sub>
- **One core, two front ends:** the MCP server and the CLI share the same tools, auth and handlers.
- **Small default surface:** the `core` profile has 10 everyday tools; delete, share, permission, list-item and raw Graph tools are opt-in.
- **Guarded requests:** request URLs are checked against `https://graph.microsoft.com` before the token is attached, and local file access is confined to one root directory.
- **Script friendly:** tool results are JSON, so `jq` and friends just work.
## Quickstart
You need Node.js 20+ and a Microsoft Entra ID app registration with the `Files.ReadWrite.All` and `Sites.ReadWrite.All` Application permissions and admin consent (see [Requirements](#requirements)).
```bash
git clone https://github.com/ftaricano/mcp-onedrive-sharepoint.git
cd mcp-onedrive-sharepoint
npm ci && npm run build
export MICROSOFT_GRAPH_TENANT_ID=<tenant-uuid>
export MICROSOFT_GRAPH_CLIENT_ID=<application-client-id>
export MICROSOFT_GRAPH_CLIENT_SECRET=<client-secret>
node build/cli.js list
node build/cli.js list_files --siteUrl=https://contoso.sharepoint.com/sites/Docs --path=/
```
Prefer injecting the secret from your secret manager over typing it into the shell. To use the MCP server, point your MCP client at `node /absolute/path/to/mcp-onedrive-sharepoint/build/index.js` with the same three variables in its environment (see [MCP stdio snippet](#mcp-stdio-snippet)).
## Example
List a folder of a SharePoint site and keep only the fields you need:
```bash
node build/cli.js list_files --siteUrl=https://contoso.sharepoint.com/sites/Acme --path=/Reports \
| jq -c '.items[] | {name, type, size}'
```
```json
{"name":"2026-Q2","type":"folder","size":0}
{"name":"Quarterly report Q1.pdf","type":"file","size":482133}
{"name":"Sales pipeline.xlsx","type":"file","size":91520}
```
The same call from an MCP client is the `list_files` tool with `{"siteUrl": "https://contoso.sharepoint.com/sites/Acme", "path": "/Reports"}`. More inputs are in [Example tool inputs](#example-tool-inputs).
## Architecture
```text
MCP client ──stdio──▶ build/index.js ─┐
├─▶ tool registry ─▶ handlers ─▶ Graph client ─▶ graph.microsoft.com
shell, scripts ─────▶ build/cli.js ───┘ (core | full) │ URL guard, retries, paging
└─ MSAL client credentials (token in memory)
```
- `src/tools/registry.ts` picks the tools for the active profile; both entry points use it.
- `src/graph/client.ts` is the only HTTP path to Graph. It rejects request URLs outside `https://graph.microsoft.com/{v1.0,beta}` before fetching or attaching the token.
- `src/auth/` gets app-only tokens with MSAL and keeps them in memory; nothing is written to disk.
- `src/sharepoint/site-resolver.ts` turns site aliases from a local, untracked registry into Graph ids.
## Tools
The default profile, `MCP_TOOL_PROFILE=core`, exposes these tools to both the MCP server and the `ods` CLI:
| Tool | What it does | Writes |
|---|---|---|
| `health_check` | Check authentication and Graph connectivity | no |
| `list_drives` | List accessible drives (OneDrive and SharePoint document libraries) | no |
| `discover_sites` | Search SharePoint sites visible to the app | no |
| `resolve_site` | Resolve a site from an alias, `siteId` or site URL | no |
| `list_files` | List files and folders in a drive path, with pagination | no |
| `search_files` | Search files and folders in a drive, with pagination | no |
| `get_file_metadata` | Get metadata (and optionally versions) for a file or folder | no |
| `download_file` | Download a file; with `outputPath` it writes (and overwrites) a local file | local disk |
| `upload_file` | Upload a local file to a drive path (`conflictBehavior`: `fail`, `replace`, `rename`) | yes |
| `create_folder` | Create a folder in a drive | yes |
## Tool profiles
The server defaults to `MCP_TOOL_PROFILE=core`, a smaller public surface intended for day-to-day document workflows (the table above).
Set `MCP_TOOL_PROFILE=full` to expose advanced and destructive tools for trusted environments:
- Files: `list_files`, `download_file`, `upload_file`, `create_folder`, `move_item`, `delete_item`, `search_files`, `get_file_metadata`, `share_item`, `copy_item`
- SharePoint: `discover_sites`, `resolve_site`, `list_site_lists`, `get_list_schema`, `list_items`, `get_list_item`, `create_list_item`, `update_list_item`, `delete_list_item`
- Utilities: `health_check`, `get_user_profile`, `list_drives`, `global_search`
- Advanced: `advanced_share`, `manage_permissions`, `check_user_access`, `sync_folder`, `batch_file_operations`, `storage_analytics`, `version_management`, `excel_operations`, `excel_analysis`
`batch_operations` is intentionally not part of either profile by default because it is a raw Microsoft Graph escape hatch. Enable it only for admin/debug workflows with `MCP_ENABLE_EXPERIMENTAL_GRAPH_BATCH=true`.
You can also remove individual tools with `MCP_DISABLED_TOOLS=delete_item,manage_permissions`.
## Why this repo
- one MCP server for both OneDrive and SharePoint document libraries
- matching `ods` CLI for shell scripting and one-shot automation
- app-only client credentials from the process environment; no `.env` loading and no token cache on disk
- site aliases loaded from a local registry so tenant IDs stay out of git
- pagination/resource helpers for `driveId`, `siteId`, `itemId` and path targeting
## Requirements
- Node.js 20+
- A Microsoft Entra ID / Azure AD confidential app registration with Application permissions (`Files.ReadWrite.All`, `Sites.ReadWrite.All`) and admin consent. The app's tenant ID, client ID and client secret are passed as `MICROSOFT_GRAPH_TENANT_ID`, `MICROSOFT_GRAPH_CLIENT_ID` and `MICROSOFT_GRAPH_CLIENT_SECRET`; the tenant must be a specific UUID, not `common`.
## Installation
```bash
git clone https://github.com/ftaricano/mcp-onedrive-sharepoint.git
cd mcp-onedrive-sharepoint
npm install
```
## Operational wrappers
Operational guidance:
- use this MCP on demand
- do not keep it permanently loaded in your MCP client when not needed
- prefer one-shot `spcall` / `mcporter --stdio` execution so the process exits right after the call and does not accumulate zombie or idle MCP processes
- the `spcall` wrapper includes post-call cleanup for stray repo-local MCP child processes
The `scripts/` directory has legacy launchers for one specific local setup: they read the three `MICROSOFT_GRAPH_*` values from a password manager through a helper that is **not** part of this repository, and inject them only into the child process. Outside that setup they fail, so run `node build/index.js` or `node build/cli.js` with the variables already in the environment instead. The launchers will be removed in a future major version.
- `./scripts/run-stdio.sh`: start the MCP stdio server after injecting the credentials
- `./scripts/spcall.sh`: run ad-hoc `mcporter` calls against the local MCP server
- `npm run stdio`: same as `./scripts/run-stdio.sh`
- `npm run spcall -- <tool> ...`: same as `./scripts/spcall.sh <tool> ...`
Quick examples:
```bash
npm run build
./scripts/spcall.sh health_check
./scripts/spcall.sh list_drives
./scripts/spcall.sh list_files driveId=b!abc123 path=/Shared%20Documents
```
Tenant-specific site aliases and drive ids are loaded from a local file — see [Site registry](#site-registry) below.
## CLI (`ods`)
Every MCP tool is also exposed as a plain subcommand through the `ods` CLI. It shares the same auth, config and handlers as the MCP server, so anything the MCP does is one-shot runnable from a terminal or a shell script.
```bash
npm run build
# `npm install` does NOT put `ods` on your PATH. Link it once, e.g.:
# npm link # or: ln -s "$PWD/scripts/ods.sh" ~/bin/ods
ods list # list all tools with descriptions
ods schema list_files # print JSON schema for a tool
ods auth # exits: delegated token persistence is intentionally disabled
ods <tool> --key=value [--key value] # invoke a tool with CLI flags
ods <tool> --json '{"k":"v"}' # pass the full payload as JSON
```
During development, rebuild before `npm run cli -- <tool> ...`; the command uses
the same launcher as the packaged `ods` bin. `node build/cli.js <tool> ...` runs the
CLI directly with the credentials from the environment.
### Examples
```bash
ods health_check
ods list_files --site=primary --path=/
ods list_files --driveId=b!abc --path=/Shared%20Documents --limit=50
ods upload_file --json '{"driveId":"b!abc","path":"/x.txt","content":"hello"}'
```
### Rules for flags
- `--key=value` and `--key value` are both accepted.
- `true` / `false` / `null` and numeric strings are coerced automatically; anything else stays a string.
- Bare flags (no value, or followed by another flag) become `true`.
- `--json '<payload>'` takes a JSON object; individual `--key=value` flags layered on top override fields from the payload. Use this for tools with nested objects/arrays (e.g. advanced Excel tools).
- Output is the raw tool payload (usually pretty-printed JSON). If the handler returns an error envelope, the process exits with code `2`.
## Configuration
The server reads the following environment variables:
```bash
# Required, from your secret manager (never from a committed file):
# MICROSOFT_GRAPH_CLIENT_ID
# MICROSOFT_GRAPH_TENANT_ID (specific UUID)
# MICROSOFT_GRAPH_CLIENT_SECRET
MICROSOFT_GRAPH_SCOPES=Files.ReadWrite.All,Sites.ReadWrite.All,Directory.Read.All,User.Read,offline_access
MICROSOFT_GRAPH_BASE_URL=https://graph.microsoft.com/v1.0
MICROSOFT_GRAPH_TIMEOUT=30000
MICROSOFT_GRAPH_MAX_RETRIES=3
MICROSOFT_GRAPH_CACHE_ENABLED=true
MICROSOFT_GRAPH_CACHE_TTL=3600
MCP_TOOL_PROFILE=core
MCP_DISABLED_TOOLS=
MCP_ENABLE_EXPERIMENTAL_GRAPH_BATCH=false
```
Notes:
- the credentials come from the process environment alone; `.env` is never loaded
- `npm start`, `npm run stdio`, `npm run cli`, `npm run spcall` and the packaged bins go through the legacy launchers in `scripts/` (see [Operational wrappers](#operational-wrappers)); `node build/index.js` and `node build/cli.js` read the environment directly
- set `MCP_LOCAL_FILE_ROOT` to constrain local upload/download/sync file access; if unset, local paths are constrained to the process working directory
## Authentication modes
### Client credentials (app-only)
The server authenticates as the app registration with the client-credentials flow, using
`MICROSOFT_GRAPH_TENANT_ID`, `MICROSOFT_GRAPH_CLIENT_ID` and
`MICROSOFT_GRAPH_CLIENT_SECRET` from the environment. There is no `.env`, Keychain, file
cache, or delegated token fallback. `npm run setup-auth` and `ods auth` fail
intentionally because delegated token persistence is disabled; provision the app
credentials instead.
## Development commands
```bash
npm run build
npm run lint
npm test
npm run ci
npm start
npm run stdio
npm run spcall -- health_check
```
`npm run ci` is the local verification entrypoint and is also what GitHub Actions runs on every PR/push.
## MCP behavior notes
### Root site inclusion
`discover_sites.includePersonalSite=true` currently attempts to append the tenant root SharePoint site (`/sites/root`) when it is available to the authenticated user.
It does not discover or synthesize a personal OneDrive site.
### Pagination
The following tools now expose consistent pagination metadata in their JSON payloads:
- `list_files`
- `search_files`
- `discover_sites`
- `list_site_lists`
- `list_items`
When Microsoft Graph returns `@odata.nextLink`, the response includes:
- `pagination.returned`
- `pagination.limit`
- `pagination.totalCount` when available
- `pagination.nextPageToken`
- `pagination.hasMore`
Pass `pageToken` back to the same tool to continue paging.
### Drive/site targeting
Core file listing/search/download flows now accept:
- `siteId` for a SharePoint site's default drive
- `driveId` for a specific document library or drive
- path-based addressing where supported
This is the current foundation for moving beyond a `/me/drive`-only model.
## Site registry
The resolver can target named SharePoint sites by alias (e.g. `site=primary`). The registry is loaded from an external JSON file so no tenant-specific ids are committed:
- Copy `config/sites.example.json` to `config/sites.local.json` (gitignored) and fill in your values.
- Or set `MCP_SITES_CONFIG_PATH` to point at a different JSON file.
- If the file is missing, the registry stays empty and the tools only accept explicit `siteId`, `siteUrl`, or `driveId`.
Each site entry looks like:
```json
{
"key": "primary",
"name": "Primary",
"siteId": "yourtenant.sharepoint.com,<guid>,<guid>",
"siteUrl": "https://yourtenant.sharepoint.com/sites/Primary",
"driveId": "b!<drive-id>",
"aliases": ["primary", "/sites/Primary"]
}
```
### MCP stdio snippet
Run the built server with Node and pass the credentials in the environment (from your secret manager or your MCP client's secret store):
```json
{
"mcpServers": {
"sharepoint": {
"command": "node",
"args": ["/absolute/path/to/mcp-onedrive-sharepoint/build/index.js"],
"env": {
"MICROSOFT_GRAPH_TENANT_ID": "<tenant-uuid>",
"MICROSOFT_GRAPH_CLIENT_ID": "<application-client-id>",
"MICROSOFT_GRAPH_CLIENT_SECRET": "<client-secret>"
}
}
}
}
```
## Example tool inputs
### List files from a specific drive
```json
{
"driveId": "b!abc123",
"path": "/Shared Documents",
"limit": 50
}
```
### Continue a paginated file listing
```json
{
"driveId": "b!abc123",
"pageToken": "https://graph.microsoft.com/v1.0/drives/b!abc123/root/children?$skiptoken=..."
}
```
### Search files in a site drive
```json
{
"siteId": "contoso.sharepoint.com,123,456",
"query": "quarterly report",
"limit": 25
}
```
### List SharePoint list items with pagination
```json
{
"siteId": "contoso.sharepoint.com,123,456",
"listId": "00000000-0000-0000-0000-000000000000",
"orderBy": "Created desc",
"limit": 100
}
```
## Troubleshooting
- `403 Forbidden` on SharePoint lists/drives: the app registration lacks permission to the target site. Check the application permissions and admin consent.
- `404` on a `driveId` or `siteId`: the identifier is stale or the resource was deleted. Use `list_drives` / `discover_sites` to re-discover.
- Build fails on a clean clone: make sure Node.js is 20+ and run `npm install` before `npm run build`.
- `AADSTS700016` or `401`: make sure `MICROSOFT_GRAPH_TENANT_ID` is a specific tenant UUID (not `common`) and the Application permissions have admin consent in Microsoft Entra ID.
- `AADSTS7000215` (invalid client secret): create a new secret in the app registration and update `MICROSOFT_GRAPH_CLIENT_SECRET` wherever you store it.
## Security
This server handles Microsoft Graph client credentials and access to corporate file storage. Treat it accordingly:
- `.env`, `tokens.json`, `credentials.json`, and secret-store exports are **never** committed — see [.gitignore](.gitignore).
- tenant-specific `siteId`, `driveId`, SharePoint URLs and internal operational paths should stay in local/private docs, not in this public repo.
- Report security issues privately via [GitHub security advisories](https://github.com/ftaricano/mcp-onedrive-sharepoint/security/advisories/new) — do not open a public issue.
- If a client secret leaks, revoke it in Microsoft Entra ID, create a new one and update your secret store.
## Contributing
Issues and PRs welcome; see [CONTRIBUTING.md](CONTRIBUTING.md). Before opening a PR:
- `npm run ci` passes (build + lint + tests)
- one focused change per PR
- no credentials, tenant-specific ids, or internal paths in commits or README
## License and credits
[MIT](LICENSE).
Built on the [Model Context Protocol TypeScript SDK](https://github.com/modelcontextprotocol/typescript-sdk), [MSAL Node](https://github.com/AzureAD/microsoft-authentication-library-for-js/tree/dev/lib/msal-node) and [axios](https://github.com/axios/axios). Microsoft, OneDrive and SharePoint are trademarks of Microsoft Corporation; this project is not affiliated with or endorsed by Microsoft.
## Current limitations
- client credentials require Application permissions and admin consent; delegated (per-user) sign-in is not supported
- advanced/destructive tools require `MCP_TOOL_PROFILE=full`
- raw Graph batch calls require `MCP_ENABLE_EXPERIMENTAL_GRAPH_BATCH=true`
TDQS
Scored across 33 tools
Most tools have distinct purposes (file ops, list ops, sharing, search, Excel, batch, etc.). There is minor overlap between batch_file_operations and batch_operations, and between search_files and global_search, but descriptions clarify the differences. Overall, an agent can distinguish tools effectively.
All tool names follow a consistent verb_noun pattern in snake_case (e.g., create_folder, delete_item, check_user_access). Multi-word names are uniform. No mixed conventions or camelCase, making the naming predictable and easy to navigate.
With 33 tools, the count is high for a single server. While each tool serves a specific function, the number exceeds the typical well-scoped range (3-15) and feels heavy. Some tools like health_check and get_user_profile are tangentially related, and batch operations add complexity.
The tool set covers the OneDrive/SharePoint domain comprehensively: file CRUD, sharing, permissions, version management, batch operations, search across content, SharePoint list full lifecycle, Excel analysis, storage analytics, and sync. No obvious gaps for common workflows.