npm package: @nicklouwers/gaply-mcp-server
Description: MCP server for Gaply Cockpit — enables AI agents to manage CRM data and the Brain knowledge base.
Author: Nick Louwers
Latest version: 0.2.8

# @nicklouwers/gaply-mcp-server

MCP (Model Context Protocol) server for Gaply Cockpit. Enables AI agents and tools to interact with the CRM API.

## Quick start

1. Create an API token in the Cockpit UI at **Admin > API Tokens**
2. Add the config below to your MCP client
3. Replace the token value with your own

No installation required — `npx` downloads the package automatically.

## Usage with Claude Desktop

Add to `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) or `%APPDATA%\Claude\claude_desktop_config.json` (Windows):

```json
{
  "mcpServers": {
    "gaply": {
      "command": "npx",
      "args": ["-y", "@nicklouwers/gaply-mcp-server"],
      "env": {
        "GAPLY_API_URL": "https://cockpit.gaply.nl",
        "GAPLY_API_TOKEN": "gply_your_token_here"
      }
    }
  }
}
```

Restart Claude Desktop (Cmd+Q / Ctrl+Q, then reopen) after saving.

## Usage with Cursor

Add to `.cursor/mcp.json` in your project root (or global Cursor settings):

```json
{
  "mcpServers": {
    "gaply": {
      "command": "npx",
      "args": ["-y", "@nicklouwers/gaply-mcp-server"],
      "env": {
        "GAPLY_API_URL": "https://cockpit.gaply.nl",
        "GAPLY_API_TOKEN": "gply_your_token_here"
      }
    }
  }
}
```

## Configuration

| Variable | Required | Description |
|----------|----------|-------------|
| `GAPLY_API_URL` | Yes | Base URL of the Cockpit (e.g. `https://cockpit.gaply.nl`) |
| `GAPLY_API_TOKEN` | Yes | API token created via Admin > API Tokens (starts with `gply_`) |

## Available Tools

### Organizations
- `list_organizations` — Search and paginate organizations
- `get_organization` — Get a single organization by ID
- `create_organization` — Create a new organization
- `update_organization` — Update an existing organization (supports `labelIds` to assign labels)

### Contacts
- `list_contacts` — Search and paginate contacts
- `get_contact` — Get a single contact by ID (includes emails, phones, LinkedIn URL, labels)
- `create_contact` — Create a new contact with optional `emails`, `phones`, `linkedinUrl`, and `organizationId`
- `update_contact` — Update an existing contact (supports `emails`, `phones`, `linkedinUrl`, `organizationId`, `labelIds`; sending emails/phones replaces the full set)

### Deals
- `list_deals` — Search and paginate deals
- `get_deal` — Get a single deal by ID
- `create_deal` — Create a new deal
- `update_deal` — Update an existing deal (supports `labelIds` to assign labels)
- `get_deal_phases` — Get the deal pipeline phases

### Projects
- `list_projects` — Search and paginate projects
- `get_project` — Get a single project by ID
- `create_project` — Create a new project
- `update_project` — Update an existing project (supports `labelIds` to assign labels)

### Agents (requires `agents:read` / `agents:write` scope + `AGENTS_ENABLED=true`)
Agents are keyed by **slug** (a kebab-case string), not by UUID. All tools require the `AGENTS_ENABLED` feature flag to be enabled on the server; calls will return `503` when the flag is off.

- `list_agents` — List all agent profiles
- `get_agent` — Get a single agent profile by `slug`
- `create_agent` — Create a new agent profile (requires `slug`, `name`, `role`, `defaultModel`; optional: `description`, `systemPrompt`, `skills`, `allowedTools`, `canWrite`, `canDelegateTo`, `maxSteps`, `maxToolCalls`, `maxCostUsd`, `maxWallclockS`, `maxParallel`, `isActive`)
- `update_agent` — Update an existing agent profile (partial update; only `slug` required, all other fields optional)
- `delete_agent` — Delete an agent profile by `slug`

> **Note:** Agent dispatch (running an agent) is not yet available via MCP.

### Documents (Brain knowledge base)
- `list_documents` — Search and filter documents (supports `search`, `type`, `tags`, `tagsMatch`, `status`, pagination). Only returns documents the token user has access to.
- `get_document` — Get a single document by ID, including full markdown content, related documents, and linked CRM entities. Set `include_drafts=true` to also return draft/pending_review versions (requires `view_drafts` permission).
- `create_document` — Create a new document (requires `title`, `type`, `tags`, `content`, `source`, `author`; optional `status`, `visibility`, `allowedRoles`, `relatedDocuments`, `linkedEntities`). For types with `review_required` policy, may save as draft if you lack publish permission — check `publicationStatus` in the response.
- `update_document` — Update an existing document (partial update; `type` cannot be changed after creation). For types with `review_required` policy, the response includes `publicationStatus` which is `draft` if you lack publish permission, or `published` if applied directly.
- `delete_document` — Soft-delete a document
- `list_document_tags` — List all tags used in the knowledge base with document counts

#### Document types
`kennisartikel`, `memo`, `meeting`, `template`, `proces`, `skill`, `notitie`

#### Publication policy
Each document type has a configurable publication policy:
- `direct` (default) — changes are published immediately
- `review_required` — changes by users without publish permission are saved as a draft; drafts must be submitted for review and approved before publishing
- `locked` — only users with manage permission can make changes

Configure via Admin > Documenttypes or the `set_document_type_policy` tool.

#### Visibility
Documents support three visibility levels via the `visibility` field:
- `everyone` (default) — visible to all authenticated users
- `roles` — only visible to users with one of the specified `allowedRoles`
- `private` — only visible to the document author

### Document Review Flow
Tools for the draft/review workflow on document types with `review_required` policy:

- `submit_document_for_review` — Submit a draft version for review (`draft` → `pending_review`). Only the draft author may submit.
- `list_pending_reviews` — List all versions with `pending_review` status, optionally filtered by type
- `get_pending_review` — Get details of a review version including the current published document for comparison
- `approve_pending_review` — Approve a pending review and publish it (requires `publish` permission on the document type)
- `reject_pending_review` — Reject a pending review with a mandatory reason
- `request_changes_on_review` — Return a pending review to draft with feedback

### Document Type Policy Management
- `get_document_type_policy` — List publication policy for all document types (requires `admin:roles`)
- `set_document_type_policy` — Set the publication policy for a type (requires `admin:roles`)

#### Example: review workflow
```
1. Agent updates a skill document → response: publicationStatus = "draft"
2. Agent calls submit_document_for_review with the versionId
3. A reviewer calls list_pending_reviews, sees the submission
4. Reviewer calls approve_pending_review → document is now published
```

### Document-Entity Links
Documents can be linked to CRM entities (deals, contacts, organizations, projects). Notes on entity detail pages are stored as documents of type `notitie` with an entity link.

- `link_document_to_entity` — Link a document to a CRM entity (requires `id`, `entityType`, `entityId`)
- `unlink_document_from_entity` — Remove a link (requires `id`, `linkId`)
- `list_document_links` — List all CRM entities linked to a document

### Labels
- `list_labels` — List all labels for a given scope (requires `scope`: `deal`, `organization`, `contact`, or `project`)
- `create_label` — Create a new label (requires `name`, `color`, `scope`)
- `delete_label` — Delete a label by ID (automatically unlinks from all entities)

#### Label assignment via update tools
All entity update tools (`update_deal`, `update_organization`, `update_contact`, `update_project`) accept an optional `labelIds` array. When provided, it fully replaces the entity's label set. Pass an empty array `[]` to remove all labels. Omit the field to leave labels unchanged.

#### Available label colors
`magenta`, `teal`, `blue`, `orange`, `purple`, `yellow`, `red`, `green`, `indigo`, `pink`

### Notes
Notes are stored as Brain documents (type `notitie`) linked to CRM entities. The notes API is a convenience facade.
- `list_notes` — List notes (documents) linked to a specific entity
- `create_note` — Create a note (document) on an entity

### Audit Trail (requires `admin:audit` scope)
- `list_audit_events` — List audit events with filters (date range, actor, entity, action, severity). Each event includes a computed severity level (`info`, `notable`, `critical`) and diff summary.
- `get_audit_event` — Get full detail of a single audit event including old/new values, diff, severity, and IP address
- `get_audit_summary` — Get aggregated audit statistics: totals, today/week counts, 30-day daily series, top actions, top tokens. Optionally group by `actor`, `entity_type`, `action`, or `severity`.

#### Severity classification (computed server-side)
- **critical**: changes to skills, templates, system prompts, taxonomy, roles, permissions, API tokens; agent mutations on documents; off-hours document changes (19:00–07:00 NL time)
- **notable**: document creates/updates/deletes; role assignments and permission changes
- **info**: all other events (CRM mutations, logins, notes, etc.)

### Infographic Cases
- `list_infographic_cases` — List infographic cases with search and pagination
- `get_infographic_case` — Get a single infographic case by ID
- `create_infographic_case` — Create a new infographic case (requires `title`, optional `organizationId`)
- `update_infographic_case` — Update title, data, or organization of a case
- `delete_infographic_case` — Soft-delete a case (owner only)
- `download_infographic_pdf` — Generate the infographic PDF (returned as base64 resource + saved to temp file)

#### Example workflow (infographic)

1. **Create** a case for an organization:
   > Use `create_infographic_case` with title "Casus Acme" and the organization ID.

2. **Download** the PDF:
   > Use `download_infographic_pdf` with the case ID. The PDF is returned inline and also saved to a local temp file.

## Releases (npm)

**Dist check:** every push/PR runs `pnpm mcp:verify` — committed `packages/mcp/dist` must match `tsc` output (run `pnpm mcp:build` before committing MCP source changes).

**API ↔ MCP coherence (CI):** if a PR or push changes `src/app/api/**/*.ts` (except `*.test.ts` / `*.spec.ts`) or `src/lib/openapi/spec.ts`, the same diff must also change at least one file under `packages/mcp/src/**/*.ts`. Otherwise CI fails so new endpoints are not silently missing from MCP. Opt out when MCP truly does not apply: put **`[mcp sync skip]`** in the PR title or in the pushed commit message.

**Automatic npm publish:** on every push to **`main`** that touches `packages/mcp/src/**` or `packages/mcp/README.md`, the *Publish MCP (npm)* workflow runs: **patch** bump → build → verify → `npm publish` → commit `package.json` + `dist` (message ends with **`[skip mcp]`** so the bot push does not re-trigger a publish) → tag `mcp-v*`. Merge API + MCP updates into `main` together so users get a new package without manual steps.

**Manual publish:** *Actions* → *Publish MCP (npm)* → *Run workflow* for **patch** / **minor** / **major** (e.g. breaking MCP changes).

**Setup once:** repository secret **`NPM_TOKEN`** (publish-capable). *Settings → Actions → General → Workflow permissions:* **Read and write**. Branch protection must allow GitHub Actions to push to `main`, or the git steps fail after a successful publish.

**Note:** npm publish runs in parallel with the main CI job; use branch protection so only green commits reach `main`, or accept the small race. To skip auto-publish for one MCP README/source push, include **`[skip mcp]`** in the commit message.

## Development

```bash
cd packages/mcp
pnpm build    # compile TypeScript
pnpm start    # run the server
```