Skip to main content
Glama
knglumt

SparxEA-MCP-Server

by knglumt

SparxEA-MCP-Server

A read-only MCP server for Sparx Systems Enterprise Architect repositories, written in Python, built to run on Ubuntu. It connects directly to the EA project database - no running copy of EA.exe, no Windows, no .NET runtime required.

How it connects: one driver, any DBMS EA supports

Sparx EA can store a repository in several different DBMSs: SQL Server, MySQL/MariaDB, PostgreSQL, Oracle, Firebird, SQLite, or MS Access. This server connects to all of the server-based ones via ODBC (pyodbc) - which is exactly what Sparx EA's own documentation recommends for server-based repositories that aren't using its "Native Connection": "set up an ODBC driver to enable connection to the repository."

Because every query in repository.py is written in strict ANSI SQL:2008 - FETCH FIRST n ROWS ONLY instead of vendor-specific TOP/ LIMIT, ? placeholders (the ODBC standard, honored by every driver regardless of the DBMS's native paramstyle), double-quoted identifiers for reserved words - this server is DBMS-agnostic by construction rather than needing a different code path per backend. You only ever install one Python dependency (pyodbc); which database you're pointed at is purely a matter of which ODBC driver is registered and what's in .env.

Verified against real servers, not just assumed: this was tested end-to-end against live PostgreSQL 16 and MariaDB 10.11 instances (in addition to SQL Server), catching real cross-backend differences along the way - see Known cross-DBMS quirks.

What this means isn't supported

  • SQLite (.qea/.qeax): SQLite has no FETCH FIRST clause, so it can't run this server's queries as written. If your model is a local SQLite file, either use EA's Project Transfer feature to move it to one of the DBMSs above first, or ask for a SQLite-specific fork of repository.py (it's a small, mechanical change - swap FETCH FIRST n ROWS ONLY for LIMIT n).

  • MS Access (.eap/.eapx, Jet/ACE): there's no maintained Linux ODBC driver for Jet with reliable SQL support. If you're on Access, use EA's Project Transfer feature to move to one of the supported DBMSs.

  • Live-EA-session tools (opening diagrams in the EA UI, selecting elements in the Browser, etc.) - there's no running EA.exe to drive. This server only implements the read/browse side.

Related MCP server: MSSQL MCP Server

Requirements

  • Ubuntu (22.04/24.04 LTS primarily tested; the general approach works on any Linux with unixODBC)

  • Python 3.10+

  • unixODBC, plus the ODBC driver for your specific DBMS (see below)

  • A DB login with read-only access to the EA repository database

Installing the right ODBC driver

Your DBMS

Ubuntu package

Driver name for .env

