Skip to content

Commit 3889403

Browse files
authored
Implementing MCP sse/http transport (#12)
* [refactor] separateing tools * adding SSE and HTTP transports * [REF] separating box tools * [REF] fix reported problems (types, unused, etc) * [REF] output dict in box search tool * [REF] output dict in several tools * [REF] separate tools generic and search + integration testing * [REF] separate tools AI + integration testing * [REF] separate tools docgen + MISSING integration testing * [REF] separate tools files+folders + integration testing * [REF] Ruff linting * version 0.1.1 * updating read.me
1 parent e3c2f3a commit 3889403

26 files changed

+1505
-928
lines changed

.vscode/settings.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,10 @@
55
"python.testing.unittestEnabled": false,
66
"python.testing.pytestEnabled": true,
77
"cSpell.words": [
8+
"Airbyte",
9+
"docgen",
810
"dotenv",
9-
"fastmcp"
11+
"fastmcp",
12+
"streamable"
1013
]
1114
}

README.md

Lines changed: 17 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,11 @@ The Model Context Protocol (MCP) is a framework designed to standardize the way
1010

1111
### Box API Tools
1212

13-
#### `box_who_am_i`
13+
### `box_who_am_i`
1414
Get your current user information and check connection status.
1515
- **Returns:** User information string
1616

17-
#### `box_authorize_app_tool`
17+
### `box_authorize_app_tool`
1818
Start the Box application authorization process.
1919
- **Returns:** Authorization status message
2020

@@ -27,7 +27,7 @@ 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`
30+
### `box_read_tool`
3131
Read the text content of a Box file.
3232

3333
**Parameters:**
@@ -305,14 +305,20 @@ List all Doc Gen jobs that used a specific template.
305305

306306
### Running the MCP Server
307307

308-
To start the MCP server, run the following command:
308+
The MCP server supports four transport methods: **stdio** (default), **SSE** (Server-Sent Events), **HTTP** (StreamableHttp), and **FastAPI**.
309+
310+
#### Running with stdio transport (default)
309311

310312
```sh
311-
uv --directory /Users/anovotny/Desktop/mcp-server-box run src/mcp_server_box.py
313+
uv --directory /path/to/mcp-server-box run src/mcp_server_box.py
312314
```
313315

316+
317+
314318
### Using Claude as the client
315319

320+
#### For stdio transport (recommended for Claude Desktop)
321+
316322
1. Edit your `claude_desktop_config.json`:
317323

318324
```sh
@@ -328,7 +334,7 @@ uv --directory /Users/anovotny/Desktop/mcp-server-box run src/mcp_server_box.py
328334
"command": "uv",
329335
"args": [
330336
"--directory",
331-
"/Users/anovotny/Desktop/mcp-server-box",
337+
"/path/to/mcp-server-box",
332338
"run",
333339
"src/mcp_server_box.py"
334340
]
@@ -339,8 +345,11 @@ uv --directory /Users/anovotny/Desktop/mcp-server-box run src/mcp_server_box.py
339345

340346
3. Restart Claude if it is running.
341347

348+
342349
### Using Cursor as the client
343350

