Skip to content

Commit 8ec03af

Browse files
committed
implementing the user tools
1 parent ef97a3b commit 8ec03af

File tree

7 files changed

+168
-74
lines changed

7 files changed

+168
-74
lines changed

README.md

Lines changed: 29 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -27,71 +27,6 @@ Search for files in Box.
2727
- `ancestor_folder_ids` (List[str], optional): List of folder IDs in which to search.
2828
- **Returns:** The search results as a newline‑separated list of file names and IDs.
2929

30-
### `box_read_tool`
31-
Read the text content of a Box file.
32-
33-
**Parameters:**
34-
- `file_id` (str): ID of the file to read
35-
36-
**Returns:** File content
37-
38-
### `box_ask_ai_tool`
39-
Ask Box AI about a file.
40-
41-
**Parameters:**
42-
- `file_id` (str): ID of the file
43-
- `prompt` (str): Question for the AI
44-
45-
**Returns:** AI response
46-
47-
### `box_hubs_ask_ai_tool`
48-
Ask Box AI about a hub. There is currently no way via API to discover a hub ID, so you must know the ID to use this tool. We will fix this in the future.
49-
50-
**Parameters:**
51-
- `hubs_id` (str): ID of the hub
52-
- `prompt` (str): Question for the AI
53-
54-
**Returns:** AI response
55-
56-
### `box_search_folder_by_name`
57-
Locate a folder by name.
58-
59-
**Parameters:**
60-
- `folder_name` (str): Name of the folder
61-
62-
**Returns:** Folder ID
63-
64-
### `box_ai_extract_data`
65-
Extract data from a file using AI.
66-
67-
**Parameters:**
68-
- `file_id` (str): ID of the file
69-
- `fields` (str): Fields to extract
70-
71-
**Returns:** Extracted data in JSON format
72-
73-
### `box_list_folder_content_by_folder_id`
74-
List folder contents.
75-
76-
**Parameters:**
77-
- `folder_id` (str): ID of the folder
78-
- `is_recursive` (bool): Whether to list recursively
79-
80-
**Returns:** Folder content in JSON format with id, name, type, and description
81-
82-
### `box_manage_folder_tool`
83-
Create, update, or delete folders in Box.
84-
85-
**Parameters:**
86-
- `action` (str): Action to perform: "create", "delete", or "update"
87-
- `folder_id` (str, optional): ID of the folder (required for delete/update)
88-
- `name` (str, optional): Folder name (required for create, optional for update)
89-
- `parent_id` (str, optional): Parent folder ID (required for create, optional for update)
90-
- `description` (str, optional): Folder description (optional for update)
91-
- `recursive` (bool, optional): Whether to delete recursively (optional for delete)
92-
93-
**Returns:** Status message with folder details
94-
9530
#### `box_search_folder_by_name_tool`
9631
Locate a folder in Box by its name.
9732
- **Parameters:**
@@ -153,15 +88,41 @@ Extract structured data from files using AI with enhanced processing and specifi
15388
- **Parameters:**
15489
- `file_ids` (List[str]): The IDs of the files to read.
15590
- `fields` (List[dict]): The fields to extract from the files.
91+
- `ai_agent_id` (str, optional): The ID of the AI agent to use.
15692
- **Returns:** Enhanced extracted structured data in JSON format.
15793

15894
#### `box_ai_extract_structured_enhanced_using_template_tool`
15995
Extract structured data from files using AI with enhanced processing and a template.
16096
- **Parameters:**
16197
- `file_ids` (List[str]): The IDs of the files to read.
16298
- `template_key` (str): The ID of the template to use for extraction.
99+
- `ai_agent_id` (str, optional): The ID of the AI agent to use.
163100
- **Returns:** Enhanced extracted structured data in JSON format.
164101