SQL Server (Microsoft's driver)

msodbcsql18 (from Microsoft's apt repo)

ODBC Driver 18 for SQL Server

SQL Server (open-source alternative)

tdsodbc

FreeTDS

MySQL or MariaDB

odbc-mariadb

MariaDB Unicode

PostgreSQL

odbc-postgresql

PostgreSQL Unicode

Oracle

manual (Oracle-licensed download)

varies - see below

Firebird

manual (not packaged for Ubuntu)

varies - see below

./install.sh automates the first four. For Oracle and Firebird, install.sh prints the manual steps (both require downloading a driver from outside Ubuntu's package repos - Oracle's due to licensing, Firebird's because it simply isn't packaged there).

After installing any driver, confirm it registered:

odbcinst -q -d

The bracketed name shown (e.g. [MariaDB Unicode]) is what goes in EA_DB_DRIVER.

Setup

Option A: automated

cd sparxea-mcp-server
chmod +x install.sh
./install.sh                    # prompts you to pick a backend
# or: ./install.sh --backend postgresql

This installs unixODBC + the matching driver, creates a .venv, installs the project into it, and writes a .env from .env.example with EA_DB_DRIVER/EA_DB_BACKEND pre-filled for your choice. Safe to re-run.

Option B: manual

sudo apt-get update
sudo apt-get install -y unixodbc unixodbc-dev
sudo apt-get install -y odbc-mariadb      # or odbc-postgresql, or tdsodbc, etc.

python3 -m venv .venv
source .venv/bin/activate
pip install -e .

cp .env.example .env
# edit .env: EA_DB_DRIVER, EA_DB_SERVER, EA_DB_NAME, EA_DB_USER, EA_DB_PASSWORD

Verify

.venv/bin/python3 scripts/smoke_test.py

Prints the live DBMS name/version (via ODBC's own SQLGetInfo, not a vendor-specific query - so this line works identically on every backend), a model overview, and the top-level packages. Fix any errors here before wiring up an MCP client.

Running

python3 -m sparx_ea_mcp.server

Or, after pip install -e .:

sparxea-mcp-server

Either way the server speaks MCP over STDIO, the same transport the official Windows add-in uses.

Connecting an MCP client

Claude Desktop / Claude Code (claude_desktop_config.json or .mcp.json)

{
  "mcpServers": {
    "Enterprise Architect": {
      "command": "/absolute/path/to/sparxea-mcp-server/.venv/bin/python3",
      "args": ["-m", "sparx_ea_mcp.server"],
      "env": {
        "EA_DB_DRIVER": "MariaDB Unicode",
        "EA_DB_BACKEND": "mariadb",
        "EA_DB_SERVER": "db-host.example.com",
        "EA_DB_NAME": "ea_repository",
        "EA_DB_USER": "ea_reader",
        "EA_DB_PASSWORD": "change-me"
      }
    }
  }
}

(Or omit env and rely on the .env file next to the project - server.py loads it automatically via python-dotenv.)

Claude Desktop, connecting to a private-network HTTP deployment

If the server is already running in HTTP mode on a machine you can reach over your LAN/VPN but that has no public domain and no TLS (e.g. a bare internal IP like http://10.0.0.1:8765/mcp), Claude Desktop's built-in "Custom Connector" UI (Customize → Connectors) will not work for this - it always connects from Anthropic's cloud infrastructure, not your device, so a private IP is simply unreachable that way, even from Claude Desktop running on the same network.

The fix: bridge it through mcp-remote in claude_desktop_config.json, which runs as a local process on your machine and does the actual HTTP call from there - that's real local network traffic, not Anthropic's cloud:

{
  "mcpServers": {
    "Enterprise Architect": {
      "command": "npx",
      "args": [
        "-y",
        "mcp-remote",
        "http://10.0.0.1:8765/mcp",
        "--allow-http",
        "--header",
        "Authorization:${AUTH_HEADER}"
      ],
      "env": {
        "AUTH_HEADER": "Bearer <your MCP_AUTH_TOKEN value>"
      }
    }
  }
}

A few details here that matter (each was a real failure mode when setting this up):

  • -y is required - without it, npx's interactive first-run confirmation prompt has nothing to answer it (Claude Desktop launches this headless), and the connection just hangs and then reports "Server disconnected" with no useful detail.

  • --allow-http is required for anything other than localhost - mcp-remote refuses plain HTTP to any other host by default as a safety measure (Non-HTTPS URLs are only allowed for localhost or when --allow-http flag is provided). This is fine to accept for a trusted internal network; never do this for a public address.

  • The env var name must match the ${...} placeholder exactly - ${AUTH_HEADER} in args only resolves if env defines AUTH_HEADER, not some other name.

  • No space around the : in Authorization:${AUTH_HEADER} - Claude Desktop on Windows has a known bug where spaces inside args values get mangled when it invokes npx. Put the space inside the env var's value instead ("Bearer <token>"), not in the arg string.

To debug this independent of Claude Desktop (which hides the real error behind a generic "Server disconnected"), run the exact same command in a terminal:

npx -y mcp-remote http://10.0.0.1:8765/mcp --allow-http --header "Authorization:Bearer <your token>"

Connected to remote server using StreamableHTTPClientTransport / Proxy established successfully means it works - restart Claude Desktop and it should too. Any other output (auth rejection, connection refused, etc.) is the actual problem, uncut.

VS Code (.vscode/mcp.json)

{
  "servers": {
    "Enterprise Architect": {
      "command": "/absolute/path/to/sparxea-mcp-server/.venv/bin/python3",
      "args": ["-m", "sparx_ea_mcp.server"]
    }
  }
}

Hosting on a server

By default this server speaks MCP over STDIO: a client (Claude Desktop, Claude Code) launches it as a local subprocess. That's all you need if the assistant runs on the same Ubuntu box as the database, or if you're fine using Claude only from that machine.

To make it reachable remotely - so you can use it from Claude.ai's Custom Connectors on any device, or share it with a team - run it as a persistent HTTP service instead. Three things change from local use:

  1. Transport: set MCP_TRANSPORT=http (env var already in .env.example).

  2. Process supervision: run it under systemd so it survives reboots and restarts on crash (deploy/sparxea-mcp-server.service).

  3. A reverse proxy for HTTPS: Claude's custom connectors require HTTPS, and Claude connects to your server from Anthropic's cloud, not from your device - so the server must be reachable on the public internet (or you allowlist Anthropic's IP ranges for a private-network setup; see Anthropic's network requirements doc if you need that route instead).

1. Enable HTTP transport

In .env:

MCP_TRANSPORT=http
MCP_HTTP_HOST=127.0.0.1   # bind locally; the reverse proxy is what's public
MCP_HTTP_PORT=8765
MCP_AUTH_TOKEN=$(openssl rand -hex 32)   # put the generated value in .env, not this command
MCP_PUBLIC_HOSTNAME=mcp.your-domain.example.com   # the domain clients will use

MCP_AUTH_TOKEN is a shared secret: any request to /mcp must send Authorization: Bearer <token> or get a 401. This is the same style of auth Claude's custom connector UI has a dedicated field for ("If your server's documentation shows Authorization: Bearer YOUR_TOKEN, enter Bearer followed by your token"). Never run with MCP_AUTH_TOKEN unset on a port reachable from the internet.

MCP_PUBLIC_HOSTNAME matters for a subtler reason: the MCP SDK's built-in DNS-rebinding protection only allows Host/Origin headers matching 127.0.0.1/localhost by default. A reverse proxy forwards the original public hostname, which would otherwise get every request rejected with 421 Invalid Host header - setting this env var adds your real domain to the allow-list without disabling the protection for anything else (arbitrary hosts still get rejected).

2. Run it as a systemd service

sudo mkdir -p /opt/sparxea-mcp-server
sudo cp -r . /opt/sparxea-mcp-server   # or however you deploy code to this box
cd /opt/sparxea-mcp-server
sudo python3 -m venv .venv
sudo .venv/bin/pip install -e .

sudo useradd --system --no-create-home --shell /usr/sbin/nologin sparxea-mcp
sudo chown -R sparxea-mcp:sparxea-mcp /opt/sparxea-mcp-server

sudo cp deploy/sparxea-mcp-server.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable --now sparxea-mcp-server
sudo systemctl status sparxea-mcp-server
sudo journalctl -u sparxea-mcp-server -f   # tail logs

The unit file runs the server under a dedicated unprivileged user with ProtectSystem=strict/ProtectHome=true (it never needs to write anywhere on disk), and restarts it automatically on failure.

3. Put a reverse proxy in front for HTTPS

Pick one - both configs are in deploy/, each with install steps in comments at the top:

  • deploy/Caddyfile - simplest option, automatic Let's Encrypt certificate and renewal with almost no config.

  • deploy/nginx-sparxea-mcp.conf - if you already run nginx.

Either way, edit the domain name in the config first, and make sure DNS for that domain already points at this server's public IP.

4. Open the firewall

sudo ufw allow 80/tcp    # for Let's Encrypt's HTTP challenge
sudo ufw allow 443/tcp   # HTTPS
sudo ufw enable

Notice port 8765 (or whatever MCP_HTTP_PORT is) is not opened - only the reverse proxy is internet-facing; it forwards to the app over localhost.

5. Connect from Claude

In Claude.ai: Customize → Connectors → + → Add custom connector, enter https://mcp.your-domain.example.com/mcp, open Advanced settings, and under the bearer-token field enter Bearer <your MCP_AUTH_TOKEN value> (the literal word Bearer, a space, then the token).

Verifying it works

curl -i -X POST https://mcp.your-domain.example.com/mcp \
  -H "Authorization: Bearer <your token>" \
  -H "Content-Type: application/json" -H "Accept: application/json, text/event-stream" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'

A 200 with a JSON-RPC result listing the 14 tools means everything is wired up correctly. 401 means the token's wrong; 421 means MCP_PUBLIC_HOSTNAME doesn't match the domain in the URL you're hitting.

Calling a specific tool directly

MCP's actual protocol has no per-tool REST URL - it's one JSON-RPC endpoint reached via POST, using the tools/call method with the tool's name and arguments. This server runs the HTTP transport in stateless mode, so - unlike the MCP spec's default session-based flow

  • a single request works standalone, with no initialize handshake or Mcp-Session-Id header required first:

curl -s -X POST https://mcp.your-domain.example.com/mcp \
  -H "Authorization: Bearer <your token>" \
  -H "Content-Type: application/json" -H "Accept: application/json, text/event-stream" \
  -d '{
        "jsonrpc": "2.0",
        "id": 1,
        "method": "tools/call",
        "params": {
          "name": "get_root_packages",
          "arguments": {}
        }
      }'

Swap "name" and "arguments" for any of the 14 tools listed below - e.g. {"name": "find_elements_by_name", "arguments": {"name": "Customer"}}.

For interactively browsing tools/arguments instead of hand-writing JSON, use MCP Inspector (npx @modelcontextprotocol/inspector, transport type "Streamable HTTP", your server URL, and the same bearer token in its auth field).

Plain-REST compatibility routes (for non-MCP tool testers)

Some internal API testers/dashboards assume a classic REST shape - one POST endpoint per tool, a plain JSON object of arguments in, a plain JSON object out - rather than MCP's JSON-RPC envelope above. For those, sparx_ea_mcp/rest.py mounts the exact same 14 tools at POST <base>/<tool_name>, at both the server root and under the MCP path (so it works whichever base URL such a tool was configured with):

curl -s -X POST https://mcp.your-domain.example.com/get_root_packages \
  -H "Authorization: Bearer <your token>" \
  -H "Content-Type: application/json" \
  -d '{}'
# identical result via the other mount point:
curl -s -X POST https://mcp.your-domain.example.com/mcp/get_root_packages \
  -H "Authorization: Bearer <your token>" \
  -H "Content-Type: application/json" \
  -d '{}'

Arguments go directly in the body - no "arguments" wrapper, no jsonrpc/method envelope:

curl -s -X POST https://mcp.your-domain.example.com/find_elements_by_name \
  -H "Authorization: Bearer <your token>" \
  -H "Content-Type: application/json" \
  -d '{"name": "Customer"}'

The response is always {"result": ...}, or {"error": "..."} with a 4xx/5xx status on failure. This is not part of the MCP spec and real MCP clients (Claude, MCP Inspector) never use it - it exists purely for tooling that expects REST. The bearer-token auth covers these routes exactly like the real /mcp endpoint, and adding them changes nothing about /mcp itself - existing Claude Desktop/mcp-remote connections are unaffected by enabling or using this layer.

Tool reference

Tool

Description

get_model_overview

Repo-wide stats (packages/elements/diagrams/connectors counts, top element types) - good starting point

get_root_packages

Top-level packages, as shown at the root of the EA Browser tree

find_packages_by_name

Search packages by name

get_package_contents

A package's metadata plus its direct subpackages, elements, and diagrams (one level deep)

find_elements_by_name

Search elements (classes, interfaces, actors, use cases, ...) by name

get_elements_information

Full detail for one element: attributes, operations, tagged values

find_element_in_diagrams

Every diagram that displays a given element

export_element_linked_documents

Linked/embedded documents attached to an element

get_connectors_information

Every connector (relationship) attached to an element, either end

find_diagrams_by_name

Search diagrams by name

get_diagrams_information

A diagram's elements and the connectors between them

get_diagram_image

Approximate schematic SVG rebuilt from stored coordinates - not EA's own renderer

search_model

One free-text search across packages/elements/diagrams

check_connection

Reports the live DBMS name/version via ODBC, plus table visibility

This is a read-only server: nothing that requires a live, running EA session (opening diagrams, selecting elements in the Browser, etc.) or that writes to the repository is implemented - see "How it connects" above for why.

get_diagram_image caveat

Reconstructs a plain box-and-line SVG from t_diagramobjects/t_connector coordinates - useful for a rough layout sense, but EA's own notation styling (UML/BPMN/ArchiMate shapes, colors, fonts) isn't reproduced. Treat get_diagrams_information's structured data as the source of truth.

Docker

A prebuilt, ODBC-ready image is the fastest path to a working deployment

  • no manual driver installs, no venv management.

docker build -t sparxea-mcp-server .
# Or skip Microsoft's SQL Server driver (its EULA) if you don't need it:
docker build --build-arg INSTALL_MSODBC=false -t sparxea-mcp-server .

One image covers SQL Server (both Microsoft's driver and FreeTDS), MySQL, MariaDB, and PostgreSQL - which one you use is just the EA_DB_* env vars at run time, not a different build. Oracle and Firebird still need their driver added on top (same reasons as bare-metal - see Requirements above); extend this Dockerfile with the same manual steps install.sh prints for those two.

cp .env.example .env   # fill in EA_DB_* and MCP_AUTH_TOKEN - see earlier sections
docker run -d --name sparxea-mcp --env-file .env -p 127.0.0.1:8765:8765 sparxea-mcp-server

Or with Compose:

docker compose up -d --build
docker compose logs -f

Why MCP_HTTP_HOST=0.0.0.0 inside the container (not 127.0.0.1)

Everywhere else in this README, binding 127.0.0.1 and putting a reverse proxy in front is the recommended pattern. Inside a container that still holds - it's just one layer removed: 0.0.0.0 is what Docker's networking needs in order to route traffic into the container at all (a container process bound to 127.0.0.1 is only reachable from inside that same container, invisible even to docker run -p). The safety property is preserved by the host-side half of the port mapping instead: -p 127.0.0.1:8765:8765 means the port is still only reachable from the Docker host itself, not the internet - your reverse proxy on the host (deploy/Caddyfile/deploy/nginx-sparxea-mcp.conf) is still what's actually internet-facing, exactly as in the bare-metal setup. Don't change that first 127.0.0.1: to 0.0.0.0: in the port mapping unless you understand you're removing that boundary.

Dockerfile sets MCP_TRANSPORT=http as its default (containers are inherently about remote hosting - nothing runs a container as a locally- spawned STDIO subprocess), overridable via .env like everything else.

Publishing to a registry from Bitbucket Pipelines

bitbucket-pipelines.yml is included: every push builds the image and runs the exact same smoke test as the manual verification steps earlier (reject unauthenticated, accept a real MCP handshake with the token) - using only the open-source drivers, so CI doesn't depend on reaching Microsoft's package server or accepting its EULA. Pushing a v* tag also builds and pushes to a registry, once you set these as Repository variables (Repository settings → Pipelines → Repository variables in Bitbucket; mark DOCKER_PASSWORD Secured):

Variable

Example

DOCKER_REGISTRY

docker.io, or <account>.dkr.ecr.<region>.amazonaws.com

DOCKER_IMAGE

your-org/sparxea-mcp-server

DOCKER_USERNAME

your registry username

DOCKER_PASSWORD

a registry access token, not your account password

Bitbucket itself doesn't host container images, so you still need a registry somewhere - Docker Hub, ECR, ACR, GHCR, or a self-hosted one all work the same way here.

Getting the code onto Bitbucket

git init
git add .
git commit -m "Initial commit"
git remote add origin https://bitbucket.org/<your-workspace>/<your-repo>.git
git push -u origin main

.env is already excluded via .gitignore - double-check git status shows it untracked before your first push. Nothing in this repo needs secrets baked in; every credential is supplied at deploy time via .env or Bitbucket's repository variables.

Known cross-DBMS quirks (already handled)

Testing against real PostgreSQL and MariaDB servers surfaced genuine differences that are easy to miss if you only test against one backend:

  • Column name casing: PostgreSQL's ODBC driver folds unaliased output column names to lowercase (Object_Typeobject_type); other backends preserve the case as written. Every query explicitly aliases every output column with a quoted alias (AS "Object_Type") to pin the exact casing everywhere, so tool output is identical regardless of backend.

  • LIKE case sensitivity: case-sensitive by default on PostgreSQL, case-insensitive by default on SQL Server/MySQL. All substring search wraps both sides in LOWER() (plain ANSI SQL) for consistent behavior.

  • Reserved words: MySQL/MariaDB treat double-quoted text as a string literal unless ANSI_QUOTES mode is enabled - db.py sets this automatically right after connecting when EA_DB_BACKEND is mysql or mariadb.

  • Identifier case-folding for table/column source names: Oracle and Firebird fold unquoted identifiers to UPPERCASE by default, unlike SQL Server/MySQL. If your Oracle/Firebird schema was created without quoting, and doesn't match the names in repository.py's docstring, scripts/smoke_test.py will surface a clear driver error naming the problem so you can adjust.

Security notes

  • Only ever issues SELECT statements; db.py actively rejects any statement containing INSERT/UPDATE/DELETE/DROP/etc. as defense-in-depth, even though nothing in the codebase constructs such statements.

  • Use a DB login scoped to read-only access on the EA repository database.

  • All queries are parameterized; user-supplied search text is never concatenated into SQL.

  • Every list-returning tool is capped by EA_MCP_ROW_LIMIT regardless of what a client requests.

  • If hosting over HTTP, MCP_AUTH_TOKEN is checked with a constant-time comparison (hmac.compare_digest) to avoid timing side-channels, and should be treated like any other credential (long, random, not committed to version control - .env is already in .gitignore).

Project layout

Dockerfile               Ubuntu 24.04 image w/ every open-source ODBC driver + Microsoft's
docker-compose.yml        Convenience wrapper around docker build/run
.dockerignore             Keeps .env and other secrets out of the image
bitbucket-pipelines.yml   CI: build + smoke-test every push; publish on v* tags
install.sh       Ubuntu setup: unixODBC + your chosen driver + Python venv
deploy/
  sparxea-mcp-server.service   systemd unit for persistent HTTP hosting (bare-metal)
  Caddyfile                    reverse proxy w/ automatic HTTPS (recommended)
  nginx-sparxea-mcp.conf       reverse proxy alternative (+ certbot)
sparx_ea_mcp/
  config.py      ODBC connection-string builder from environment variables
  db.py          pyodbc connection handling, read-only guard, ANSI_QUOTES hook
  repository.py  All SQL (ANSI SQL:2008) against the EA schema
  svg.py         Schematic SVG renderer for get_diagram_image
  auth.py        Bearer-token middleware for the HTTP transport
  rest.py        Optional plain-REST compat routes (POST <base>/<tool_name>)
  server.py      MCP tool definitions; STDIO and HTTP entry points
scripts/
  smoke_test.py  Standalone connectivity check

Extending

To add a new read tool: add a function to sparx_ea_mcp/repository.py that runs a parameterized, ANSI-SQL SELECT via db.run_query/ run_query_one (remember to alias every output column in double quotes), then wrap it with @mcp.tool() in sparx_ea_mcp/server.py with a clear docstring - the docstring is what the LLM client reads to decide when to call it.

A
license - permissive license
Not graded
quality - not tested
C
maintenance

Maintenance

Maintainers
Response time
Release cycle
Releases (12mo)
Commit activity

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Servers

  • A
    license
    Not graded
    quality
    D
    maintenance
    Enables LLM tools like Claude Desktop to query databases via ODBC connections, allowing access, analysis, and insight generation from database data while maintaining security through read-only safeguards.
    4
    MIT
  • A
    license
    Not graded
    quality
    C
    maintenance
    Provides read-only access to Microsoft SQL Server databases using Windows Authentication, enabling AI assistants to safely explore schemas and query data with built-in security controls.
    MIT

View all related MCP servers

Related MCP Connectors

View all MCP Connectors

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/knglumt/sparxea-mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server