ExcelTamer 0.2.0__tar.gz → 0.2.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {exceltamer-0.2.0 → exceltamer-0.2.1}/ExcelTamer/mcp/server.py +23 -24
- {exceltamer-0.2.0 → exceltamer-0.2.1}/ExcelTamer.egg-info/PKG-INFO +39 -4
- {exceltamer-0.2.0 → exceltamer-0.2.1}/ExcelTamer.egg-info/SOURCES.txt +1 -0
- {exceltamer-0.2.0 → exceltamer-0.2.1}/MANIFEST.in +1 -0
- {exceltamer-0.2.0 → exceltamer-0.2.1}/PKG-INFO +39 -4
- {exceltamer-0.2.0 → exceltamer-0.2.1}/README.md +38 -3
- exceltamer-0.2.1/docs/developer_guide.md +202 -0
- {exceltamer-0.2.0 → exceltamer-0.2.1}/pyproject.toml +9 -9
- {exceltamer-0.2.0 → exceltamer-0.2.1}/test/test_mcp_smoke.py +91 -0
- {exceltamer-0.2.0 → exceltamer-0.2.1}/ExcelTamer/__init__.py +0 -0
- {exceltamer-0.2.0 → exceltamer-0.2.1}/ExcelTamer/mcp/__init__.py +0 -0
- {exceltamer-0.2.0 → exceltamer-0.2.1}/ExcelTamer/mcp/audit.py +0 -0
- {exceltamer-0.2.0 → exceltamer-0.2.1}/ExcelTamer/mcp/config.py +0 -0
- {exceltamer-0.2.0 → exceltamer-0.2.1}/ExcelTamer/mcp/engine/__init__.py +0 -0
- {exceltamer-0.2.0 → exceltamer-0.2.1}/ExcelTamer/mcp/engine/diff.py +0 -0
- {exceltamer-0.2.0 → exceltamer-0.2.1}/ExcelTamer/mcp/engine/read.py +0 -0
- {exceltamer-0.2.0 → exceltamer-0.2.1}/ExcelTamer/mcp/engine/search.py +0 -0
- {exceltamer-0.2.0 → exceltamer-0.2.1}/ExcelTamer/mcp/engine/workbook.py +0 -0
- {exceltamer-0.2.0 → exceltamer-0.2.1}/ExcelTamer/mcp/engine/write.py +0 -0
- {exceltamer-0.2.0 → exceltamer-0.2.1}/ExcelTamer/mcp/excel.py +0 -0
- {exceltamer-0.2.0 → exceltamer-0.2.1}/ExcelTamer/mcp/main.py +0 -0
- {exceltamer-0.2.0 → exceltamer-0.2.1}/ExcelTamer/mcp/prompts/README.md +0 -0
- {exceltamer-0.2.0 → exceltamer-0.2.1}/ExcelTamer/mcp/prompts/financial_metric_extract.md +0 -0
- {exceltamer-0.2.0 → exceltamer-0.2.1}/ExcelTamer/mcp/prompts/safe_edit.md +0 -0
- {exceltamer-0.2.0 → exceltamer-0.2.1}/ExcelTamer/mcp/safety.py +0 -0
- {exceltamer-0.2.0 → exceltamer-0.2.1}/ExcelTamer/mcp/schemas/__init__.py +0 -0
- {exceltamer-0.2.0 → exceltamer-0.2.1}/ExcelTamer/mcp/sessions.py +0 -0
- {exceltamer-0.2.0 → exceltamer-0.2.1}/ExcelTamer.egg-info/dependency_links.txt +0 -0
- {exceltamer-0.2.0 → exceltamer-0.2.1}/ExcelTamer.egg-info/entry_points.txt +0 -0
- {exceltamer-0.2.0 → exceltamer-0.2.1}/ExcelTamer.egg-info/requires.txt +0 -0
- {exceltamer-0.2.0 → exceltamer-0.2.1}/ExcelTamer.egg-info/top_level.txt +0 -0
- {exceltamer-0.2.0 → exceltamer-0.2.1}/LICENSE +0 -0
- {exceltamer-0.2.0 → exceltamer-0.2.1}/setup.cfg +0 -0
|
@@ -498,36 +498,35 @@ async def run_stdio():
|
|
|
498
498
|
server.create_initialization_options()
|
|
499
499
|
)
|
|
500
500
|
|
|
501
|
-
async def run_sse(port: int):
|
|
502
|
-
from mcp.server.sse import SseServerTransport
|
|
503
|
-
from starlette.applications import Starlette
|
|
504
|
-
from starlette.
|
|
505
|
-
import
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
501
|
+
async def run_sse(port: int):
|
|
502
|
+
from mcp.server.sse import SseServerTransport
|
|
503
|
+
from starlette.applications import Starlette
|
|
504
|
+
from starlette.responses import Response
|
|
505
|
+
from starlette.routing import Mount, Route
|
|
506
|
+
import uvicorn
|
|
507
|
+
|
|
508
|
+
sse = SseServerTransport("/messages/")
|
|
509
|
+
|
|
510
|
+
async def handle_sse(request):
|
|
511
|
+
async with sse.connect_sse(
|
|
511
512
|
request.scope,
|
|
512
513
|
request.receive,
|
|
513
514
|
request._send
|
|
514
515
|
) as streams:
|
|
515
516
|
await server.run(
|
|
516
517
|
streams[0],
|
|
517
|
-
streams[1],
|
|
518
|
-
server.create_initialization_options()
|
|
519
|
-
)
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
]
|
|
530
|
-
)
|
|
518
|
+
streams[1],
|
|
519
|
+
server.create_initialization_options()
|
|
520
|
+
)
|
|
521
|
+
return Response()
|
|
522
|
+
|
|
523
|
+
app = Starlette(
|
|
524
|
+
debug=True,
|
|
525
|
+
routes=[
|
|
526
|
+
Route("/sse", endpoint=handle_sse, methods=["GET"]),
|
|
527
|
+
Mount("/messages/", app=sse.handle_post_message),
|
|
528
|
+
]
|
|
529
|
+
)
|
|
531
530
|
|
|
532
531
|
config = uvicorn.Config(app, host="0.0.0.0", port=port, log_level="info")
|
|
533
532
|
server_instance = uvicorn.Server(config)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ExcelTamer
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.1
|
|
4
4
|
Summary: A Model Context Protocol server for safe Microsoft Excel automation
|
|
5
5
|
Author: Shamit
|
|
6
6
|
License-Expression: GPL-3.0-only
|
|
@@ -33,6 +33,9 @@ client.
|
|
|
33
33
|
below or read the
|
|
34
34
|
[complete MCP usage guide](https://github.com/shamitv/ExcelTamer/blob/main/docs/mcp_server.md).
|
|
35
35
|
|
|
36
|
+
**Building or contributing?** Read the
|
|
37
|
+
[developer guide](https://github.com/shamitv/ExcelTamer/blob/main/docs/developer_guide.md).
|
|
38
|
+
|
|
36
39
|
## Quick start: use ExcelTamer with an MCP client
|
|
37
40
|
|
|
38
41
|
### 1. Install ExcelTamer
|
|
@@ -41,13 +44,19 @@ ExcelTamer requires Windows with Microsoft Excel installed and Python 3.11 or
|
|
|
41
44
|
newer.
|
|
42
45
|
|
|
43
46
|
```powershell
|
|
44
|
-
pip install
|
|
47
|
+
pip install ExcelTamer
|
|
45
48
|
```
|
|
46
49
|
|
|
47
50
|
### 2. Add ExcelTamer to your MCP client
|
|
48
51
|
|
|
49
|
-
|
|
50
|
-
|
|
52
|
+
Choose either stdio or SSE, depending on the transports supported by your MCP
|
|
53
|
+
client.
|
|
54
|
+
|
|
55
|
+
#### Stdio
|
|
56
|
+
|
|
57
|
+
For the default stdio transport, add this server definition to your MCP
|
|
58
|
+
client's configuration, replacing the allowed root with the directory
|
|
59
|
+
containing your workbooks:
|
|
51
60
|
|
|
52
61
|
```json
|
|
53
62
|
{
|
|
@@ -66,6 +75,32 @@ replacing the allowed root with the directory containing your workbooks:
|
|
|
66
75
|
access. The server defaults to read-only workbook mode and records write
|
|
67
76
|
operations in an audit log.
|
|
68
77
|
|
|
78
|
+
#### SSE
|
|
79
|
+
|
|
80
|
+
To use SSE, start ExcelTamer separately in PowerShell. Set server environment
|
|
81
|
+
variables in the same shell:
|
|
82
|
+
|
|
83
|
+
```powershell
|
|
84
|
+
$env:EXCELTAMER_MCP_ALLOWED_ROOTS = "C:\Users\you\Documents\Excel"
|
|
85
|
+
exceltamer-mcp --port 8123
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Then configure an SSE-capable MCP client to connect to the stream endpoint:
|
|
89
|
+
|
|
90
|
+
```json
|
|
91
|
+
{
|
|
92
|
+
"mcpServers": {
|
|
93
|
+
"exceltamer": {
|
|
94
|
+
"url": "http://127.0.0.1:8123/sse"
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Client configuration field names can vary; use
|
|
101
|
+
`http://127.0.0.1:8123/sse` as the server URL. ExcelTamer receives client
|
|
102
|
+
messages at `http://127.0.0.1:8123/messages/`.
|
|
103
|
+
|
|
69
104
|
### 3. Restart the client and ask it to use Excel
|
|
70
105
|
|
|
71
106
|
For inspection, try:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ExcelTamer
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.1
|
|
4
4
|
Summary: A Model Context Protocol server for safe Microsoft Excel automation
|
|
5
5
|
Author: Shamit
|
|
6
6
|
License-Expression: GPL-3.0-only
|
|
@@ -33,6 +33,9 @@ client.
|
|
|
33
33
|
below or read the
|
|
34
34
|
[complete MCP usage guide](https://github.com/shamitv/ExcelTamer/blob/main/docs/mcp_server.md).
|
|
35
35
|
|
|
36
|
+
**Building or contributing?** Read the
|
|
37
|
+
[developer guide](https://github.com/shamitv/ExcelTamer/blob/main/docs/developer_guide.md).
|
|
38
|
+
|
|
36
39
|
## Quick start: use ExcelTamer with an MCP client
|
|
37
40
|
|
|
38
41
|
### 1. Install ExcelTamer
|
|
@@ -41,13 +44,19 @@ ExcelTamer requires Windows with Microsoft Excel installed and Python 3.11 or
|
|
|
41
44
|
newer.
|
|
42
45
|
|
|
43
46
|
```powershell
|
|
44
|
-
pip install
|
|
47
|
+
pip install ExcelTamer
|
|
45
48
|
```
|
|
46
49
|
|
|
47
50
|
### 2. Add ExcelTamer to your MCP client
|
|
48
51
|
|
|
49
|
-
|
|
50
|
-
|
|
52
|
+
Choose either stdio or SSE, depending on the transports supported by your MCP
|
|
53
|
+
client.
|
|
54
|
+
|
|
55
|
+
#### Stdio
|
|
56
|
+
|
|
57
|
+
For the default stdio transport, add this server definition to your MCP
|
|
58
|
+
client's configuration, replacing the allowed root with the directory
|
|
59
|
+
containing your workbooks:
|
|
51
60
|
|
|
52
61
|
```json
|
|
53
62
|
{
|
|
@@ -66,6 +75,32 @@ replacing the allowed root with the directory containing your workbooks:
|
|
|
66
75
|
access. The server defaults to read-only workbook mode and records write
|
|
67
76
|
operations in an audit log.
|
|
68
77
|
|
|
78
|
+
#### SSE
|
|
79
|
+
|
|
80
|
+
To use SSE, start ExcelTamer separately in PowerShell. Set server environment
|
|
81
|
+
variables in the same shell:
|
|
82
|
+
|
|
83
|
+
```powershell
|
|
84
|
+
$env:EXCELTAMER_MCP_ALLOWED_ROOTS = "C:\Users\you\Documents\Excel"
|
|
85
|
+
exceltamer-mcp --port 8123
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Then configure an SSE-capable MCP client to connect to the stream endpoint:
|
|
89
|
+
|
|
90
|
+
```json
|
|
91
|
+
{
|
|
92
|
+
"mcpServers": {
|
|
93
|
+
"exceltamer": {
|
|
94
|
+
"url": "http://127.0.0.1:8123/sse"
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Client configuration field names can vary; use
|
|
101
|
+
`http://127.0.0.1:8123/sse` as the server URL. ExcelTamer receives client
|
|
102
|
+
messages at `http://127.0.0.1:8123/messages/`.
|
|
103
|
+
|
|
69
104
|
### 3. Restart the client and ask it to use Excel
|
|
70
105
|
|
|
71
106
|
For inspection, try:
|
|
@@ -9,6 +9,9 @@ client.
|
|
|
9
9
|
below or read the
|
|
10
10
|
[complete MCP usage guide](https://github.com/shamitv/ExcelTamer/blob/main/docs/mcp_server.md).
|
|
11
11
|
|
|
12
|
+
**Building or contributing?** Read the
|
|
13
|
+
[developer guide](https://github.com/shamitv/ExcelTamer/blob/main/docs/developer_guide.md).
|
|
14
|
+
|
|
12
15
|
## Quick start: use ExcelTamer with an MCP client
|
|
13
16
|
|
|
14
17
|
### 1. Install ExcelTamer
|
|
@@ -17,13 +20,19 @@ ExcelTamer requires Windows with Microsoft Excel installed and Python 3.11 or
|
|
|
17
20
|
newer.
|
|
18
21
|
|
|
19
22
|
```powershell
|
|
20
|
-
pip install
|
|
23
|
+
pip install ExcelTamer
|
|
21
24
|
```
|
|
22
25
|
|
|
23
26
|
### 2. Add ExcelTamer to your MCP client
|
|
24
27
|
|
|
25
|
-
|
|
26
|
-
|
|
28
|
+
Choose either stdio or SSE, depending on the transports supported by your MCP
|
|
29
|
+
client.
|
|
30
|
+
|
|
31
|
+
#### Stdio
|
|
32
|
+
|
|
33
|
+
For the default stdio transport, add this server definition to your MCP
|
|
34
|
+
client's configuration, replacing the allowed root with the directory
|
|
35
|
+
containing your workbooks:
|
|
27
36
|
|
|
28
37
|
```json
|
|
29
38
|
{
|
|
@@ -42,6 +51,32 @@ replacing the allowed root with the directory containing your workbooks:
|
|
|
42
51
|
access. The server defaults to read-only workbook mode and records write
|
|
43
52
|
operations in an audit log.
|
|
44
53
|
|
|
54
|
+
#### SSE
|
|
55
|
+
|
|
56
|
+
To use SSE, start ExcelTamer separately in PowerShell. Set server environment
|
|
57
|
+
variables in the same shell:
|
|
58
|
+
|
|
59
|
+
```powershell
|
|
60
|
+
$env:EXCELTAMER_MCP_ALLOWED_ROOTS = "C:\Users\you\Documents\Excel"
|
|
61
|
+
exceltamer-mcp --port 8123
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Then configure an SSE-capable MCP client to connect to the stream endpoint:
|
|
65
|
+
|
|
66
|
+
```json
|
|
67
|
+
{
|
|
68
|
+
"mcpServers": {
|
|
69
|
+
"exceltamer": {
|
|
70
|
+
"url": "http://127.0.0.1:8123/sse"
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Client configuration field names can vary; use
|
|
77
|
+
`http://127.0.0.1:8123/sse` as the server URL. ExcelTamer receives client
|
|
78
|
+
messages at `http://127.0.0.1:8123/messages/`.
|
|
79
|
+
|
|
45
80
|
### 3. Restart the client and ask it to use Excel
|
|
46
81
|
|
|
47
82
|
For inspection, try:
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
# ExcelTamer Developer Guide
|
|
2
|
+
|
|
3
|
+
This guide covers the Windows development workflow for ExcelTamer: setting up
|
|
4
|
+
an editable checkout, navigating the MCP implementation, running automated and
|
|
5
|
+
live validation, and building release artifacts. For MCP client configuration
|
|
6
|
+
and the public protocol reference, see the [MCP server guide](mcp_server.md).
|
|
7
|
+
|
|
8
|
+
## Prerequisites
|
|
9
|
+
|
|
10
|
+
- Windows
|
|
11
|
+
- Python 3.11, 3.12, 3.13, or 3.14
|
|
12
|
+
- Git
|
|
13
|
+
- Microsoft Excel for live workbook validation
|
|
14
|
+
|
|
15
|
+
Microsoft Excel is not required to run the automated smoke tests or build the
|
|
16
|
+
package.
|
|
17
|
+
|
|
18
|
+
## Set up a development environment
|
|
19
|
+
|
|
20
|
+
Clone the repository and enter its directory:
|
|
21
|
+
|
|
22
|
+
```powershell
|
|
23
|
+
git clone https://github.com/shamitv/ExcelTamer.git
|
|
24
|
+
Set-Location ExcelTamer
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Create and activate a virtual environment:
|
|
28
|
+
|
|
29
|
+
```powershell
|
|
30
|
+
py -m venv .venv
|
|
31
|
+
.\.venv\Scripts\Activate.ps1
|
|
32
|
+
python --version
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
The reported Python version must be between 3.11 and 3.14. Install ExcelTamer
|
|
36
|
+
in editable mode, then install the build-validation tools:
|
|
37
|
+
|
|
38
|
+
```powershell
|
|
39
|
+
python -m pip install --upgrade pip
|
|
40
|
+
python -m pip install -e .
|
|
41
|
+
python -m pip install build twine
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
`build` and `twine` are development tools and are intentionally not installed
|
|
45
|
+
as ExcelTamer runtime dependencies.
|
|
46
|
+
|
|
47
|
+
## Repository layout
|
|
48
|
+
|
|
49
|
+
| Path | Purpose |
|
|
50
|
+
| --- | --- |
|
|
51
|
+
| `pyproject.toml` | Package metadata, supported Python versions, runtime dependencies, and the `exceltamer-mcp` entry point |
|
|
52
|
+
| `ExcelTamer/mcp/main.py` | CLI parsing and stdio/SSE transport selection |
|
|
53
|
+
| `ExcelTamer/mcp/server.py` | MCP tools, resources, prompts, request dispatch, and transport hosts |
|
|
54
|
+
| `ExcelTamer/mcp/engine/` | Workbook lifecycle, read, write, search, checkpoint, and audit-history operations |
|
|
55
|
+
| `ExcelTamer/mcp/excel.py` | Internal xlwings backend |
|
|
56
|
+
| `ExcelTamer/mcp/config.py` | Environment-driven server settings |
|
|
57
|
+
| `ExcelTamer/mcp/safety.py` | Workbook path validation |
|
|
58
|
+
| `ExcelTamer/mcp/audit.py` and `sessions.py` | Write-audit persistence and active workbook sessions |
|
|
59
|
+
| `ExcelTamer/mcp/prompts/` | Markdown bodies for the packaged MCP prompts |
|
|
60
|
+
| `test/test_mcp_smoke.py` | Automated protocol and engine smoke tests |
|
|
61
|
+
| `test/mcp_client.py` | Standalone stdio/SSE validation client |
|
|
62
|
+
| `docs/excel/` | Internal Excel behavior, constraints, and threading documentation |
|
|
63
|
+
|
|
64
|
+
The MCP protocol is the public integration boundary. Treat the xlwings backend
|
|
65
|
+
and engine modules as internal implementation details.
|
|
66
|
+
|
|
67
|
+
## Run the development server
|
|
68
|
+
|
|
69
|
+
After the editable install, the `exceltamer-mcp` command runs the current
|
|
70
|
+
checkout.
|
|
71
|
+
|
|
72
|
+
### Stdio
|
|
73
|
+
|
|
74
|
+
Run the default stdio transport:
|
|
75
|
+
|
|
76
|
+
```powershell
|
|
77
|
+
exceltamer-mcp
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
The equivalent module command is:
|
|
81
|
+
|
|
82
|
+
```powershell
|
|
83
|
+
python -m ExcelTamer.mcp.main
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
The standalone client starts its own stdio server subprocess, performs MCP
|
|
87
|
+
discovery, retrieves the packaged prompts, and exits:
|
|
88
|
+
|
|
89
|
+
```powershell
|
|
90
|
+
python test/mcp_client.py
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### SSE
|
|
94
|
+
|
|
95
|
+
Start the SSE server in one PowerShell window. Environment variables must be
|
|
96
|
+
set in the server process:
|
|
97
|
+
|
|
98
|
+
```powershell
|
|
99
|
+
$env:EXCELTAMER_MCP_ALLOWED_ROOTS = (Get-Location).Path
|
|
100
|
+
exceltamer-mcp --port 8123
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
In a second activated PowerShell window, connect the validation client:
|
|
104
|
+
|
|
105
|
+
```powershell
|
|
106
|
+
python test/mcp_client.py --transport sse --port 8123
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
The SSE stream is available at `http://127.0.0.1:8123/sse`; client messages are
|
|
110
|
+
posted to `http://127.0.0.1:8123/messages/`. See the
|
|
111
|
+
[configuration reference](mcp_server.md#configuration) for every environment
|
|
112
|
+
variable that influences the server.
|
|
113
|
+
|
|
114
|
+
## Test changes
|
|
115
|
+
|
|
116
|
+
Run the automated suite from the repository root:
|
|
117
|
+
|
|
118
|
+
```powershell
|
|
119
|
+
python -m unittest discover -s test -p "test_*.py" -v
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
The current suite runs eight tests covering the MCP surface, packaged prompts,
|
|
123
|
+
range normalization, engine read/search/write behavior, and a real stdio
|
|
124
|
+
and SSE handshake. These tests use fakes where workbook behavior is needed and
|
|
125
|
+
do not launch Microsoft Excel.
|
|
126
|
+
|
|
127
|
+
For optional live validation, use the included example workbook:
|
|
128
|
+
|
|
129
|
+
```powershell
|
|
130
|
+
python test/mcp_client.py --file .\test\example.xlsx
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
This opens the workbook through Excel in read-only mode, retrieves its
|
|
134
|
+
structure, and closes it. To run the same check against an already running SSE
|
|
135
|
+
server:
|
|
136
|
+
|
|
137
|
+
```powershell
|
|
138
|
+
python test/mcp_client.py --transport sse --port 8123 --file .\test\example.xlsx
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
The workbook must be under one of `EXCELTAMER_MCP_ALLOWED_ROOTS`. The default
|
|
142
|
+
allowed root is the current working directory.
|
|
143
|
+
|
|
144
|
+
## Build and verify packages
|
|
145
|
+
|
|
146
|
+
Run the following workflow from a clean repository checkout with the virtual
|
|
147
|
+
environment activated.
|
|
148
|
+
|
|
149
|
+
### 1. Confirm the package version
|
|
150
|
+
|
|
151
|
+
The version used in both artifact names comes from `pyproject.toml`:
|
|
152
|
+
|
|
153
|
+
```powershell
|
|
154
|
+
Select-String -Path .\pyproject.toml -Pattern '^version = '
|
|
155
|
+
git status --short
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
The current version is `0.2.1`. Review unexpected working-tree changes before
|
|
159
|
+
building.
|
|
160
|
+
|
|
161
|
+
### 2. Run the automated tests
|
|
162
|
+
|
|
163
|
+
```powershell
|
|
164
|
+
python -m unittest discover -s test -p "test_*.py" -v
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
### 3. Build the source and binary distributions
|
|
168
|
+
|
|
169
|
+
```powershell
|
|
170
|
+
python -m build
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
For version `0.2.1`, this creates:
|
|
174
|
+
|
|
175
|
+
- `dist\exceltamer-0.2.1.tar.gz` — source distribution
|
|
176
|
+
- `dist\exceltamer-0.2.1-py3-none-any.whl` — binary wheel
|
|
177
|
+
|
|
178
|
+
The `dist/` directory is ignored by Git. If it contains artifacts from other
|
|
179
|
+
versions, verify only the files for the version being prepared.
|
|
180
|
+
|
|
181
|
+
### 4. Check PyPI metadata and README rendering
|
|
182
|
+
|
|
183
|
+
```powershell
|
|
184
|
+
python -m twine check `
|
|
185
|
+
.\dist\exceltamer-0.2.1.tar.gz `
|
|
186
|
+
.\dist\exceltamer-0.2.1-py3-none-any.whl
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
Both artifacts must report `PASSED`.
|
|
190
|
+
|
|
191
|
+
### 5. Generate optional SHA-256 hashes
|
|
192
|
+
|
|
193
|
+
```powershell
|
|
194
|
+
Get-FileHash -Algorithm SHA256 `
|
|
195
|
+
.\dist\exceltamer-0.2.1.tar.gz, `
|
|
196
|
+
.\dist\exceltamer-0.2.1-py3-none-any.whl
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
Record these hashes when artifacts are transferred or retained for a release.
|
|
200
|
+
|
|
201
|
+
Uploading to TestPyPI or PyPI, managing credentials, and publishing a release
|
|
202
|
+
are intentionally outside the scope of this guide.
|
|
@@ -1,19 +1,19 @@
|
|
|
1
|
-
[build-system]
|
|
2
|
-
requires = ["setuptools>=83.0.0"]
|
|
3
|
-
build-backend = "setuptools.build_meta"
|
|
4
|
-
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=83.0.0"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
5
|
[project]
|
|
6
6
|
name = "ExcelTamer"
|
|
7
|
-
version = "0.2.
|
|
7
|
+
version = "0.2.1"
|
|
8
8
|
description = "A Model Context Protocol server for safe Microsoft Excel automation"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
authors = [
|
|
11
11
|
{ name = "Shamit" },
|
|
12
12
|
]
|
|
13
|
-
license = "GPL-3.0-only"
|
|
14
|
-
license-files = ["LICENSE"]
|
|
15
|
-
classifiers = [
|
|
16
|
-
"Programming Language :: Python :: 3",
|
|
13
|
+
license = "GPL-3.0-only"
|
|
14
|
+
license-files = ["LICENSE"]
|
|
15
|
+
classifiers = [
|
|
16
|
+
"Programming Language :: Python :: 3",
|
|
17
17
|
"Programming Language :: Python :: 3.11",
|
|
18
18
|
"Programming Language :: Python :: 3.12",
|
|
19
19
|
"Programming Language :: Python :: 3.13",
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import asyncio
|
|
4
4
|
import os
|
|
5
|
+
import socket
|
|
5
6
|
import sys
|
|
6
7
|
import unittest
|
|
7
8
|
from types import SimpleNamespace
|
|
@@ -9,6 +10,7 @@ from unittest.mock import patch
|
|
|
9
10
|
|
|
10
11
|
import pandas as pd
|
|
11
12
|
from mcp import ClientSession, StdioServerParameters
|
|
13
|
+
from mcp.client.sse import sse_client
|
|
12
14
|
from mcp.client.stdio import stdio_client
|
|
13
15
|
|
|
14
16
|
from ExcelTamer.mcp.engine import read, search, write
|
|
@@ -191,5 +193,94 @@ class StdioHandshakeTests(unittest.IsolatedAsyncioTestCase):
|
|
|
191
193
|
self.assertTrue(safe_edit.messages[0].content.text.strip())
|
|
192
194
|
|
|
193
195
|
|
|
196
|
+
class SseHandshakeTests(unittest.IsolatedAsyncioTestCase):
|
|
197
|
+
async def test_sse_discovery_and_prompt_retrieval(self):
|
|
198
|
+
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as port_socket:
|
|
199
|
+
port_socket.bind(("127.0.0.1", 0))
|
|
200
|
+
port = port_socket.getsockname()[1]
|
|
201
|
+
|
|
202
|
+
env = os.environ.copy()
|
|
203
|
+
env["PYTHONPATH"] = os.pathsep.join(
|
|
204
|
+
path for path in (os.getcwd(), env.get("PYTHONPATH")) if path
|
|
205
|
+
)
|
|
206
|
+
process = await asyncio.create_subprocess_exec(
|
|
207
|
+
sys.executable,
|
|
208
|
+
"-m",
|
|
209
|
+
"ExcelTamer.mcp.main",
|
|
210
|
+
"--port",
|
|
211
|
+
str(port),
|
|
212
|
+
env=env,
|
|
213
|
+
stdout=asyncio.subprocess.DEVNULL,
|
|
214
|
+
stderr=asyncio.subprocess.PIPE,
|
|
215
|
+
)
|
|
216
|
+
stderr_text = ""
|
|
217
|
+
try:
|
|
218
|
+
await self._wait_for_listener(process, port)
|
|
219
|
+
async with asyncio.timeout(10):
|
|
220
|
+
async with sse_client(
|
|
221
|
+
f"http://127.0.0.1:{port}/sse",
|
|
222
|
+
timeout=5,
|
|
223
|
+
sse_read_timeout=5,
|
|
224
|
+
) as streams:
|
|
225
|
+
async with ClientSession(*streams) as session:
|
|
226
|
+
await session.initialize()
|
|
227
|
+
tools = await session.list_tools()
|
|
228
|
+
resources = await session.list_resources()
|
|
229
|
+
prompts = await session.list_prompts()
|
|
230
|
+
safe_edit = await session.get_prompt("safe-edit")
|
|
231
|
+
|
|
232
|
+
self.assertEqual(len(tools.tools), 15)
|
|
233
|
+
self.assertEqual(len(resources.resources), 1)
|
|
234
|
+
self.assertEqual(len(prompts.prompts), 2)
|
|
235
|
+
self.assertTrue(safe_edit.messages[0].content.text.strip())
|
|
236
|
+
|
|
237
|
+
# Give the request handler time to finish after the SSE client closes.
|
|
238
|
+
await asyncio.sleep(0.1)
|
|
239
|
+
finally:
|
|
240
|
+
if process.returncode is None:
|
|
241
|
+
process.terminate()
|
|
242
|
+
try:
|
|
243
|
+
await asyncio.wait_for(process.wait(), timeout=5)
|
|
244
|
+
except TimeoutError:
|
|
245
|
+
process.kill()
|
|
246
|
+
await process.wait()
|
|
247
|
+
if process.stderr is not None:
|
|
248
|
+
stderr_text = (await process.stderr.read()).decode(
|
|
249
|
+
errors="replace"
|
|
250
|
+
)
|
|
251
|
+
|
|
252
|
+
self.assertNotIn("Exception in ASGI application", stderr_text)
|
|
253
|
+
self.assertNotIn(
|
|
254
|
+
"TypeError: 'NoneType' object is not callable",
|
|
255
|
+
stderr_text,
|
|
256
|
+
)
|
|
257
|
+
|
|
258
|
+
async def _wait_for_listener(self, process, port):
|
|
259
|
+
loop = asyncio.get_running_loop()
|
|
260
|
+
deadline = loop.time() + 10
|
|
261
|
+
while loop.time() < deadline:
|
|
262
|
+
if process.returncode is not None:
|
|
263
|
+
stderr = ""
|
|
264
|
+
if process.stderr is not None:
|
|
265
|
+
stderr = (await process.stderr.read()).decode(
|
|
266
|
+
errors="replace"
|
|
267
|
+
)
|
|
268
|
+
self.fail(
|
|
269
|
+
f"SSE server exited before accepting connections:\n{stderr}"
|
|
270
|
+
)
|
|
271
|
+
try:
|
|
272
|
+
_reader, writer = await asyncio.open_connection(
|
|
273
|
+
"127.0.0.1",
|
|
274
|
+
port,
|
|
275
|
+
)
|
|
276
|
+
except OSError:
|
|
277
|
+
await asyncio.sleep(0.05)
|
|
278
|
+
continue
|
|
279
|
+
writer.close()
|
|
280
|
+
await writer.wait_closed()
|
|
281
|
+
return
|
|
282
|
+
self.fail(f"SSE server did not listen on port {port} within 10 seconds")
|
|
283
|
+
|
|
284
|
+
|
|
194
285
|
if __name__ == "__main__":
|
|
195
286
|
unittest.main()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|