---
name: read-remote-document
description: Read SharePoint, OneDrive remote documents as Markdown. Uses Copilot's indexed content path as the primary reader, which respects the current user's Purview access rights (not a policy bypass).
metadata:
  activation:
    keywords: [sharepoint document, onedrive document, shared document, read sharepoint, read onedrive]
    patterns:
      - "(?i)https?://[\\w-]+\\.sharepoint\\.com/"
      - "(?i)https?://[\\w-]+-my\\.sharepoint\\.com/"
      - "(?i)https?://1drv\\.ms/"
      - "(?i)\\b(read|extract|open|summarize|convert|fetch|get)\\b.{0,30}\\b(?:sharepoint|onedrive|shared\\s+(?:document|file|link))\\b"
      - "(?i)(读|打开|获取|提取|总结|转换).{0,10}(sharepoint|onedrive|共享|在线)(文档|文件|链接)?"
  requires:
    tools: [MCP_m365copilot]
---

# Read Remote Document

Read Office files (docx/xlsx/pptx) and PDFs on SharePoint/OneDrive as Markdown. Input: sharing link or canonical `webUrl` (or `driveId`+`itemId` → passed as `documentLibraryId`/`fileId` for byte-level paths). Name-only input: resolve via `MCP_onedrive_findFileOrFolder` first.

**Access model**: surfaces only content the user is entitled to see — Copilot reads Substrate's plaintext index under the user's OBO token (Purview honored, not a bypass). MIP-encrypted files work only through Copilot; server-side extractors can't decrypt them.

## URL recognition

Share-link path segments:
- `:w:` Word · `:x:` Excel · `:p:` PowerPoint · `:b:` generic · `:f:` folder
- `/g/` anonymous link · `/s/` signed link
- `tenant-my.sharepoint.com/personal/...` = OneDrive · `tenant.sharepoint.com/sites/...` = team site
- `:b:` files: try Copilot; if non-Office/PDF, go to `readSmallTextFile` / `readSmallBinaryFile`.

## Retrieval methods

Row 1: primary for all Office/PDF. Rows 2-3: DOCX fallback pipeline (metadata → word) when Copilot can't ground. Rows 4-5: specialty tools for PDFs and plaintext.

| Method | Use for | Returns | Fails on |
|---|---|---|---|
| **`MCP_m365copilot_copilot_chat(message, fileUris=[<url>])`** | Primary — any Office/PDF on SharePoint/OneDrive | JSON: markdown at `rawResponse.messages[1].text`; label at `rawResponse.messages[1].sensitivityLabel.displayName` (via Substrate plaintext index, transparent to MIP-encryption) | Not-yet-indexed files (new/just-edited); cross-tenant links without explicit access |
| `MCP_onedrive_getFileOrFolderMetadataByUrl(fileOrFolderUrl=<url>)` | Resolve sharing URL → `driveId`/`itemId`/`webUrl`; check `shared.effectiveRoles`, size | Metadata JSON. Does NOT expose MIP label (`file.irmEnabled` is legacy IRM, not Purview — ignore it). Map `parentReference.driveId` → `documentLibraryId` and `id` → `fileId` when calling `readSmall*File`. | Access denied for files the user has no permission on. |
| `MCP_word_GetDocumentContent(url=<webUrl>)` | Fallback for `.docx` where the label doesn't encrypt (e.g. `General`, `Public`) | JSON: `{fileName, size, driveId, documentId, content}` where `content` is extracted plain text | Labels that apply encryption (e.g. `Confidential\Internal Only`) → `"Failed to extract content from DOCX due to unexpected error"` |
| `MCP_onedrive_readSmallBinaryFile(fileId, documentLibraryId)` | PDFs → pipe through local `pdftotext`; byte-signature diagnosis | Base64 blob + trailing `CorrelationId:` line | Files above the "small" cap; for MIP-encrypted Office returns the CFB-wrapped encrypted container |
| `MCP_onedrive_readSmallTextFile(fileId, documentLibraryId)` | Plain-text files only | Raw text | Binary files → UTF-8-decoded mojibake. **Never use for Office/PDF.** |

### Primary invocation

