Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,17 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

### Added
- `sharepoint_list_folder` tool for direct folder browsing (#65)
- Lists files and subfolders directly from SharePoint/OneDrive, bypassing the search index
- Useful when files are not indexed or not discoverable via `sharepoint_docs_search`
- Supports pagination via continuation token (`next_token`) using SharePoint's `$skiptoken` mechanism
- File paths in results are compatible with `sharepoint_docs_download`
- Supports both full URLs and server-relative URLs
- Can be disabled via `SHAREPOINT_DISABLED_TOOLS=sharepoint_list_folder`

## [0.5.0] - 2026-02-11

### Added
Expand Down
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@ Two authentication methods are supported:
- **sharepoint_docs_download**
- File download from search results
- Automatic method selection for SharePoint vs OneDrive files
- **sharepoint_list_folder**
- List files and subfolders directly in a SharePoint or OneDrive folder
- Bypasses the search index — use when `sharepoint_docs_search` cannot find a file due to indexing issues
- Supports pagination via continuation token (`next_token`)
- File paths in results can be passed directly to `sharepoint_docs_download`
- **sharepoint_excel**
- Read or search Excel files in SharePoint
- Search mode: find cells containing specific text with `query` parameter
Expand Down
5 changes: 5 additions & 0 deletions README_ja.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@ stdioとHTTPの両方のトランスポートに対応しています。
- **sharepoint_docs_download**
- 検索結果からファイルをダウンロード
- SharePoint/OneDriveファイルに応じた自動メソッド選択
- **sharepoint_list_folder**
- SharePointまたはOneDriveのフォルダ内のファイル・サブフォルダを直接リスト取得
- 検索インデックスをバイパス — `sharepoint_docs_search` でファイルが見つからない場合に使用
- 継続トークン(`next_token`)によるページング対応
- 返却されたファイルパスは `sharepoint_docs_download` にそのまま渡せる
- **sharepoint_excel**
- SharePoint上のExcelファイルの読み取りと検索
- 検索モード: `query`パラメータで特定テキストを含むセルを検索
Expand Down
79 changes: 79 additions & 0 deletions docs/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ This guide covers how to use the SharePoint MCP server with various clients and
- [MCP Inspector Verification](#mcp-inspector-verification)
- [Claude Desktop Integration](#claude-desktop-integration)
- [Search Usage Examples](#search-usage-examples)
- [Folder Listing Usage Examples](#folder-listing-usage-examples)
- [Excel Operations Usage Examples](#excel-operations-usage-examples)

## MCP Server Startup
Expand Down Expand Up @@ -188,6 +189,84 @@ results = sharepoint_docs_search(
)
```

## Folder Listing Usage Examples

The `sharepoint_list_folder` tool lists files and subfolders directly in a SharePoint or OneDrive folder, bypassing the search index. Use it when `sharepoint_docs_search` cannot find a file due to indexing issues.

### Tool Parameters

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `folder_path` | str | Required | Full URL or server-relative URL of the folder |
| `max_results` | int | 100 | Max files per page (capped at 500). Subfolders are always returned in full. |
| `next_token` | str \| None | None | Continuation token from the previous response (omit for the first page) |

### Basic Usage

```python
# First page — list files and subfolders in a folder
result = sharepoint_list_folder(
folder_path="https://company.sharepoint.com/sites/mysite/Shared Documents/Reports"
)
```

**Response:**
```json
{
"folders": [
{"name": "2023", "path": "https://company.sharepoint.com/sites/mysite/Shared Documents/Reports/2023", "modified": "2024-01-15T10:30:00Z"},
{"name": "2024", "path": "https://company.sharepoint.com/sites/mysite/Shared Documents/Reports/2024", "modified": "2024-06-01T09:00:00Z"}
],
"files": [
{"name": "summary.pdf", "path": "https://company.sharepoint.com/sites/mysite/Shared Documents/Reports/summary.pdf", "size": 102400, "modified": "2024-06-01T09:00:00Z"}
],
"next_token": null
}
```

- `folders` is only populated on the first page (when `next_token` is omitted).
- `next_token` is `null` when no further pages exist; otherwise pass it to the next call.

### Pagination

```python
# Page 1
page1 = sharepoint_list_folder(folder_path="/sites/mysite/Shared Documents/Archive", max_results=50)

# Page 2 (only if page1["next_token"] is not null)
if page1["next_token"]:
page2 = sharepoint_list_folder(
folder_path="/sites/mysite/Shared Documents/Archive",
max_results=50,
next_token=page1["next_token"],
)
```

### Download a File from Listing Results

File paths returned by `sharepoint_list_folder` can be passed directly to `sharepoint_docs_download`:

```python
# List folder contents
result = sharepoint_list_folder(folder_path="/sites/mysite/Shared Documents/Reports")

# Download the first file
file_path = result["files"][0]["path"]
content = sharepoint_docs_download(file_path=file_path)
```

### Server-Relative URL

Both full URLs and server-relative URLs are accepted:

```python
# Full URL
sharepoint_list_folder(folder_path="https://company.sharepoint.com/sites/mysite/Shared Documents")

# Server-relative URL
sharepoint_list_folder(folder_path="/sites/mysite/Shared Documents")
```

## Excel Operations Usage Examples

The `sharepoint_excel` tool allows you to read and search Excel files in SharePoint. It supports two modes:
Expand Down
79 changes: 79 additions & 0 deletions docs/usage_ja.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
- [MCP Inspectorでの検証](#mcp-inspectorでの検証)
- [Claude Desktopとの統合](#claude-desktopとの統合)
- [検索の使用例](#検索の使用例)
- [フォルダ一覧の使用例](#フォルダ一覧の使用例)
- [Excel操作の使用例](#excel操作の使用例)

## MCPサーバーの起動
Expand Down Expand Up @@ -188,6 +189,84 @@ results = sharepoint_docs_search(
)
```

## フォルダ一覧の使用例

`sharepoint_list_folder` ツールは、検索インデックスをバイパスして SharePoint または OneDrive のフォルダ内のファイル・サブフォルダを直接取得します。`sharepoint_docs_search` でファイルが見つからない場合に使用してください。

### ツールパラメータ

| パラメータ | 型 | デフォルト | 説明 |
|-----------|------|---------|-------------|
| `folder_path` | str | 必須 | フォルダのフルURLまたはサーバー相対URL |
| `max_results` | int | 100 | 1ページあたりの最大ファイル数(上限500)。サブフォルダは常に全件返却。 |
| `next_token` | str \| None | None | 前回レスポンスの継続トークン(最初のページでは省略) |

### 基本的な使い方

```python
# 最初のページ — フォルダ内のファイルとサブフォルダを取得
result = sharepoint_list_folder(
folder_path="https://company.sharepoint.com/sites/mysite/Shared Documents/Reports"
)
```

**レスポンス:**
```json
{
"folders": [
{"name": "2023", "path": "https://company.sharepoint.com/sites/mysite/Shared Documents/Reports/2023", "modified": "2024-01-15T10:30:00Z"},
{"name": "2024", "path": "https://company.sharepoint.com/sites/mysite/Shared Documents/Reports/2024", "modified": "2024-06-01T09:00:00Z"}
],
"files": [
{"name": "summary.pdf", "path": "https://company.sharepoint.com/sites/mysite/Shared Documents/Reports/summary.pdf", "size": 102400, "modified": "2024-06-01T09:00:00Z"}
],
"next_token": null
}
```

- `folders` は最初のページ(`next_token` を省略した呼び出し)にのみ含まれます。
- `next_token` が `null` の場合は最終ページ。値がある場合は次の呼び出しに渡してください。

### ページング

```python
# 1ページ目
page1 = sharepoint_list_folder(folder_path="/sites/mysite/Shared Documents/Archive", max_results=50)

# 2ページ目(page1["next_token"] が null でない場合のみ)
if page1["next_token"]:
page2 = sharepoint_list_folder(
folder_path="/sites/mysite/Shared Documents/Archive",
max_results=50,
next_token=page1["next_token"],
)
```

### 一覧結果からファイルをダウンロード

`sharepoint_list_folder` が返すファイルパスは `sharepoint_docs_download` にそのまま渡せます:

```python
# フォルダ内容を取得
result = sharepoint_list_folder(folder_path="/sites/mysite/Shared Documents/Reports")

# 最初のファイルをダウンロード
file_path = result["files"][0]["path"]
content = sharepoint_docs_download(file_path=file_path)
```

### サーバー相対URLも使用可能

フルURLとサーバー相対URLの両方に対応しています:

```python
# フルURL
sharepoint_list_folder(folder_path="https://company.sharepoint.com/sites/mysite/Shared Documents")

# サーバー相対URL
sharepoint_list_folder(folder_path="/sites/mysite/Shared Documents")
```

## Excel操作の使用例

`sharepoint_excel`ツールを使用して、SharePoint上のExcelファイルの読み取りと検索ができます。2つのモードをサポートしています:
Expand Down
2 changes: 1 addition & 1 deletion src/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -278,7 +278,7 @@ def disabled_tools(self) -> set[str]:
"""無効化されたツール名のセットを返す

環境変数 SHAREPOINT_DISABLED_TOOLS で指定されたツールのセット。
有効な値: sharepoint_docs_search, sharepoint_docs_download, sharepoint_excel
有効な値: sharepoint_docs_search, sharepoint_docs_download, sharepoint_list_folder, sharepoint_excel
"""
if not self._disabled_tools_str:
return set()
Expand Down
19 changes: 19 additions & 0 deletions src/error_messages.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ class ErrorCategory(Enum):
EXCEL_SHEET_NOT_FOUND = "excel_sheet_not_found"
EXCEL_INVALID_RANGE = "excel_invalid_range"
EXCEL_INVALID_FILE = "excel_invalid_file"
FOLDER_NOT_FOUND = "folder_not_found"
UNKNOWN = "unknown"


Expand Down Expand Up @@ -212,6 +213,22 @@ def get_excel_invalid_file_error(original_error: Exception) -> SharePointError:
)


def get_folder_not_found_error(
folder_path: str | None, original_error: Exception
) -> SharePointError:
"""Generate folder not found error message"""
if folder_path:
message = f"The specified folder was not found: {folder_path}"
else:
message = "The requested folder was not found."
return SharePointError(
category=ErrorCategory.FOLDER_NOT_FOUND,
message=message,
solution="Please verify the folder path is correct. You can use sharepoint_docs_search to find a file in the target folder, then derive the folder path from its path field.",
original_error=original_error,
)


def get_unknown_error(original_error: Exception) -> SharePointError:
"""Generate unknown error message"""
return SharePointError(
Expand Down Expand Up @@ -285,6 +302,8 @@ def handle_sharepoint_error(
return get_authorization_error(error)
elif status_code == 404 and context == "download":
return get_file_not_found_error(None, error, is_onedrive_file)
elif status_code == 404 and context == "list":
return get_folder_not_found_error(None, error)

# Classification by error message content
if any(
Expand Down
60 changes: 60 additions & 0 deletions src/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -528,6 +528,51 @@ def sharepoint_excel(
) from e


def sharepoint_list_folder(
folder_path: str,
max_results: int = 100,
next_token: str | None = None,
ctx: Context | None = None,
) -> dict[str, Any]:
"""
List files and folders directly in a SharePoint or OneDrive folder.

Use this when sharepoint_docs_search cannot find a file due to indexing issues.
This reads folder contents directly from SharePoint, bypassing the search index.

Args:
folder_path: Full URL or server-relative URL of the folder.
Examples:
https://company.sharepoint.com/sites/mysite/Shared Documents/Reports
/sites/mysite/Shared Documents/Reports
max_results: Max files per page (default: 100, max: 500). Folders are always returned in full.
next_token: Continuation token from a previous response (default: None = first page).
Pass the next_token value from the previous response to retrieve the next page.
ctx: FastMCP context (injected automatically)

Returns:
{
"folders": [{"name": str, "path": str, "modified": str}], -- only on first page
"files": [{"name": str, "path": str, "size": int, "modified": str}],
"next_token": str | None -- null if last page; pass to next call for more files
}
File paths can be passed directly to sharepoint_docs_download.
"""
logging.info(f"Listing SharePoint folder: {folder_path}")

try:
client = _get_sharepoint_client(ctx)
max_results = min(max_results, 500)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

max_results0 や負の数が指定された場合に SharePoint API がエラーを返すのを防ぐため、下限値を 1 に制限する防衛的プログラミングを行うことをお勧めします。

Suggested change
max_results = min(max_results, 500)
max_results = max(1, min(max_results, 500))

return client.list_folder(
folder_path=folder_path,
max_results=max_results,
next_token=next_token,
)
except Exception as e:
logging.error(f"SharePoint folder listing failed: {str(e)}")
raise handle_sharepoint_error(e, "list") from e


def register_tools():
"""Register MCP tools

Expand All @@ -546,6 +591,21 @@ def register_tools():
else:
logging.info("Tool disabled: sharepoint_docs_download")

if config.is_tool_enabled("sharepoint_list_folder"):
mcp.tool(
description=(
"List files and folders directly in a SharePoint or OneDrive folder (bypasses search index). "
"Use when sharepoint_docs_search cannot find a file due to indexing issues. "
"Input: full URL or server-relative URL of the folder. "
"Returns: folders (first page only), files (paginated), next_token (null = last page). "
"Pagination: pass next_token from previous response to retrieve the next page. "
"File paths in results can be passed directly to sharepoint_docs_download."
)
)(sharepoint_list_folder)
logging.info("Registered tool: sharepoint_list_folder")
else:
logging.info("Tool disabled: sharepoint_list_folder")

if config.is_tool_enabled("sharepoint_excel"):
mcp.tool(
description=(
Expand Down
Loading
Loading