102+
### Box User Tools
103+
104+
#### `box_users_list_tool`
105+
List all users in the Box enterprise.
106+
- **Returns:** List of user dictionaries.
107+
108+
#### `box_users_locate_by_email_tool`
109+
Find a user by their email address.
110+
- **Parameters:**
111+
- `email` (str): The user's email address.
112+
- **Returns:** User dictionary if found.
113+
114+
#### `box_users_locate_by_name_tool`
115+
Find a user by their name.
116+
- **Parameters:**
117+
- `name` (str): The user's name.
118+
- **Returns:** User dictionary if found.
119+
120+
#### `box_users_search_by_name_or_email_tool`
121+
Search for users by name or email.
122+
- **Parameters:**
123+
- `query` (str): Name or email to search for.
124+
- **Returns:** List of user dictionaries matching the query.
125+
165126
### Box File Tools
166127

167128
#### `box_read_tool`
@@ -392,17 +353,17 @@ List all Doc Gen jobs that used a specific template.
392353
uv lock
393354
```
394355

395-
4. Create a `.env` file in the root directory and add your Box API credentials:
356+
4. Create a [.env](http://_vscodecontentref_/0) file in the root directory and add your Box API credentials:
396357

397358
For OAuth
398-
```.env
359+
```
399360
BOX_CLIENT_ID=your_client_id
400361
BOX_CLIENT_SECRET=your_client_secret
401362
BOX_REDIRECT_URL = http://localhost:8000/callback
402363
```
403364

404365
For CCG
405-
```.env
366+
```
406367
BOX_CLIENT_ID=your_client_id
407368
BOX_CLIENT_SECRET=your_client_secret
408369
BOX_SUBJECT_TYPE = user # or enterprise

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ description = "Add your description here"
55
readme = "README.md"
66
requires-python = ">=3.13"
77
dependencies = [
8-
"box-ai-agents-toolkit>=0.0.48",
8+
"box-ai-agents-toolkit>=0.0.49",
99
"fastapi>=0.116.2",
1010
"mcp[cli]>=1.14.0",
1111
"python-dotenv>=1.1.1",

src/box_tools_users.py

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
2+
from box_ai_agents_toolkit import (
3+
box_users_list,
4+
box_users_locate_by_name,
5+
box_users_locate_by_email,
6+
box_users_search_by_name_or_email,
7+
)
8+
from mcp.server.fastmcp import Context
9+
10+
from box_tools_generic import get_box_client
11+
12+
13+
async def box_users_list_tool(ctx: Context) -> dict:
14+
"""List all users in the Box account.
15+
Args:
16+
ctx (Context): The context object containing the request and lifespan context.
17+
Returns:
18+
dict: A dictionary containing the list of users."""
19+
client = get_box_client(ctx)
20+
return box_users_list(client)
21+
22+
23+
async def box_users_locate_by_name_tool(ctx: Context, name: str) -> dict:
24+
"""Locate a user by their name. This is an exact match search.
25+
Args:
26+
ctx (Context): The context object containing the request and lifespan context.
27+
name (str): The name of the user to locate.
28+
Returns:
29+
dict: A dictionary containing the user information if found, otherwise a message with no user found."""
30+
client = get_box_client(ctx)
31+
return box_users_locate_by_name(client, name)
32+
33+
34+
async def box_users_locate_by_email_tool(ctx: Context, email: str) -> dict:
35+
"""Locate a user by their email address. This is an exact match search.
36+
Args:
37+
ctx (Context): The context object containing the request and lifespan context.
38+
email (str): The email address of the user to locate.
39+
Returns:
40+
dict: A dictionary containing the user information if found, otherwise a message with no user found."""
41+
client = get_box_client(ctx)
42+
return box_users_locate_by_email(client, email)
43+
44+
45+
async def box_users_search_by_name_or_email_tool(ctx: Context, query: str) -> dict:
46+
"""Search for users by name or email. This is a partial match search.
47+
Args:
48+
ctx (Context): The context object containing the request and lifespan context.
49+
query (str): The search query to match against user names and email addresses.
50+
Returns:
51+
dict: A dictionary containing the list of matching users."""
52+
client = get_box_client(ctx)
53+
return box_users_search_by_name_or_email(client, query)

src/mcp_server_box.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,12 @@
5454
box_metadata_update_instance_on_file_tool,
5555
)
5656
from box_tools_search import box_search_folder_by_name_tool, box_search_tool
57+
from box_tools_users import (
58+
box_users_list_tool,
59+
box_users_locate_by_email_tool,
60+
box_users_locate_by_name_tool,
61+
box_users_search_by_name_or_email_tool,
62+
)
5763
from server_context import box_lifespan_ccg, box_lifespan_oauth
5864

