Skip to content

Commit 92a8c0c

Browse files
committed
Initial VBA tool
1 parent c0bbee5 commit 92a8c0c

File tree

15 files changed

+636
-11
lines changed

15 files changed

+636
-11
lines changed

Cargo.lock

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

Cargo.toml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ lru = "0.12"
2222
ahash = "0.8"
2323
smallvec = "1.13"
2424
umya-spreadsheet = "2.3.3"
25+
ovba = "0.7.1"
2526
formualizer-parse = { version = "0.1.0" }
2627
walkdir = "2.5"
2728
globset = "0.4"
@@ -43,14 +44,14 @@ base64 = { version = "0.22", optional = true }
4344

4445
async-trait = { version = "0.1", optional = true }
4546
uuid = { version = "1.10", features = ["v4"], optional = true }
46-
zip = { version = "0.6", optional = true }
47+
zip = "0.6"
4748
quick-xml = { version = "0.31", optional = true }
4849
xxhash-rust = { version = "0.8", features = ["xxh64"], optional = true }
4950
tempfile = "3.10"
5051

5152
[features]
5253
default = []
53-
recalc = ["async-trait", "uuid", "zip", "quick-xml", "xxhash-rust", "image", "base64"]
54+
recalc = ["async-trait", "uuid", "quick-xml", "xxhash-rust", "image", "base64"]
5455
docker-tests = []
5556

5657
[dev-dependencies]

README.md

Lines changed: 40 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@ MCP server for spreadsheet analysis and editing. Slim, token-efficient tool surf
1212

1313
Dumping a 50,000-row spreadsheet into an LLM context is expensive and usually unnecessary. Most spreadsheet tasks need surgical access: find a region, profile its structure, read a filtered slice. This server exposes tools that let agents **discover → profile → extract** without burning tokens on cells they don't need.
1414

15-
- **Full support:** `.xlsx` (via `umya-spreadsheet`)
15+
- **Full support:** `.xlsx`, `.xlsm` (via `umya-spreadsheet`)
16+
- **VBA source inspection (optional):** `.xlsm` via `SPREADSHEET_MCP_VBA_ENABLED=true` / `--vba-enabled` (parses embedded `xl/vbaProject.bin` via `ovba`)
1617
- **Discovery only:** `.xls`, `.xlsb` (enumerated, not parsed)
1718

1819
## Architecture
@@ -36,9 +37,26 @@ Dumping a 50,000-row spreadsheet into an LLM context is expensive and usually un
3637
| `sheet_formula_map`, `formula_trace`, `scan_volatiles` | Formula analysis and tracing |
3738
| `sheet_styles`, `workbook_style_summary` | Style inspection (sheet-scoped + workbook-wide) |
3839
| `named_ranges` | List defined names + tables |
40+
| `vba_project_summary`, `vba_module_source` | Read VBA project metadata + module source (disabled by default; `.xlsm`) |
3941
| `get_manifest_stub` | Generate manifest scaffold |
4042
| `close_workbook` | Evict workbook from cache |
4143

44+
## VBA Support (Read-Only)
45+
46+
VBA tools are **disabled by default**. When enabled, the server can extract and parse the embedded VBA project from `.xlsm` files and return module source code.
47+
48+
Enable via:
49+
- CLI: `--vba-enabled`
50+
- Env: `SPREADSHEET_MCP_VBA_ENABLED=true`
51+
52+
Tools:
53+
- `vba_project_summary`: Lists modules + basic project metadata
54+
- `vba_module_source`: Returns paged source for a single module
55+
56+
Notes:
57+
- This does **not** execute macros; it only reads and returns text.
58+
- Responses are size-limited; page through module source.
59+
4260
## Write & Recalc Support
4361

4462
Write tools allow "what-if" analysis: fork a workbook, edit cells, recalculate formulas via LibreOffice, and diff the results. For safety, you can create checkpoints for high‑fidelity rollback and apply previewed (staged) changes explicitly.
@@ -201,6 +219,9 @@ Two image variants are published:
201219
# Read-only (slim image)
202220
docker run -v /path/to/workbooks:/data -p 8079:8079 ghcr.io/psu3d0/spreadsheet-mcp:latest
203221

222+
# Read-only + VBA tools enabled
223+
docker run -v /path/to/workbooks:/data -p 8079:8079 -e SPREADSHEET_MCP_VBA_ENABLED=true ghcr.io/psu3d0/spreadsheet-mcp:latest
224+
204225
# With write/recalc support (full image)
205226
docker run -v /path/to/workbooks:/data -p 8079:8079 ghcr.io/psu3d0/spreadsheet-mcp:full
206227
```
@@ -211,6 +232,9 @@ docker run -v /path/to/workbooks:/data -p 8079:8079 ghcr.io/psu3d0/spreadsheet-m
211232
# Read-only
212233
cargo install spreadsheet-mcp
213234
spreadsheet-mcp --workspace-root /path/to/workbooks
235+
236+
# Enable VBA tools
237+
SPREADSHEET_MCP_VBA_ENABLED=true spreadsheet-mcp --workspace-root /path/to/workbooks
214238
```
215239

216240
**Note:** For write/recalc features, use the `:full` Docker image instead of cargo install. The Docker image includes LibreOffice with required macro configuration.
@@ -243,13 +267,25 @@ Add to `~/.claude.json` or project `.mcp.json`:
243267
}
244268
```
245269

270+
**Read-only + VBA tools enabled:**
271+
```json
272+
{
273+
"mcpServers": {
274+
"spreadsheet": {
275+
"command": "docker",
276+
"args": ["run", "-i", "--rm", "-v", "/path/to/workbooks:/data", "ghcr.io/psu3d0/spreadsheet-mcp:latest", "--transport", "stdio", "--vba-enabled"]
277+
}
278+
}
279+
}
280+
```
281+
246282
**With write/recalc (full image):**
247283
```json
248284
{
249285
"mcpServers": {
250286
"spreadsheet": {
251287
"command": "docker",
252-
"args": ["run", "-i", "--rm", "-v", "/path/to/workbooks:/data", "-i", "ghcr.io/psu3d0/spreadsheet-mcp:latest-full", "--transport", "stdio", "--recalc-enabled"]
288+
"args": ["run", "-i", "--rm", "-v", "/path/to/workbooks:/data", "ghcr.io/psu3d0/spreadsheet-mcp:latest-full", "--transport", "stdio", "--recalc-enabled"]
253289
}
254290
}
255291
}
@@ -275,7 +311,7 @@ Add to `~/.claude.json` or project `.mcp.json`:
275311
"mcp.servers": {
276312
"spreadsheet": {
277313
"command": "docker",
278-
"args": ["run", "-i", "--rm", "-v", "${workspaceFolder}:/data", "-i", "ghcr.io/psu3d0/spreadsheet-mcp:latest", "--transport", "stdio"]
314+
"args": ["run", "-i", "--rm", "-v", "${workspaceFolder}:/data", "ghcr.io/psu3d0/spreadsheet-mcp:latest", "--transport", "stdio"]
279315
}
280316
}
281317
}
@@ -287,7 +323,7 @@ Add to `~/.claude.json` or project `.mcp.json`:
287323
"mcp.servers": {
288324
"spreadsheet": {
289325
"command": "docker",
290-
"args": ["run", "-i", "--rm", "-v", "${workspaceFolder}:/data", "-i", "ghcr.io/psu3d0/spreadsheet-mcp:latest-full", "--transport", "stdio", "--recalc-enabled"]
326+
"args": ["run", "-i", "--rm", "-v", "${workspaceFolder}:/data", "ghcr.io/psu3d0/spreadsheet-mcp:latest-full", "--transport", "stdio", "--recalc-enabled"]
291327
}
292328
}
293329
}

src/config.rs

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use std::path::{Path, PathBuf};
88

99
const DEFAULT_CACHE_CAPACITY: usize = 5;
1010
const DEFAULT_MAX_RECALCS: usize = 2;
11-
const DEFAULT_EXTENSIONS: &[&str] = &["xlsx", "xls", "xlsb"];
11+
const DEFAULT_EXTENSIONS: &[&str] = &["xlsx", "xlsm", "xls", "xlsb"];
1212
const DEFAULT_HTTP_BIND: &str = "127.0.0.1:8079";
1313