```
MCP_m365copilot_copilot_chat(
  message="Convert the content of the attached document to clean Markdown. "
          "Preserve headings, lists, tables, and code blocks. "
          "Return ONLY the markdown body, no preamble.",
  fileUris=["<sharing-url-or-webUrl>"],
  enableWebSearch=False
)
```

Response format:
- Tool output ends with a `\nCorrelationId: <guid>, TimeStamp: ...` trailer — strip it first.
- Top-level JSON has `reply` (always empty, ignore) and `rawResponse` (a JSON-encoded string — parse it a second time).
- Inside the parsed `rawResponse`:
  - `messages[1].text` — the markdown content
  - `messages[1].sensitivityLabel.displayName` — label name, or `null` if none

For Excel/PowerPoint, tweak the prompt (e.g. "return each sheet as a markdown table", "list each slide's title + bullet points").

For **summary** (not verbatim conversion), you may instead put the URL in `message` and leave `fileUris=[]` — Copilot will return a summary with citations.

### Fallback when Copilot can't ground the file

Trigger (when `fileUris=[<url>]` was passed): `rawResponse.messages[1].attributions` is an **empty array** — grounding failed. Success → ≥1 citation entry. Don't string-match the prose. Usually caused by Substrate indexing lag, bad/expired sharing token, or missing access.

1. `MCP_onedrive_getFileOrFolderMetadataByUrl(fileOrFolderUrl=<url>)` → `driveId`, `itemId`, `webUrl`.
2. `MCP_word_GetDocumentContent(url=<webUrl>)` — returns JSON; extracted text is in `content`. Works unless the label applies encryption.
3. If step 2 returns `"Failed to extract content from DOCX due to unexpected error"`, the file is MIP-encrypted. Tell the user server-side tools can't decrypt; options: open in Word Online (has MIP decryption), or ask the author to remove the encrypting label.

(Optional diagnostic: `readSmallBinaryFile` + decode — first 8 bytes `d0 cf 11 e0 a1 b1 1a e1` = MIP-encrypted; `50 4b 03 04` = plain OOXML.)

## Limits

- **MIP/Purview encryption is the main block.** Label presence alone is fine — `General`/`Public` don't break anything. Only encryption-applying labels (e.g. `Confidential\Internal Only`) fail `MCP_word_GetDocumentContent`; Copilot reads through either way via Substrate. No other server-side MCP has AADRM/MIP decryption.
- **Substrate indexing lag.** New/just-modified files may not yet be indexed — grounding fails (empty `attributions`). Wait or use the metadata/word fallback.
- **Tested dead-ends — don't retry.** `uploadFileFromUrl`/`copyFileOrFolder` preserve MIP encryption on the copy. `setSensitivityLabelOnFile` returns 403 for non-admins.
- **`readSmall*File` size cap** (~5 MB per `createSmallBinaryFile` spec). No chunked-download is exposed; large files need the Copilot path.
- **`file.irmEnabled = False` is misleading** — legacy IRM flag, doesn't reflect Purview labels. Don't decide based on metadata alone.
- **Cross-tenant links** may fail at token level (MCP tokens are home-tenant scoped). Fall back to Copilot search by filename, or have the owner reshare.
- **Wrong MCP**: `MCP_sharepoint` is `SharePointListsTools` — site Lists only, not file bodies.
- **Parameter gotchas**: `getFileOrFolderMetadataByUrl` takes `fileOrFolderUrl` (not `url`/`sharingUrl`/`webUrl`); `word.GetDocumentContent` takes `url` only (no `driveId`/`documentId`).

## Post-processing

Wrap the extracted Markdown body (from `rawResponse.messages[1].text` or `content`) with these agent-added lines. Keep them OUT of the Copilot prompt so the tool returns clean content:

- **Prepend** (only if `sensitivityLabel.displayName` is non-null): `> Label: <displayName>`
- **Append** `Source: <url>`, picking the first available of: `attributions[0].seeMoreWebUrl` (Copilot's citation), resolved `webUrl` from metadata, original input URL. Never leave `<url>` as a literal placeholder.

Preserve the body's headings, lists, tables, and code blocks unchanged.
