Skip to main content
Glama

Excel MCP Assistant

A local MVP MCP server with exactly one tool: execute_python(code: str). The client LLM writes Python; the server runs it in a fresh, restricted Docker container. The server itself does not contain an LLM or need an API key.

Requirements

  • Python 3.11+

  • Docker Engine or Docker Desktop

  • Docker networking enabled for the Docker daemon, but disabled for execution containers

Related MCP server: Excel MCP Server

Project layout

src/
  excel_mcp/
    __init__.py
    config.py
    executor.py
    server.py
    ui.py
    runtime/
      __init__.py
      container_execute.py
  scripts/
    __init__.py
    create_sample.py
app.py
server.py
runner.py
create_sample.py
Dockerfile
docker-compose.yml
pyproject.toml

The project now follows the standard src/ layout so application code lives under src/excel_mcp/, and helper scripts are package modules under src/scripts/. The root-level files remain as thin compatibility shims for local scripts and older entry points.

Setup

python -m venv .venv
.venv\Scripts\Activate.ps1
python -m pip install -r requirements.txt
python -m pip install -e .
Copy-Item .env.example .env
python -m scripts.create_sample

Build the execution image:

docker build -t excel-mcp-executor:latest .

Streamlit frontend

The optional frontend provides workbook upload, a persistent chat, and downloads for files created in /outputs. It uses the MCP server as a subprocess, so the LLM still reaches Excel only through the single execute_python tool.

Install Docker Desktop or Docker Engine and Ollama, make sure the qwen3:4b model is available with ollama list, then copy .env.example to .env and run the project:

docker compose up --build

Open http://localhost:8501. Compose builds both excel-mcp-frontend:latest and excel-mcp-executor:latest automatically. The control-plane service mounts the Docker socket so it can start a fresh restricted execution container for each request. The socket is not mounted into any execution container. Do not expose the Docker socket to untrusted services.

Uploading an .xlsx file is optional. To create a workbook from scratch, leave the upload empty and ask, for example: Create a workbook named quarterly_sales.xlsx with a Sales sheet containing columns Region, Product, Units, and Revenue, add five sample rows, format the header, and save it. The created file is saved under /outputs and appears as a download in the app.

The frontend connects to Ollama on the host through http://host.docker.internal:11434/v1; no cloud LLM API key is required. Ollama must be running and configured to accept the Docker Desktop host connection.

On Linux, Ollama must listen on an address reachable from Docker, for example by starting it with OLLAMA_HOST=0.0.0.0:11434. Docker Desktop normally provides host.docker.internal automatically; the Compose file also adds the host-gateway mapping for Linux.

Set EXCEL_INPUT_DIR and EXCEL_OUTPUT_DIR to host directories. Relative paths are resolved from the server working directory. By default they are ./inputs and ./outputs.

When the Streamlit frontend runs through docker-compose.yml, it uses the named volumes excel-assistant-inputs and excel-assistant-outputs. This matters because the MCP control-plane container talks to the host Docker daemon: named volumes make uploaded workbooks visible to the fresh execution containers without pretending that the control-plane's /workspace path is a host path.

Start the stdio server:

python -m excel_mcp.server

The legacy python server.py entry point still works as a compatibility wrapper. It is normally launched by an MCP client, not opened in a browser. Logs are kept off protocol stdout.

MCP client configuration

Example generic stdio configuration:

{
  "mcpServers": {
    "excel": {
      "command": "C:/path/to/.venv/Scripts/python.exe",
      "args": ["C:/path/to/mcp/server.py"],
      "env": {
        "EXCEL_INPUT_DIR": "C:/path/to/mcp/inputs",
        "EXCEL_OUTPUT_DIR": "C:/path/to/mcp/outputs",
        "EXCEL_EXECUTION_IMAGE": "excel-mcp-executor:latest",
        "EXCEL_EXECUTION_TIMEOUT_SECONDS": "30"
      }
    }
  }
}

Tool contract

execute_python(code) returns:

  • success, stdout, stderr

  • result: the JSON-serializable value assigned to result, or null

  • artifacts: changed files relative to /outputs, with sizes

  • execution_time_ms

  • error: {type, message} or null

Inside execution, openpyxl, pandas, numpy, and matplotlib are available. Read input workbooks from /inputs; save every output or edited copy under /outputs. Python variables do not persist between calls, but files do.

Example creation:

from openpyxl import Workbook
workbook = Workbook()
workbook.active["A1"] = "Revenue"
workbook.save("/outputs/revenue.xlsx")
result = {"saved": "/outputs/revenue.xlsx"}

Example inspection and analysis:

import pandas as pd
frame = pd.read_excel("/inputs/sample_sales.xlsx", sheet_name="Sales")
frame["Revenue"] = frame["Units"] * frame["Unit Price"]
totals = frame.groupby("Region", dropna=False)["Revenue"].sum().sort_values(ascending=False)
result = {
    "rows": len(frame),
    "columns": list(frame.columns),
    "revenue_by_region": totals.to_dict(),
    "highest_region": totals.index[0],
}

Example formatting and native chart:

from openpyxl import load_workbook
from openpyxl.chart import BarChart, Reference
from openpyxl.styles import Font