1414
#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum, Serialize, Deserialize)]
@@ -39,6 +39,7 @@ pub struct ServerConfig {
3939
pub transport: TransportKind,
4040
pub http_bind_address: SocketAddr,
4141
pub recalc_enabled: bool,
42+
pub vba_enabled: bool,
4243
pub max_concurrent_recalcs: usize,
4344
pub allow_overwrite: bool,
4445
}
@@ -55,6 +56,7 @@ impl ServerConfig {
5556
transport: cli_transport,
5657
http_bind: cli_http_bind,
5758
recalc_enabled: cli_recalc_enabled,
59+
vba_enabled: cli_vba_enabled,
5860
max_concurrent_recalcs: cli_max_concurrent_recalcs,
5961
allow_overwrite: cli_allow_overwrite,
6062
} = args;
@@ -74,6 +76,7 @@ impl ServerConfig {
7476
transport: file_transport,
7577
http_bind: file_http_bind,
7678
recalc_enabled: file_recalc_enabled,
79+
vba_enabled: file_vba_enabled,
7780
max_concurrent_recalcs: file_max_concurrent_recalcs,
7881
allow_overwrite: file_allow_overwrite,
7982
} = file_config;
@@ -174,6 +177,7 @@ impl ServerConfig {
174177
});
175178

176179
let recalc_enabled = cli_recalc_enabled || file_recalc_enabled.unwrap_or(false);
180+
let vba_enabled = cli_vba_enabled || file_vba_enabled.unwrap_or(false);
177181

178182
let max_concurrent_recalcs = cli_max_concurrent_recalcs
179183
.or(file_max_concurrent_recalcs)
@@ -191,6 +195,7 @@ impl ServerConfig {
191195
transport,
192196
http_bind_address,
193197
recalc_enabled,
198+
vba_enabled,
194199
max_concurrent_recalcs,
195200
allow_overwrite,
196201
})
@@ -321,6 +326,13 @@ pub struct CliArgs {
321326
)]
322327
pub recalc_enabled: bool,
323328

329+
#[arg(
330+
long,
331+
env = "SPREADSHEET_MCP_VBA_ENABLED",
332+
help = "Enable VBA introspection tools (read-only)"
333+
)]
334+
pub vba_enabled: bool,
335+
324336
#[arg(
325337
long,
326338
env = "SPREADSHEET_MCP_MAX_CONCURRENT_RECALCS",
@@ -346,6 +358,7 @@ struct PartialConfig {
346358
transport: Option<TransportKind>,
347359
http_bind: Option<SocketAddr>,
348360
recalc_enabled: Option<bool>,
361+
vba_enabled: Option<bool>,
349362
max_concurrent_recalcs: Option<usize>,
350363
allow_overwrite: Option<bool>,
351364
}

src/model.rs

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -814,3 +814,47 @@ pub struct CloseWorkbookResponse {
814814
pub workbook_id: WorkbookId,
815815
pub message: String,
816816
}
817+
818+
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
819+
pub struct VbaProjectSummaryResponse {
820+
pub workbook_id: WorkbookId,
821+
pub workbook_short_id: String,
822+
pub has_vba: bool,
823+
pub code_page: Option<u16>,
824+
pub sys_kind: Option<String>,
825+
pub modules: Vec<VbaModuleDescriptor>,
826+
pub modules_truncated: bool,
827+
pub references: Vec<VbaReferenceDescriptor>,
828+
pub references_truncated: bool,
829+
pub notes: Vec<String>,
830+
}
831+
832+
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
833+
pub struct VbaModuleDescriptor {
834+
pub name: String,
835+
pub stream_name: String,
836+
pub doc_string: String,
837+
pub text_offset: u64,
838+
pub help_context: u32,
839+
pub module_type: String,
840+
pub read_only: bool,
841+
pub private: bool,
842+
}
843+
844+
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
845+
pub struct VbaReferenceDescriptor {
846+
pub kind: String,
847+
pub debug: String,
848+
}
849+
850+
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
851+
pub struct VbaModuleSourceResponse {
852+
pub workbook_id: WorkbookId,
853+
pub workbook_short_id: String,
854+
pub module_name: String,
855+
pub offset_lines: u32,
856+
pub limit_lines: u32,
857+
pub total_lines: u32,
858+
pub truncated: bool,
859+
pub source: String,
860+
}

0 commit comments

Comments
 (0)