351+
#### For stdio transport
352+
344353
1. Open your IDE with Cursor.
345354
2. In settings, select `Cursor settings`.
346355
3. In the left nav, select `MCP`.
@@ -354,7 +363,7 @@ uv --directory /Users/anovotny/Desktop/mcp-server-box run src/mcp_server_box.py
354363
"command": "uv",
355364
"args": [
356365
"--directory",
357-
"/Users/shurrey/local/mcp-server-box",
366+
"/path/to/mcp-server-box",
358367
"run",
359368
"src/mcp_server_box.py"
360369
]
@@ -365,6 +374,7 @@ uv --directory /Users/anovotny/Desktop/mcp-server-box run src/mcp_server_box.py
365374
366375
6. Save and close the mcp.json file, and restart if necessary.
367376
377+
368378
## Running Tests
369379
370380
The project includes a suite of tests to verify Box API functionality. Before running the tests, update the file and folder IDs in the test files to match those in your Box account.
@@ -395,15 +405,6 @@ pytest -v
395405
pytest -v -s
396406
```
397407
398-
### Available Test Suites
399-
400-
- `test_box_auth.py`: Tests authentication functionality.
401-
- `test_box_api_basic.py`: Basic Box API tests.
402-
- `test_box_api_read.py`: Tests file reading capabilities.
403-
- `test_box_api_search.py`: Tests search functionality.
404-
- `test_box_api_ai.py`: Tests AI-based features.
405-
- `test_box_api_file_ops.py`: Tests file upload and download operations.
406-
- Additional tests cover folder operations and Doc Gen features.
407408
408409
## Troubleshooting
409410

pyproject.toml

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,18 @@
11
[project]
22
name = "mcp-server-box"
3-
version = "0.1.0"
3+
version = "0.1.1"
44
description = "Add your description here"
55
readme = "README.md"
66
requires-python = ">=3.13"
77
dependencies = [
88
"box-ai-agents-toolkit>=0.0.42",
9-
"box-sdk-gen>=1.13.0",
10-
"mcp[cli]>=1.6.0",
11-
"python-dotenv>=1.1.0",
9+
"fastapi>=0.115.14",
10+
"mcp[cli]>=1.10.1",
11+
"python-dotenv>=1.1.1",
1212
]
1313

1414
[dependency-groups]
1515
dev = ["pytest>=8.3.5", "pytest-asyncio>=0.26.0", "pytest-cov>=6.1.0"]
16+
17+
[tool.pytest.ini_options]
18+
pythonpath = [".", "src"]

src/box_tools_ai.py

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
from typing import List
2+
3+
from box_ai_agents_toolkit import (
4+
box_file_ai_ask,
5+
box_file_ai_extract,
6+
box_hubs_ai_ask,
7+
box_multi_file_ai_ask,
8+
box_multi_file_ai_extract,
9+
)
10+
from mcp.server.fastmcp import Context
11+
12+
from box_tools_generic import get_box_client
13+
14+
15+
async def box_ask_ai_tool(ctx: Context, file_id: str, prompt: str) -> dict:
16+
"""
17+
Ask box ai about a file in Box.
18+
19+
Args:
20+
file_id (str): The ID of the file to read.
21+
prompt (str): The prompt to ask the AI.
22+
return:
23+
dict: The text content of the file.
24+
"""
25+
# check if file id isn't a string and convert to a string
26+
if not isinstance(file_id, str):
27+
file_id = str(file_id)
28+
29+
box_client = get_box_client(ctx)
30+
response = box_file_ai_ask(box_client, file_id, prompt=prompt)
31+
return response
32+
33+
34+
async def box_ask_ai_tool_multi_file(
35+
ctx: Context, file_ids: List[str], prompt: str
36+
) -> dict:
37+
"""
38+
Use Box AI to analyze and respond to a prompt based on the content of multiple files.
39+
40+
This tool allows users to query Box AI with a specific prompt, leveraging the content
41+
of multiple files stored in Box. The AI processes the files and generates a response
42+
based on the provided prompt.
43+
44+
Args:
45+
ctx (Context): The context object containing the request and lifespan context.
46+
file_ids (List[str]): A list of file IDs to be analyzed by the AI.
47+
prompt (str): The prompt or question to ask the AI.
48+
49+
Returns:
50+
dict: The AI-generated response based on the content of the specified files.
51+
52+
Raises:
53+
Exception: If there is an issue with the Box client, AI agent, or file processing.
54+
"""
55+
box_client = get_box_client(ctx)
56+
response = box_multi_file_ai_ask(box_client, file_ids, prompt=prompt)
57+
return response
58+
59+
60+
async def box_hubs_ask_ai_tool(ctx: Context, hubs_id: str, prompt: str) -> dict:
61+
"""
62+
Ask box ai about a hub in Box. Currently there is no way to discover a hub
63+
in Box, so you need to know the id of the hub. We will fix this in the future.
64+
65+
Args:
66+
hubs_id (str): The ID of the hub to read.
67+
prompt (str): The prompt to ask the AI.
68+
return:
69+
dict: The text content of the file.
70+
"""
71+
# check if file id isn't a string and convert to a string
72+
if not isinstance(hubs_id, str):
73+
hubs_id = str(hubs_id)
74+
75+
box_client = get_box_client(ctx)
76+
# ai_agent = box_claude_ai_agent_ask()
77+
response = box_hubs_ai_ask(box_client, hubs_id, prompt=prompt)
78+
return response
79+
80+
81+
async def box_ai_extract_tool(ctx: Context, file_id: str, fields: str) -> dict:
82+
""" "
83+
Extract data from a single file in Box using AI.
84+
85+
Args:
86+
file_id (str): The ID of the file to read.
87+
fields (str): The fields to extract from the file.
88+
return:
89+
dict: The extracted data in a json string format.
90+
"""
91+
box_client = get_box_client(ctx)
92+
93+
# check if file id isn't a string and convert to a string
94+
if not isinstance(file_id, str):
95+
file_id = str(file_id)
96+
97+
response = box_file_ai_extract(box_client, file_id, fields)
98+
return response
99+
100+
101+
async def box_ai_extract_tool_multi_file(
102+
ctx: Context, file_ids: List[str], fields: str
103+
) -> dict:
104+
""" "
105+
Extract data from multiple files in Box using AI.
106+
107+
Args:
108+
file_ids (List[str]): The IDs of the files to read.
109+
fields (str): The fields to extract from the files.
110+
return:
111+
dict: The extracted data in a json string format.
112+
"""
113+
box_client = get_box_client(ctx)
114+
115+
# check if file ids aren't a list of strings and convert to a list of strings
116+
if not isinstance(file_ids, list):
117+
file_ids = [str(file_ids)]
118+
else:
119+
file_ids = [str(file_id) for file_id in file_ids]
120+
121+
response = box_multi_file_ai_extract(box_client, file_ids, fields)
122+
return response

0 commit comments

Comments
 (0)