Genesis-World-MCP
by Gnidreve
README.md
<p align="center">
<img src="https://www.acp-gruppe.com/hs-fs/hubfs/cas_genesis_world-1.png?width=1600&height=600&name=cas_genesis_world-1.png" alt="CAS genesisWorld" width="100%" />
</p>
# cas-genesisworld-mcp
[](https://www.npmjs.com/package/cas-genesis-world-mcp)
[](https://hub.docker.com/r/vaatu/cas-genesis-world-mcp)
[](https://github.com/Gnidreve/Genesis-World-MCP/actions/workflows/ci.yml)
[](./LICENSE)
Connect Claude, Cursor, or any [MCP](https://modelcontextprotocol.io)-compatible
AI agent to your **CAS genesisWorld** CRM. Search contacts, manage tasks and
appointments, and read or write records — through natural language, without
writing a single API call.
- **69 tools**, including **7 native flows** that bundle multi-step CRM
operations (like checking for scheduling conflicts before booking a
meeting, or checking for duplicates before creating a contact) into a
single call.
- **Full read/write access** to tasks, contacts, appointments, documents,
distribution lists, and more — plus generic access to any custom object
type your installation defines.
- **Read-only mode** available for safe, exploratory use.
- Ships as a ready-made **Docker image** or **npm package**.
## Quick start
Add this to your MCP client's config (e.g. Claude Desktop's
`claude_desktop_config.json`):
```json
{
"mcpServers": {
"cas-genesisworld": {
"command": "npx",
"args": ["-y", "cas-genesis-world-mcp"],
"env": {
"GENESISWORLD_BASE_URL": "http://your-genesisworld-server/genesisrest.svc",
"GENESISWORLD_PRODUCT_KEY": "your-product-key",
"GENESISWORLD_USERNAME": "your-username",
"GENESISWORLD_PASSWORD": "your-password"
}
}
}
}
```
Restart your client — the tools show up automatically. Ask your agent to
find a contact, list your open tasks, or book a meeting, and it takes it
from there.
Prefer a persistent, self-hosted server instead of a per-client process?
See [Self-hosting with Docker](#self-hosting-with-docker) below.
## What you can ask it
Once connected, your agent can handle requests like:
- *"Find the contact info for Jane Doe and show me her open tasks."*
- *"Create a follow-up task for this lead and link it to their contact record."*
- *"Book a meeting with the sales team next Tuesday at 10am — check everyone's
calendar for conflicts first."*
- *"Check for possible duplicates before creating a new contact for Acme Corp."*
- *"Generate a report for this opportunity."*
Requests like these are answered by **flows** — single tool calls that
bundle the multi-step sequence of API requests a human would otherwise have
to script by hand.
## Tools & flows
### Flows — compound actions, one call each
| Flow | Mode | What it does |
|-----------------|-------|--------------|
| `my_open_tasks` | read | Current user + their task list (due window, saved view, or full-text filter) in one call |
| `task_overview` | read | Task record + links + tags, fetched in parallel |
| `create_task` | write | Create a task and optionally link it to another object (e.g. a contact) |
| `find_contact` | read | Contact search by name and/or phone number, in parallel |
| `contact_360` | read | Contact + collection dossier + tags + links, fetched in parallel |
| `create_address_safe` | write | Duplicate check first — creates only when no candidates are found |
| `create_appointment_safe` | write | Optional conflict check → create → add participants, in one call |
<details>
<summary><strong>Full tool reference (62 atomic tools)</strong></summary>
#### Read (39)
| Tool | Endpoint |
|-------------------------------------|-------------------------------------------------------------------|
| `smart_search` | `GET /v7.0/smartsearch` |
| `get_data_object` | `GET /v7.0/type/{dataObjectType}/{dataObjectGGUID}` |
| `get_dossier` | `GET /v7.0/type/{dataObjectType}/{dataObjectGGUID}/dossier/full` |
| `list_data_objects` | `GET /v7.0/type/{dataObjectType}/list` |
| `list_views` | `GET /v7.0/type/{dataObjectType}/view/list` |
| `list_data_objects_by_view` | `GET /v7.0/type/{dataObjectType}/view/{viewID}/list` |
| `list_available_data_object_types` | `GET /v7.0/user/self/dataobjecttypepermission/list` |
| `get_data_object_types_metadata` | `GET /v7.0/metadata` |
| `list_links` | `GET /v7.0/type/{dataObjectType}/{dataObjectGGUID}/link/list` |
| `list_recent_data_objects` | `GET /v7.0/type/{dataObjectType}/recent/list` |
| `get_available_products` | `GET /v7.0/type/gwopportunity/availableproducts` |
| `get_data_object_count` | `GET /v7.0/type/{dataObjectType}/count` |
| `get_primary_link_parents` | `GET /v7.0/type/{dataObjectType}/{dataObjectGGUID}/primarylinkparents` |
| `list_users` | `GET /v7.0/user/list` |
| `get_user_self` | `GET /v7.0/user/self` |
| `get_view` | `GET /v7.0/type/{dataObjectType}/view/{viewID}` |
| `list_tags` | `GET /v7.0/tags` |
| `get_object_tags` | `GET /v7.0/type/{dataObjectType}/{dataObjectGGUID}/tags` |
| `get_full_data_objects` | `GET /v7.0/type/{dataObjectType}/full` |
| `list_data_objects_by_view_full` | `GET /v7.0/type/{dataObjectType}/view/{viewID}/full` |
| `get_data_objects_bulk` | `POST /v7.0/type/{dataObjectType}/records` (read despite POST) |
| `get_ticket_service_agreements` | `GET /v7.0/type/task/ticket/serviceagreements` |
| `get_vcard` | `GET /v7.0/type/address/{dataObjectGGUID}/vcard` |
| `get_salutation` | `POST /v7.0/type/address/salutation` (read despite POST) |
| `format_phone_number` | `POST /v7.0/type/address/formatphonenumber` (read despite POST) |
| `check_appointment_conflicts` | `GET /v7.0/type/appointment/conflicts` |
| `get_participant_summary` | `GET /v7.0/type/appointment/{gguid}/participant/summary` |
| `list_appointment_participants` | `GET /v7.0/type/appointment/{gguid}/participant/full` |
| `get_document_file` | `GET /v7.0/type/document/{gguid}/file` (never locks) |
| `list_document_versions` | `GET /v7.0/type/document/{gguid}/file/version/list` |
| `list_email_attachments` | `GET /v7.0/type/emailstore/{gguid}/attachment/list` |
| `get_email_attachment` | `GET /v7.0/type/emailstore/{gguid}/attachment/{attachmentId}` |
| `get_email_file` | `GET /v7.0/type/emailstore/{gguid}/file` |
| `list_object_permissions` | `GET /v7.0/type/{t}/{gguid}/permission/full` |
| `list_distributions` | `GET /v7.0/type/gwdistribution/list` |
| `list_distribution_addresses` | `GET /v7.0/type/gwdistribution/{distributionGuid}/address/list` |
| `list_report_templates` | `GET /v7.0/type/report/template/{templateType}` |
| `generate_report` | `POST /v7.0/type/report/template/{templateGGUID}` (read despite POST — renders, doesn't mutate) |
| `readme` (tool form) | server-local static orientation document |
#### Write (23 — hidden in read-only mode, along with the write flows above)
| Tool | Endpoint |
|-------------------------|------------------------------------------------------------------------|
| `create_data_object` | `POST /v7.0/type/{dataObjectType}` |
| `update_data_object` | `PUT /v7.0/type/{dataObjectType}/{dataObjectGGUID}` |
| `delete_data_object` | `DELETE /v7.0/type/{dataObjectType}/{dataObjectGGUID}` |
| `restore_data_object` | `POST /v7.0/type/{dataObjectType}/rbin/undelete` |
| `create_link` | `POST /v7.0/type/{dataObjectType}/{dataObjectGGUID}/link` |
| `delete_link` | `DELETE /v7.0/type/{t}/{gguid}/link/{objecttype2}/{guid2}/{attribute}` |
| `set_object_tags` | `POST /v7.0/type/{dataObjectType}/{dataObjectGGUID}/tags/user` |
| `append_notes` | `POST /v7.0/type/{t}/{gguid}/notes/{fieldName}` |
| `create_dossier_entry` | `POST /v7.0/type/{dataObjectType}/{dataObjectGGUID}/dossier` |
| `delete_dossier_entry` | `DELETE /v7.0/type/{t}/{gguid}/dossier/{dossierEntryGGUID}` |
| `set_contact_persons_active` | `POST /v7.0/type/address/{gguid}/contactperson/activate\|deactivate` |
| `add_appointment_participant` | `POST /v7.0/type/appointment/{gguid}/participant` |
| `remove_appointment_participant` | `DELETE /v7.0/type/appointment/{gguid}/participant/{participantGGUID}` |
| `set_recurrence` | `POST /v7.0/type/{t}/recurrence` / `PUT …/recurrence/{periodGuid}` |
| `delete_recurrence` | `DELETE /v7.0/type/{t}/recurrence/{periodGuid}` |
| `set_alarm` | `PUT /v7.0/type/{t}/{gguid}/alarm/self` |
| `delete_alarm` | `DELETE /v7.0/type/{t}/{gguid}/alarm/self` |
| `set_object_permission` | `POST /v7.0/type/{t}/{gguid}/permission` |
| `delete_object_permission` | `DELETE /v7.0/type/{t}/{gguid}/permission/{permissionGGUID}` |
| `add_distribution_addresses` | `POST /v7.0/type/gwdistribution/{distributionGuid}/address` |
| `remove_distribution_address` | `DELETE /v7.0/type/gwdistribution/{distributionGuid}/address/{addressGGUID}` |
| `convert_lead` | `POST /v7.0/type/gwsllead/{dataObjectGGUID}/convert` |
| `recalculate_opportunity_positions` | `PUT /v7.0/type/gwopportunitypos/recalculatevalues` |
The full upstream API this is built on is committed as
[`swagger.json`](./swagger.json).
</details>
## Resources
Beyond tools, the server exposes MCP resources for data that rarely
changes, so your agent doesn't burn tool calls rediscovering it every
session:
- `genesisworld://readme` — static orientation document for the agent
itself (domain model, navigation patterns, efficiency rules).
- `genesisworld://types` — data-object types accessible to the user, with
permissions. Cached 15 min.
- `genesisworld://metadata/{objectType}` — field/relationship schema of one
type, e.g. `genesisworld://metadata/ADDRESS`. Cached 15 min.
- `genesisworld://views/{objectType}` — saved views of one type, e.g.
`genesisworld://views/TASK`. Cached 15 min.
## Self-hosting with Docker
For a persistent server multiple clients can point at (instead of one
`npx` process per client):
```bash
docker run -d --name cas-genesisworld-mcp -p 8084:3000 \
-e GENESISWORLD_BASE_URL="http://your-genesisworld-server/genesisrest.svc" \
-e GENESISWORLD_PRODUCT_KEY="your-product-key" \
-e GENESISWORLD_USERNAME="your-username" \
-e GENESISWORLD_PASSWORD="your-password" \
vaatu/cas-genesis-world-mcp
# Read-only mode: append --read-only after the image name
docker run -d --name cas-genesisworld-mcp -p 8084:3000 \
-e GENESISWORLD_BASE_URL="http://your-genesisworld-server/genesisrest.svc" \
-e GENESISWORLD_PRODUCT_KEY="your-product-key" \
-e GENESISWORLD_USERNAME="your-username" \
-e GENESISWORLD_PASSWORD="your-password" \
vaatu/cas-genesis-world-mcp --read-only
```
Or with `docker compose`, using [`docker-compose.yml`](./docker-compose.yml)
(repo root) and [`.env.example`](./.env.example):
```bash
cp .env.example .env # fill in your values — .env is gitignored
docker compose up -d
```
For `--read-only`, uncomment the matching `command:` line in
`docker-compose.yml` — launch options are CLI flags only, never `.env`
entries, so mode selection stays in the compose file rather than `.env`.
For `--client-credentials`, use
[`docker-compose.client-credentials.yml`](./docker-compose.client-credentials.yml)
instead (see "Multi-tenant" below) — it has that flag active by default,
rather than making you uncomment it in the primary file.
### Multi-tenant: one server, many genesisWorld identities
By default the container has one fixed genesisWorld *user* for every
client that connects. With `--client-credentials`, it has none — each
client authenticates as itself, so one server can safely serve several
people/teams with different genesisWorld logins. The product key stays
server-side either way — it identifies your product license, not a user,
so it's not something a client ever supplies:
```bash
docker run -d --name cas-genesisworld-mcp -p 8084:3000 \
-e GENESISWORLD_BASE_URL="http://your-genesisworld-server/genesisrest.svc" \
-e GENESISWORLD_PRODUCT_KEY="your-product-key" \
vaatu/cas-genesis-world-mcp --client-credentials
```
Or with `docker compose`, using
[`docker-compose.client-credentials.yml`](./docker-compose.client-credentials.yml)
(only `GENESISWORLD_BASE_URL`/`GENESISWORLD_PRODUCT_KEY` need values in
`.env` here — leave `GENESISWORLD_USERNAME`/`PASSWORD` unset, each client
brings its own instead):
```bash
cp .env.example .env # fill in your values — .env is gitignored
docker compose -f docker-compose.client-credentials.yml up -d
```
Each client then sends its own credentials when connecting — two ways,
whichever your MCP client supports:
**Standard `Authorization: Basic` header** (preferred — works with
Claude's built-in "Add custom connector" flow, which rejects arbitrary
custom header names unless Anthropic pre-approves them):
```json
{
"mcpServers": {
"cas-genesisworld": {
"url": "http://localhost:8084/mcp",
"headers": {
"Authorization": "Basic base64(your-username:your-password)"
}
}
}
}
```
**Or custom headers**, for clients that support arbitrary headers but have
no first-class "Basic Auth" option (if `Authorization: Basic` is present,
it wins — these are only used as a fallback):
```json
{
"mcpServers": {
"cas-genesisworld": {
"url": "http://localhost:8084/mcp",
"headers": {
"X-GenesisWorld-Username": "your-username",
"X-GenesisWorld-Password": "your-password"
}
}
}
}
```
There is no product-key header (and no product-key slot in the
`Authorization` value either) — `GENESISWORLD_PRODUCT_KEY` on the server
is used for every client, always; clients can't set or override it. HTTP
transport only (there's no per-request header channel on stdio); a
request missing valid credentials via either path is rejected outright
(HTTP 401), it never falls back to a shared identity.
The MCP endpoint is now at `http://localhost:8084/mcp`. Point your client
at it:
```json
{
"mcpServers": {
"cas-genesisworld": {
"url": "http://localhost:8084/mcp"
}
}
}
```
## Configuration
Two kinds of settings, kept strictly separate — no setting is both:
**Environments** configure the deployment (where the API lives, who
connects); **launch options** toggle behavior at startup.
### Environments
| Variable | Required | Purpose |
| --------------------------- | -------- | ------------------------------------------------ |
| `GENESISWORLD_BASE_URL` | **yes** | Base URL of the REST service, e.g. `http://demo.cas.de/genesisrest.svc` |
| `GENESISWORLD_PRODUCT_KEY` | **yes** | Sent as `X-CAS-PRODUCT-KEY` on every request. Always required — including in `--client-credentials` mode, where it identifies your product license and stays server-side, never client-suppliable |
| `GENESISWORLD_USERNAME` | yes\* | Basic Auth user |
| `GENESISWORLD_PASSWORD` | yes\* | Basic Auth password |
| `MCP_TRANSPORT` | no | `http` (default in Docker) or `stdio` |
| `MCP_HOST` / `MCP_PORT` | no | Bind address for HTTP mode (default `0.0.0.0:3000`) |
| `GENESISWORLD_MAX_RESULT_CHARS` | no | Truncate oversized responses (default 60000 chars; `0` disables) |
| `GENESISWORLD_QUIET` | no | `true` disables per-request stderr logging |
\* Required in practice for any real request to succeed — except in
`--client-credentials` mode (see below), where the server has no fixed
*user* identity and these two are not required (each client brings its
own instead).
### Launch options
| Flag | Required | Purpose |
| ------------------------ | -------- | ------------------------------------------------ |
| `--read-only` | no | Registers only read tools for that session — mutating tools are not merely blocked, they don't exist |
| `--client-credentials` | no | Server holds no fixed genesisWorld *user* identity; each HTTP client authenticates itself via request headers (see "Multi-tenant" above). Product key stays server-side. HTTP transport only |
Pass launch options on the command line, or after the image name in
`docker run` (as shown above). None of them have an environment-variable
equivalent, by design.
## License
[MIT](./LICENSE)
---
Want to add a tool or understand how this is built? See
[AGENTS.md](./AGENTS.md) for architecture and contributor docs, and
[ROADMAP.md](./ROADMAP.md) for the project plan.
This server cannot be deployed
Maintenance
ActivityActive
ResponsivenessNo issues