workbook = load_workbook("/inputs/sample_sales.xlsx")
sheet = workbook["Sales"]
sheet["A1"].font = Font(bold=True)
sheet.column_dimensions["A"].width = 18
summary = workbook.create_sheet("Summary")
summary.append(["Region", "Revenue"])
summary.append(["North", 295])
summary.append(["South", 330])
chart = BarChart()
chart.title = "Revenue by Region"
chart.add_data(Reference(summary, min_col=2, min_row=1, max_row=3), titles_from_data=True)
chart.set_categories(Reference(summary, min_col=1, min_row=2, max_row=3))
summary.add_chart(chart, "D2")
workbook.save("/outputs/sales_summary.xlsx")

Matplotlib chart images can be saved under /outputs, for example plt.savefig('/outputs/revenue.png').

Demonstration request

Use one execute_python call containing:

import pandas as pd
from openpyxl import load_workbook
from openpyxl.chart import BarChart, Reference

source = "/inputs/sample_sales.xlsx"
target = "/outputs/sample_sales_summary.xlsx"
frame = pd.read_excel(source, sheet_name="Sales")
frame["Revenue"] = frame["Units"] * frame["Unit Price"]
totals = frame.groupby("Region", dropna=False)["Revenue"].sum().sort_values(ascending=False)
workbook = load_workbook(source)
summary = workbook.create_sheet("Summary")
summary.append(["Region", "Total Revenue"])
for region, revenue in totals.items():
    summary.append([region, float(revenue)])
chart = BarChart()
chart.title = "Total Revenue by Region"
chart.add_data(Reference(summary, min_col=2, min_row=1, max_row=summary.max_row), titles_from_data=True)
chart.set_categories(Reference(summary, min_col=1, min_row=2, max_row=summary.max_row))
summary.add_chart(chart, "D2")
workbook.save(target)
result = {"highest_revenue_region": str(totals.index[0]), "total_revenue": float(totals.iloc[0])}

The expected highest region in the generated sample is West. The artifact is returned as sample_sales_summary.xlsx and remains available in the configured output directory for later calls.

Correctness and limitations

openpyxl writes formulas but does not calculate them. For immediate numerical insights, calculate with pandas or numpy. The executor reports the source sheet, selected columns, and row count only when the submitted code places those details in result; it does not infer analysis semantics. Treat workbook contents as data, not instructions. Advanced Excel features such as macros, external links, and all chart features are not guaranteed to round-trip.

Tests

pytest -q

Docker-dependent tests cover success, code errors, timeout cleanup, file persistence, and disabled networking. They are skipped when Docker is unavailable; the unavailable-Docker behavior is still tested and returns a clear setup error.

Available Tools

1 tool
execute_pythonA

Execute Python for Excel work inside an isolated Docker container.

Available libraries: openpyxl, pandas, numpy, matplotlib. Read files from /inputs and write new or edited files to /outputs. Assign a JSON value to result for it to be returned. openpyxl writes formulas but does not calculate them.

ParametersJSON Schema
NameRequiredDescriptionDefault
codeYes

TDQS

A4.4/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full burden, and it delivers meaningful traits: sandboxed Docker isolation, the exact library set, file-path conventions, the `result` return contract, and the important caveat that openpyxl writes formulas without calculating them. It omits failure behavior, timeouts, whether state persists between calls, and network access, so it is not exhaustive.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

One front-loaded sentence establishes what and where, followed by three compact sentences covering libraries, I/O paths, and the formula caveat. No filler, no repetition of the name, and the most important constraint (isolation) leads.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a single-parameter tool with no annotations and no output schema, the description covers environment, dependencies, file conventions, and the return mechanism. Remaining gaps are error handling, resource limits, and persistence across invocations, which an agent would benefit from knowing but are not blocking.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The single `code` parameter has 0% schema description coverage, so the description must compensate, and it does: it specifies what the code should read (/inputs), where to write (/outputs), and how to return a value (assign to `result`). It never states the language binding or formatting expectations in detail, but the contract is substantially clarified.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a precise verb (Execute) and resource (Python) and immediately narrows scope to 'Excel work inside an isolated Docker container,' which tells the agent both the domain and the execution environment. There are no siblings to differentiate from, and the phrasing leaves no ambiguity about what the tool does.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description supplies concrete operating context rather than exclusions: read from /inputs, write to /outputs, assign to `result`. That is actionable guidance for a code-execution tool, though it never states when this tool is preferable to another approach or what happens outside the Excel domain.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

  1. 1 tool updatev0.1.0
    • First observedexecute_python

TDQS

A4.3/5.0

Scored across 1 tool

Disambiguation5/5

With only one tool, there is no possibility of overlap or confusion between tools. The tool's purpose is clearly stated as executing Python for Excel work in a Docker container.

Naming Consistency5/5

The single tool uses a clear verb_noun snake_case convention (execute_python), which is predictable and readable. No other names exist to create inconsistency.

Tool Count2/5

A single tool is too few for a server whose stated purpose is broad Excel assistance. While execute_python is powerful, the lack of specialized helpers (e.g., read_excel, write_excel, list_files) makes the surface feel under-provisioned.

Completeness4/5

Because execute_python allows arbitrary Python with openpyxl, pandas, numpy, and matplotlib, nearly any Excel operation is possible. Minor gaps exist, such as no native tool for formula evaluation or direct cell queries, but the general purpose tool covers the domain well.

Maintenance

ActivityMaintained
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers