SparxEA-MCP-Server
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@SparxEA-MCP-Serverwhat are the top-level packages in the Enterprise Architect model?"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
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 noFETCH FIRSTclause, 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 ofrepository.py(it's a small, mechanical change - swapFETCH FIRST n ROWS ONLYforLIMIT 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 |
SQL Server (Microsoft's driver) |
|
|
SQL Server (open-source alternative) |
|
|
MySQL or MariaDB |
|
|
PostgreSQL |
|
|
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 -dThe 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 postgresqlThis 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_PASSWORDVerify
.venv/bin/python3 scripts/smoke_test.pyPrints 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.serverOr, after pip install -e .:
sparxea-mcp-serverEither 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):
-yis 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-httpis required for anything other thanlocalhost-mcp-remoterefuses 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}inargsonly resolves ifenvdefinesAUTH_HEADER, not some other name.No space around the
:inAuthorization:${AUTH_HEADER}- Claude Desktop on Windows has a known bug where spaces insideargsvalues get mangled when it invokesnpx. 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:
Transport: set
MCP_TRANSPORT=http(env var already in.env.example).Process supervision: run it under systemd so it survives reboots and restarts on crash (
deploy/sparxea-mcp-server.service).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 useMCP_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 logsThe 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 enableNotice 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
initializehandshake orMcp-Session-Idheader 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 |
| Repo-wide stats (packages/elements/diagrams/connectors counts, top element types) - good starting point |
| Top-level packages, as shown at the root of the EA Browser tree |
| Search packages by name |
| A package's metadata plus its direct subpackages, elements, and diagrams (one level deep) |
| Search elements (classes, interfaces, actors, use cases, ...) by name |
| Full detail for one element: attributes, operations, tagged values |
| Every diagram that displays a given element |
| Linked/embedded documents attached to an element |
| Every connector (relationship) attached to an element, either end |
| Search diagrams by name |
| A diagram's elements and the connectors between them |
| Approximate schematic SVG rebuilt from stored coordinates - not EA's own renderer |
| One free-text search across packages/elements/diagrams |
| 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-serverOr with Compose:
docker compose up -d --build
docker compose logs -fWhy 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 |
|
|
|
|
| your registry username |
| 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_Type→object_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.LIKEcase sensitivity: case-sensitive by default on PostgreSQL, case-insensitive by default on SQL Server/MySQL. All substring search wraps both sides inLOWER()(plain ANSI SQL) for consistent behavior.Reserved words: MySQL/MariaDB treat double-quoted text as a string literal unless
ANSI_QUOTESmode is enabled -db.pysets this automatically right after connecting whenEA_DB_BACKENDismysqlormariadb.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.pywill surface a clear driver error naming the problem so you can adjust.
Security notes
Only ever issues
SELECTstatements;db.pyactively rejects any statement containingINSERT/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_LIMITregardless of what a client requests.If hosting over HTTP,
MCP_AUTH_TOKENis 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 -.envis 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 checkExtending
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.
This server cannot be installed
Maintenance
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
- AlicenseNot gradedqualityDmaintenanceEnables 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.4MIT
- AlicenseNot gradedqualityCmaintenanceProvides 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
- AlicenseNot gradedqualityCmaintenanceEnables LLMs to query ODBC-accessible databases via DSN, supporting SQL, SPARQL, and schema exploration.MIT
- AlicenseNot gradedqualityDmaintenanceEnables AI assistants to securely interact with IBM i (AS/400) systems via ODBC, supporting SQL queries, CL commands, source code reading, compilation, and spoolfile analysis.2MIT
Related MCP Connectors
Read-only AI project discovery, verification, comparison, shortlisting, and stack planning.
Read-only bank access for your AI agent. Connects Claude, ChatGPT, Cursor, Gemini, Codex.
Connect AI assistants to your GitHub-hosted Obsidian vault to seamlessly access, search, and analy…
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
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