Skip to main content
Glama
README.md
<div align="center">

# ⬡ COR MCP Server

**61 MCP tools** for [Project COR](https://www.projectcor.com) — the project management platform for creative and professional teams.

[![CI](https://img.shields.io/github/actions/workflow/status/fxckcode/mcp-cor/ci.yml?branch=main&logo=github&label=tests)](https://github.com/fxckcode/mcp-cor/actions/workflows/ci.yml)
[![Python](https://img.shields.io/badge/Python-3.11%2B-3776AB?logo=python)](https://python.org)
[![License](https://img.shields.io/badge/license-MIT-green)](LICENSE)
[![Ruff](https://img.shields.io/badge/Ruff-enabled-7A1FA2?logo=ruff)](https://astral.sh/ruff)
[![uv](https://img.shields.io/badge/uv-package%20manager-6929C4?logo=uv)](https://docs.astral.sh/uv/)
[![MCP](https://img.shields.io/badge/MCP-compatible-5C5CE7?logo=modelcontextprotocol)](https://modelcontextprotocol.io)
[![Docker](https://img.shields.io/badge/Docker-ready-2496ED?logo=docker)](Dockerfile)
[![Tests](https://img.shields.io/badge/tests-24%20passing-22c55e)](tests/)

Exposes the **complete** COR REST API surface as MCP tools — usable from **any** MCP-compatible client: Hermes Agent, Claude Desktop, Cursor, Claude Code, Cline, Windsurf, and more.

</div>

---

## ✨ Features

- **61 MCP tools** — full CRUD for all COR entities across 11 modules
- **Dual authentication** — pick what suits you:
  - **User Credentials** (email + password) — quick personal use
  - **Client Credentials** (API key + client secret) — server-to-server
- **Async everywhere** — `httpx` + `asyncio`, non-blocking I/O
- **Dual transport** — `stdio` (default for agents) and `sse` (HTTP for remote access)
- **OAuth token auto-refresh** — never worry about expiry mid-session
- **Docker support** — one-command deployment
- **Hermes Agent skill** included — see [`skills/cor-mcp-setup/`](skills/cor-mcp-setup/SKILL.md)
- **24 passing tests** — fully mocked, no live credentials required

### Entity Coverage

| Module          | Tools | Covers |
|-----------------|-------|--------|
| Projects        | 14    | CRUD, collaborators, costs, labels, ratecards, templates, profitability |
| Tasks           | 10    | Search, CRUD, collaborators, labels |
| Team & Users    | 8     | Profile, users, teams, working time |
| Clients         | 6     | CRUD, fees |
| Contracts       | 5     | CRUD, positions |
| Time Tracking   | 5     | Log, search, status, accept suggested |
| Messaging       | 4     | Project + task messages |
| Allocations     | 3     | CRUD |
| Ratecards       | 3     | CRUD |
| Products        | 2     | CRUD |
| Labels          | 1     | By entity type |

---

## 🚀 Quick Start

### Prerequisites

- Python 3.11+
- [uv](https://docs.astral.sh/uv/) (recommended) or pip
- COR account with credentials

### Install & Run

```bash
# Clone
git clone https://github.com/fxckcode/mcp-cor.git
cd mcp-cor

# Install with uv (recommended)
uv sync

# Or with pip
pip install -e .
```

### Configure

```bash
cp .env.example .env
```

Then choose **one** auth mode in `.env`:

**Mode A — Email + Password** _(recommended for personal use)_
```ini
COR_EMAIL=your.email@company.com
COR_PASSWORD=your_password
COR_API_URL=https://api.projectcor.com/v1
```

**Mode B — API Key + Client Secret** _(for server-to-server)_
```ini
COR_API_KEY=your_api_key
COR_CLIENT_SECRET=your_client_secret
COR_API_URL=https://api.projectcor.com/v1
```

The server **auto-detects** which mode you configured. Email/password takes priority when both are set.

### Run

```bash
# stdio transport (default — for MCP agents)
uv run cor-mcp-server

# SSE/HTTP transport (for remote access)
uv run cor-mcp-server --transport sse --host 0.0.0.0 --port 8000

# Verbose logging
uv run cor-mcp-server --verbose
```

---

## 🔐 Authentication

Two modes supported. The server auto-detects which to use.

### User Credentials (Mode A)

```
POST https://api.projectcor.com/v1/auth/login
Content-Type: application/x-www-form-urlencoded

email=user@corp.com&password=*****
```

Returns JWT `access_token` + `refresh_token`. Auto-refresh built in.

### Client Credentials (Mode B)

```
POST https://api.projectcor.com/v1/oauth/token?grant_type=client_credentials
Authorization: Basic base64(api_key:client_secret)
```

Returns `access_token` (1-hour TTL). Cached and auto-refreshed with a 60-second safety margin.

---

## 🔌 Connecting from MCP Clients

<details>
<summary><b>Hermes Agent</b> (recommended) — <i>click to expand</i></summary>

Add to `~/.hermes/config.yaml`:

```yaml
mcp_servers:
  cor:
    command: "uv"
    args: ["run", "--directory", "/path/to/cor-mcp-server", "python", "-m", "cor_mcp_server"]
    env:
      COR_EMAIL: "your.email@company.com"
      COR_PASSWORD: "your_password"
      COR_API_URL: "https://api.projectcor.com/v1"
    timeout: 180
    connect_timeout: 60
```

Then restart or run `/reload-mcp`.
</details>

<details>
<summary><b>Claude Desktop</b> — <i>click to expand</i></summary>

Add to `claude_desktop_config.json`:

```json
{
  "mcpServers": {
    "cor": {
      "command": "uv",
      "args": ["run", "--directory", "/path/to/cor-mcp-server", "python", "-m", "cor_mcp_server"],
      "env": {
        "COR_EMAIL": "your.email@company.com",
        "COR_PASSWORD": "your_password",
        "COR_API_URL": "https://api.projectcor.com/v1"
      }
    }
  }
}
```
</details>

<details>
<summary><b>Cursor / Windsurf / Cline</b> — <i>click to expand</i></summary>

In MCP Servers settings, add:

- **Name**: COR
- **Type**: command
- **Command**: `uv run --directory /path/to/cor-mcp-server python -m cor_mcp_server`
- **Environment variables**: `COR_EMAIL`, `COR_PASSWORD`, `COR_API_URL`
</details>

<details>
<summary><b>Claude Code</b> — <i>click to expand</i></summary>

```bash
env COR_EMAIL=your@email.com COR_PASSWORD=your_pass COR_API_URL=https://api.projectcor.com/v1 \
  npx @anthropic/claude-code --mcp "uv run --directory /path/to/cor-mcp-server python -m cor_mcp_server"
```
</details>

### Docker

```bash
docker build -t cor-mcp-server .

# stdio mode
docker run -i --rm \
  -e COR_EMAIL=your@email.com \
  -e COR_PASSWORD=your_password \
  -e COR_API_URL=https://api.projectcor.com/v1 \
  cor-mcp-server

# SSE mode
docker run -i --rm \
  -p 8000:8000 \
  -e COR_EMAIL=your@email.com \
  -e COR_PASSWORD=your_password \
  -e COR_API_URL=https://api.projectcor.com/v1 \
  cor-mcp-server --transport sse
```

---

## 🛠 Available Tools

All tool names are prefixed with `cor_` for clean discoverability.

### Projects (14 tools)

| Tool | Description |
|------|-------------|
| `cor_list_projects` | List projects with filters (clientId, status, health, dates) |
| `cor_get_project` | Get project details |
| `cor_create_project` | Create a new project |
| `cor_update_project` | Update a project (partial) |
| `cor_delete_project` | Delete a project |
| `cor_get_project_collaborators` | Get project collaborators |
| `cor_add_project_collaborator` | Add collaborator to project |
| `cor_remove_project_collaborator` | Remove collaborator from project |
| `cor_get_project_costs` | Get project costs/estimates |
| `cor_add_project_cost` | Add cost to project |
| `cor_get_project_labels` | Get project labels |
| `cor_get_project_ratecard` | Get project ratecard |
| `cor_get_project_templates` | Get available project templates |
| `cor_get_project_profitability` | Get project profitability |

### Tasks (10 tools)

| Tool | Description |
|------|-------------|
| `cor_search_tasks` | Search tasks with filters (projectId, clientId, status, text, dates, labels) |
| `cor_get_my_pending_tasks` | Get my pending tasks |
| `cor_get_task` | Get task details |
| `cor_create_task` | Create a new task |
| `cor_update_task` | Update a task |
| `cor_delete_task` | Delete a task |
| `cor_get_task_collaborators` | Get task collaborators |
| `cor_sync_task_collaborators` | Sync (replace) task collaborators |
| `cor_add_task_label` | Add label to task |
| `cor_remove_task_label` | Remove label from task |

### Team & Users (8 tools)

| Tool | Description |
|------|-------------|
| `cor_get_my_profile` | Get current user profile |
| `cor_list_users` | List users with filters |
| `cor_get_user` | Get user details |
| `cor_list_teams` | List teams |
| `cor_create_team` | Create a new team |
| `cor_add_team_users` | Add users to a team |
| `cor_remove_team_users` | Remove users from a team |
| `cor_get_working_time` | Get working time for users |

### Clients (6 tools)

| Tool | Description |
|------|-------------|
| `cor_list_clients` | List clients |
| `cor_get_client` | Get client details |
| `cor_create_client` | Create a new client |
| `cor_update_client` | Update a client |
| `cor_delete_client` | Delete a client |
| `cor_get_client_fees` | Get client fees |

### Contracts (5 tools)

| Tool | Description |
|------|-------------|
| `cor_list_contracts` | List contracts |
| `cor_get_contract` | Get contract details |
| `cor_create_contract` | Create a new contract |
| `cor_get_contract_positions` | Get contract positions |
| `cor_create_contract_position` | Create position in contract |

### Time Tracking (5 tools)

| Tool | Description |
|------|-------------|
| `cor_log_hours` | Log hours against a task |
| `cor_search_time_entries` | Search time entries with filters |
| `cor_get_hours_by_date` | Get time entries by date |
| `cor_change_hours_status` | Change time entry status |
| `cor_accept_suggested_hours` | Accept suggested hours |

### Messaging (4 tools)

| Tool | Description |
|------|-------------|
| `cor_get_task_messages` | Get messages on a task |
| `cor_post_task_message` | Post a message on a task |
| `cor_get_project_messages` | Get messages on a project |
| `cor_post_project_message` | Post a message on a project |

### Ratecards (3 tools)

| Tool | Description |
|------|-------------|
| `cor_list_ratecards` | List ratecards |
| `cor_get_ratecard` | Get ratecard details |
| `cor_create_ratecard` | Create a new ratecard |

### Allocations (3 tools)

| Tool | Description |
|------|-------------|
| `cor_get_allocations_by_project` | Get allocations for a project |
| `cor_save_allocation` | Create/update a resource allocation |
| `cor_delete_allocation` | Delete a resource allocation |

### Products (2 tools)

| Tool | Description |
|------|-------------|
| `cor_list_products` | List products |
| `cor_create_product` | Create a new product |

### Labels (1 tool)

| Tool | Description |
|------|-------------|
| `cor_get_labels` | Get labels (filter by entity type: project, task, user) |

---

## 🏗 Architecture

```
┌──────────────────────┐     ┌───────────────────────────┐     ┌──────────────────────┐
│  MCP Client          │     │  COR MCP Server           │     │  COR REST API        │
│  (Hermes / Claude /  │◄───►│  (FastMCP + httpx)        │────►│  api.projectcor.com  │
│   Cursor / etc.)     │     │                            │     │  /v1                 │
└──────────────────────┘     ├───────────────────────────┤     └──────────────────────┘
                             │  Auth auto-detection       │
                             │  • Email/Password (prio)   │
                             │  • API Key + Secret        │
                             └───────────────────────────┘
```

---

## 🧑‍💻 Development

```bash
# Install dev deps
uv sync --dev

# Run tests (24 passing tests — no live API needed, uses mocking)
uv run pytest tests/ -v

# Test server startup
uv run cor-mcp-server --help

# Lint
uv run ruff check .
```

### Adding New Tools

To add a new tool to the COR MCP Server:

1. **Create or edit a tool module** in `cor_mcp_server/tools/` (e.g., `reports.py`).

2. **Write an async function** with the `cor_` prefix pattern:

   ```python
   from ..context import get_client

   async def cor_list_reports(page: int = 1, per_page: int = 20) -> str:
       """List reports.

       Args:
           page: Page number (default: 1)
           per_page: Results per page (default: 20)
       """
       client = get_client()
       data = await client.get("/reports", page=page, per_page=per_page)
       import json
       return json.dumps(data, indent=2, ensure_ascii=False, default=str)
   ```

3. **Register the tool** in `cor_mcp_server/server.py`:

   ```python
   from .tools import reports as _reports

   mcp.tool(
       name="cor_list_reports",
       description=_reports.cor_list_reports.__doc__,
   )(_reports.cor_list_reports)
   ```

4. **Add tests** in `tests/test_tools.py` following the existing patterns (import assertion + tool count update).

5. **Update the tool count** in `tests/test_tools.py` if needed.

The `get_client()` function provides a fully-authenticated `CORClient` instance with automatic Bearer token handling. All tools return JSON strings for consistent serialization.

### Test Structure

Tests are in `tests/test_tools.py` and cover:

- **`TestTokenStore`** — Thread-safe token caching, expiry, and refresh logic
- **`TestCORClient`** — HTTP methods (GET, POST, PUT, DELETE), pagination, auth headers
- **`TestToolFunctions`** — Import verification for all 61 tool functions across 11 modules
- **`TestServerCreation`** — Server factory creates a valid FastMCP instance

All tests use mocking — **no live COR credentials required** to run them.

---

## 📁 Project Structure

```
cor-mcp-server/
├── pyproject.toml              # Dependencies and metadata
├── README.md                   # This file
├── LICENSE                     # MIT license
├── .env.example                # Env var template (both auth modes)
├── Dockerfile                  # Container deployment
├── skills/                     # Hermes Agent skills
│   └── cor-mcp-setup/
│       └── SKILL.md            # Skill: setup + usage guide
├── handoff-personal-hermes.md  # Self-contained instructions for personal Hermes
├── cor_mcp_server/
│   ├── __init__.py             # Package init
│   ├── __main__.py             # CLI entry point
│   ├── server.py               # FastMCP server + tool registration (all 61 tools)
│   ├── auth.py                 # OAuth 2.0 dual-mode (user creds + client creds)
│   ├── client.py               # COR API HTTP client (httpx, async)
│   ├── context.py              # Shared client context / dependency injection
│   ├── models.py               # Pydantic models (14 entity types)
│   └── tools/
│       ├── __init__.py
│       ├── projects.py         # 14 tools
│       ├── tasks.py            # 10 tools
│       ├── time_tracking.py    # 5 tools
│       ├── clients.py          # 6 tools
│       ├── contracts.py        # 5 tools
│       ├── messaging.py        # 4 tools
│       ├── team.py             # 8 tools
│       ├── labels.py           # 1 tool
│       ├── ratecards.py        # 3 tools
│       ├── allocations.py      # 3 tools
│       └── products.py         # 2 tools
├── tests/
│   ├── __init__.py
│   └── test_tools.py           # 24 tests for auth, client, and tools
├── .github/                    # GitHub community files
│   ├── dependabot.yml          # Dependency auto-updates
│   ├── ISSUE_TEMPLATE/         # Bug report + feature request templates
│   ├── PULL_REQUEST_TEMPLATE.md
│   └── workflows/              # CI + Release pipelines
├── .gitignore
├── .gitattributes
├── CONTRIBUTING.md
├── CODE_OF_CONDUCT.md
└── SECURITY.md
```

---

## 📄 License

[MIT](LICENSE)

## 👤 Author

**Alejandro Duran** — Omnicom Media Group

TDQS

B3/5.0

Scored across 61 tools

Disambiguation5/5

Each tool targets a distinct resource and action, with clear separation between read/write/delete for different entities. No two tools have overlapping purposes.

Naming Consistency5/5

All tools follow the 'cor_verb_noun' pattern consistently. Underscores and ordering are uniform, making the set predictable.

Tool Count2/5

With 61 tools, the server is bloated for typical MCP usage. This number exceeds reasonable scope and may cause agent confusion or inefficiency.

Completeness3/5

Core entities (clients, projects, tasks) have full CRUD, but contracts, ratecards, and products lack update/delete operations, leaving notable gaps.