accelo-mcp
An unofficial Model Context Protocol server for the Accelo CRM platform. It exposes Accelo data and operations to MCP-compatible AI assistants through the documented Accelo REST API.
This project is not affiliated with or endorsed by Accelo. Users are responsible for their Accelo account, OAuth application, permissions, and data handling.
Installation
With uvx
The recommended installation for MCP hosts is:
{
"mcpServers": {
"accelo": {
"command": "uvx",
"args": ["accelo-mcp"],
"env": {
"ACCELO_DEPLOYMENT": "your-deployment",
"ACCELO_CLIENT_ID": "your-client-id",
"ACCELO_CLIENT_SECRET": "your-client-secret"
}
}
}
}
To pin a release:
"args": ["accelo-mcp==0.1.0"]
uvx installs the package into an isolated environment and runs the accelo-mcp command.
With pip
python3 -m pip install accelo-mcp
accelo-mcp
From source
git clone <repository-url>
cd accelo-mcp
python3 -m venv .venv
source .venv/bin/activate
pip install -e ".[dev]"
Configuration
Copy .env.example to .env, or provide these variables through the MCP host:
| Variable | Required | Default | Description |
|---|---|---|---|
ACCELO_DEPLOYMENT |
Yes | — | Deployment prefix, such as example for example.accelo.com |
ACCELO_CLIENT_ID |
Yes | — | OAuth2 client ID from an Accelo application |
ACCELO_CLIENT_SECRET |
Depends on auth type | — | OAuth2 client secret; not required for public PKCE applications |
ACCELO_AUTH_TYPE |
No | web |
web, service, installed, or public |
ACCELO_SCOPE |
No | read(all) |
OAuth2 scope; defaults to read-only. Add write access explicitly, e.g. read(all),write(all), only if you need the write tools |
ACCELO_REDIRECT_URI |
For web and public |
http://localhost:9876/callback (web only) |
Local OAuth callback URL. Optional for web (defaults as shown); required for public with no default. Must exactly match the redirect URI registered on the Accelo application. |
HTTP transport variables (only used with --http or --sse):
Single-user scope. The HTTP / SSE transports have no inbound authentication and act with one shared Accelo identity for every caller. This server is for a single operator; when running over HTTP it is your responsibility to ensure only the one intended user can reach it. See the fuller note under Usage and
SECURITY.md.
| Variable | Default | Description |
|---|---|---|
ACCELO_MCP_HOST |
127.0.0.1 |
Interface to bind. 127.0.0.1 keeps it local-only (recommended). Only set 0.0.0.0 inside a container, and publish the port to loopback on the host — see the single-user scope note above and SECURITY.md. |
ACCELO_MCP_PORT |
3000 |
TCP port to listen on. |
ACCELO_MCP_PATH |
/mcp |
Streamable HTTP endpoint path. |
ACCELO_MCP_JSON_RESPONSE |
false |
Return a single JSON response instead of an SSE stream. |
ACCELO_MCP_STATELESS |
false |
Run without per-session MCP protocol state. This only affects the transport-level session the MCP library tracks; it does not add per-user credentials — every request still uses the one shared Accelo identity (see the single-user scope note above). |
ACCELO_MCP_ALLOWED_HOSTS |
— | Comma-separated Host header allow-list for DNS-rebinding protection. Matching is exact unless an entry ends in :*, and clients send the bound port, so use the host:* form — e.g. 127.0.0.1:*,localhost:* for a loopback-published container, or your-host:* behind a proxy. Required when the server is reached through any hostname other than localhost. A non-standard loopback bind also needs it: e.g. ACCELO_MCP_HOST=127.0.0.5 fails closed (every request 421) unless you set ACCELO_MCP_ALLOWED_HOSTS=127.0.0.5:*. |
ACCELO_MCP_ALLOWED_ORIGINS |
— | Comma-separated Origin header allow-list. |
ACCELO_MCP_DISABLE_DNS_REBINDING_PROTECTION |
false |
Disable the Host/Origin check entirely. Prefer setting the allow-lists above instead. On a non-loopback bind, this is the explicit override that lets the server start with no allow-list (otherwise it refuses — see below). |
ACCELO_MCP_ALLOW_UNAUTHENTICATED |
false |
Acknowledge that the HTTP transport has no inbound authentication. Required to start on a non-loopback bind (e.g. 0.0.0.0); the server refuses to start on a non-loopback host without it, to prevent accidental exposure. On a non-loopback bind the server also requires ACCELO_MCP_ALLOWED_HOSTS (an Origin allow-list alone does not close the DNS-rebinding gap) — or an explicit ACCELO_MCP_DISABLE_DNS_REBINDING_PROTECTION=true — otherwise it refuses. See SECURITY.md. |
Create or obtain an OAuth application for the Accelo deployment you intend to access. Never commit client secrets, refresh tokens, access tokens, cookies, or webhook secrets.
Authentication types
All four authentication types use OAuth 2.0. They differ in whether a person authorises interactively in a browser and whether a client secret is required.
| Auth type | OAuth 2.0 grant | Interactive browser login | Client secret | Typical use |
|---|---|---|---|---|
web |
Authorisation Code + refresh token | Yes, once | Required | Individual users; the recommended default |
public |
Authorisation Code with PKCE + refresh token | Yes, once | Not required | Publicly distributed clients that cannot keep a secret |
installed |
Authorisation Code via PIN + refresh token | Yes (approve, then enter a PIN) | Required | Installed clients using Accelo's PIN approval |
service |
Client Credentials | No | Required | Server-to-server automation under a shared service identity |
Key points:
publicstill requires OAuth. "Public" means no client secret is distributed, not that authentication is skipped. On first run the server opens your browser, you log in to Accelo and authorise, Accelo redirects back to a local127.0.0.1callback listener, and the server exchanges the authorisation code (using a PKCEcode_verifier) for tokens. No client secret is ever sent.serviceis the only non-interactive flow. It authenticates as a service identity via the Client Credentials grant and never opens a browser. Every other type requires a one-time interactive authorisation.- After the first authorisation,
web,public, andinstalledcache a refresh token and reauthorise silently, so the browser step is not repeated on later runs. - The redirect URI must line up in three places for
webandpublic: theACCELO_REDIRECT_URIyou set, the port the local callback listener binds (derived from that URI, or8080if the URI has no explicit port), and the redirect URI registered on the Accelo application. Accelo rejects the authorisation request if its registered value does not match. Forpublic,ACCELO_REDIRECT_URIis mandatory and has no default.
Tokens are cached locally at ~/.accelo-mcp/tokens.json with restrictive file permissions. The local callback listener binds to 127.0.0.1 only.
Usage
The default transport is stdio, which is suitable for Claude Desktop, Kiro, and other local MCP hosts:
accelo-mcp
For remote or networked hosts, run the server over Streamable HTTP (the current MCP HTTP transport):
accelo-mcp --http
This listens on http://127.0.0.1:3000/mcp by default.
Single-user scope. This server has no inbound authentication and acts with one shared Accelo identity for every caller (see
SECURITY.md). It is intended for a single operator. When running over HTTP, it is your responsibility to ensure only the one intended user can reach it — keep the bind on loopback, or restrict network access to that single client and set the Host/Origin allow-lists below. Do not expose the HTTP transport to an untrusted network.
If you must serve from inside a container or behind a reverse proxy, bind beyond loopback only behind a network restriction that admits the single permitted client, and allow-list the expected hostname so DNS-rebinding protection stays active and does not reject proxied requests:
# Publish the container port to loopback on the host, not to all interfaces:
# docker run -p 127.0.0.1:8080:8080 ...
# The allow-list below assumes a host-side reverse proxy that forwards with
# Host: mcp.internal.example; the 127.0.0.1:* / localhost:* entries keep a
# direct loopback request working (a custom allow-list REPLACES the library's
# loopback defaults, and matching is exact unless an entry ends in ":*").
ACCELO_MCP_HOST=0.0.0.0 \
ACCELO_MCP_PORT=8080 \
ACCELO_MCP_ALLOWED_HOSTS=mcp.internal.example,127.0.0.1:*,localhost:* \
ACCELO_MCP_ALLOWED_ORIGINS=https://mcp.internal.example \
ACCELO_MCP_ALLOW_UNAUTHENTICATED=true \
accelo-mcp --http
The server refuses to start on a non-loopback host unless you set
ACCELO_MCP_ALLOW_UNAUTHENTICATED=true, acknowledging that it has no inbound
authentication. This stops accidental exposure (an empty ACCELO_MCP_HOST=
value or a copied example); it does not block an intended exposed bind — you opt
in explicitly. On a non-loopback bind the server also requires a Host/Origin
allow-list (ACCELO_MCP_ALLOWED_HOSTS/ACCELO_MCP_ALLOWED_ORIGINS), as in the
example above — without one it refuses, because DNS-rebinding protection would
otherwise be off and the permitted user's browser becomes an attack vector. If
you truly want no Host/Origin check, set
ACCELO_MCP_DISABLE_DNS_REBINDING_PROTECTION=true explicitly. Here
ACCELO_MCP_HOST=0.0.0.0 only binds all interfaces inside the container; the
-p 127.0.0.1:8080:8080 publish is what keeps it reachable only from the host.
An IP restriction alone does not stop DNS-rebinding, so keep the Host/Origin
allow-list set regardless.
ACCELO_MCP_STATELESS=true runs the transport without per-session MCP protocol
state. This only affects the protocol-level session the MCP library tracks for
stream resumption; it does not add per-user credentials or make the server
multi-user — every request still uses the same single Accelo identity, so the
single-user scope in SECURITY.md still applies.
The older SSE transport is still available but deprecated; prefer --http
for new deployments:
accelo-mcp --sse
The server provides 219 tools covering companies, contacts, affiliations, activities, projects, tickets, tasks, retainers, sales, billing, time tracking, custom fields, resources, workflow progressions, and related Accelo objects.
For complex queries, call accelo_get_context first. It describes the Accelo object model, relationships, filtering conventions, pagination, and field selection syntax.
Filtering and field selection
All list tools accept structured filters:
{"standing": "active"}
{"date_created_after": 1690000000}
{"status": [1, 2, 3]}
{"order_by_desc": "date_modified"}
Request linked or optional fields with fields:
website,phone
postal_address(city,state)
_ALL
Development
make install # install the package and development dependencies
make test # run tests
make lint # run Ruff checks
make audit # audit Python dependencies for known vulnerabilities
make check # run lint, tests, and dependency audit
make build # build a wheel and source distribution
Releasing
Releases are driven by the version in pyproject.toml. Bumping that version on
main is the only manual step; validation, tagging, the GitHub Release, and the
PyPI publish are automated by a single workflow.
-
Bump
versioninpyproject.toml(for example0.1.0to0.2.0) on a branch and merge it tomainthrough a pull request. Follow semantic versioning. -
On merge, the Release workflow (
.github/workflows/publish.yml) detects the changed version and, in one run:- runs
make check(lint, tests, and dependency audit), - creates the matching
v<version>tag and a GitHub Release with generated notes, and - builds the wheel and source distribution and publishes them to PyPI via OIDC Trusted Publishing (no stored API token).
The workflow is idempotent: if the version is unchanged since the previous commit, or the tag already exists on the remote, it does nothing.
- runs
Tagging and publishing live in the same workflow on purpose. A tag pushed by
the built-in GITHUB_TOKEN does not trigger a second workflow, so a separate
tag-triggered publish job would never run. Folding both into one push-triggered
workflow avoids that loop-guard.
To cut a release by hand, bump the version and push the tag yourself:
git tag v0.2.0 # must equal "v" + the pyproject.toml version
git push origin v0.2.0
Note that a hand-pushed tag alone does not publish (nothing is triggered by a tag
push); publishing happens through the version bump on main. The Release workflow
still verifies the tag equals v + the pyproject.toml version and fails if they
differ, so the tag and the package version cannot drift.
Publishing to PyPI requires a configured pypi GitHub environment and a PyPI
Trusted Publisher registered for this repository's publish.yml workflow. The
workflow file must stay named publish.yml: PyPI matches the trusted publisher on
the exact workflow filename, so renaming it breaks publishing even though the
workflow's display name is "Release".
Security
Please report suspected vulnerabilities privately. See SECURITY.md for the reporting process. Do not include credentials, customer data, or access tokens in an issue.
License
MIT. See LICENSE.
Metadata
Release files for accelo-mcp 0.4.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| accelo_mcp-0.4.0.tar.gz | 83.7 kB | With provenance | Details for accelo_mcp-0.4.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| accelo_mcp-0.4.0-py3-none-any.whl | Python 3 | none | any | Details for accelo_mcp-0.4.0-py3-none-any.whl |
Total release size: 174.8 kB
Release files / accelo_mcp-0.4.0.tar.gz
| Download URL | accelo_mcp-0.4.0.tar.gz |
|---|---|
| Size | 83.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
bebb1890a9dce22d72347b0ad662a9f1a5ba365579468c47dcd25d15d7982364
|
|
BLAKE2b-256 checksum How to use checksums |
551b9b593f0d1111d48ac820b3f72295009f2fd6fc76aad46d8e58c2ca5f5931
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 12, 2026.
Transparency logRelease files / accelo_mcp-0.4.0-py3-none-any.whl
| Download URL | accelo_mcp-0.4.0-py3-none-any.whl |
|---|---|
| Size | 91.1 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
036715012f7f3552ff3e210802615a3c8e7a5f581e8d1f0c002c2cb30e8647bd
|
|
BLAKE2b-256 checksum How to use checksums |
d9e5dd23bb8ae54a1e367b62de8bc35302dfc31821aa9e8c9c6a9b5dd14f291e
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 12, 2026.
Transparency logSecurity for accelo-mcp 0.4.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for accelo_mcp-0.4.0.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 12, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.