5965
# Disable all logging
@@ -143,6 +149,12 @@ def register_tools(mcp: FastMCP):
143149
mcp.tool()(box_metadata_update_instance_on_file_tool)
144150
mcp.tool()(box_metadata_template_create_tool)
145151

152+
# User Tools
153+
mcp.tool()(box_users_list_tool)
154+
mcp.tool()(box_users_locate_by_name_tool)
155+
mcp.tool()(box_users_locate_by_email_tool)
156+
mcp.tool()(box_users_search_by_name_or_email_tool)
157+
146158

147159
if __name__ == "__main__":
148160
parser = argparse.ArgumentParser(description="Box MCP Server")

tests/test_box_tools_users.py

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import pytest
2+
from unittest.mock import MagicMock, patch
3+
4+
from src.box_tools_users import (
5+
box_users_list_tool,
6+
box_users_locate_by_email_tool,
7+
box_users_locate_by_name_tool,
8+
box_users_search_by_name_or_email_tool,
9+
)
10+
from mcp.server.fastmcp import Context
11+
12+
13+
@pytest.mark.asyncio
14+
async def test_box_users_list_tool():
15+
ctx = MagicMock(spec=Context)
16+
with (
17+
patch("src.box_tools_users.get_box_client") as mock_get_client,
18+
patch("src.box_tools_users.box_users_list") as mock_list,
19+
):
20+
mock_get_client.return_value = "client"
21+
mock_list.return_value = [{"id": "1", "name": "Test User"}]
22+
result = await box_users_list_tool(ctx)
23+
assert isinstance(result, list)
24+
assert result[0]["name"] == "Test User"
25+
26+
27+
@pytest.mark.asyncio
28+
async def test_box_users_locate_by_email_tool():
29+
ctx = MagicMock(spec=Context)
30+
email = "test@example.com"
31+
with (
32+
patch("src.box_tools_users.get_box_client") as mock_get_client,
33+
patch("src.box_tools_users.box_users_locate_by_email") as mock_locate,
34+
):
35+
mock_get_client.return_value = "client"
36+
mock_locate.return_value = {"id": "1", "email": email}
37+
result = await box_users_locate_by_email_tool(ctx, email)
38+
assert isinstance(result, dict)
39+
assert result["email"] == email
40+
41+
42+
@pytest.mark.asyncio
43+
async def test_box_users_locate_by_name_tool():
44+
ctx = MagicMock(spec=Context)
45+
name = "Test User"
46+
with (
47+
patch("src.box_tools_users.get_box_client") as mock_get_client,
48+
patch("src.box_tools_users.box_users_locate_by_name") as mock_locate,
49+
):
50+
mock_get_client.return_value = "client"
51+
mock_locate.return_value = {"id": "1", "name": name}
52+
result = await box_users_locate_by_name_tool(ctx, name)
53+
assert isinstance(result, dict)
54+
assert result["name"] == name
55+
56+
57+
@pytest.mark.asyncio
58+
async def test_box_users_search_by_name_or_email_tool():
59+
ctx = MagicMock(spec=Context)
60+
query = "Test"
61+
with (
62+
patch("src.box_tools_users.get_box_client") as mock_get_client,
63+
patch("src.box_tools_users.box_users_search_by_name_or_email") as mock_search,
64+
):
65+
mock_get_client.return_value = "client"
66+
mock_search.return_value = [{"id": "1", "name": "Test User"}]
67+
result = await box_users_search_by_name_or_email_tool(ctx, query)
68+
assert isinstance(result, list)
69+
assert result[0]["name"] == "Test User"

tests/test_mcp_server_box.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
import pytest
55
from mcp.server.fastmcp import FastMCP
66

7-
from mcp_server_box import get_mcp_server
87

98

109
@pytest.fixture

uv.lock

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)