Skip to main content
Glama

⬡ COR MCP Server

61 MCP tools for Project COR — the project management platform for creative and professional teams.

CI Python License Ruff uv MCP Docker 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.


✨ 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/

  • 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


Related MCP server: Redmine MCP Server

🚀 Quick Start

Prerequisites

  • Python 3.11+

  • uv (recommended) or pip

  • COR account with credentials

Install & Run

# 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

cp .env.example .env

Then choose one auth mode in .env:

Mode A — Email + Password (recommended for personal use)

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)

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

# 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

Add to ~/.hermes/config.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.

Add to claude_desktop_config.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"
      }
    }
  }
}

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

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"

Docker

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

# 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:

    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:

    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

👤 Author

Alejandro Duran — Omnicom Media Group

Available Tools

61 tools
cor_accept_suggested_hoursC

Accept suggested hours for a given date.

Args: datetime_unix: Unix timestamp for the date

Inputs: datetime_unix (required). Returns: result

cor_add_project_collaboratorC

Add a collaborator to a project.

Args: project_id: The project ID user_id: The user ID to add

Inputs: user_id (required), project_id (required). Returns: result

cor_add_project_costC

Add a cost entry to a project.

Args: project_id: The project ID data: Cost data (description, amount, date, etc.)

Inputs: data (required), project_id (required). Returns: result

cor_add_task_labelC

Add a label to a task.

Args: task_id: The task ID label_id: The label ID to add

Inputs: task_id (required), label_id (required). Returns: result

cor_add_team_usersB

Add users to a team.

Args: team_id: The team ID user_ids: List of user IDs to add

Inputs: team_id (required), user_ids (required). Returns: result

cor_change_hours_statusB

Change the status of a time entry.

Args: hours_id: The time entry ID status: New status (e.g. "approved", "rejected", "pending")

Inputs: status (required), hours_id (required). Returns: result

cor_create_clientB

Create a new client.

Args: data: Client data (name, email, phone, notes, etc.)

Inputs: data (required). Returns: result

cor_create_contractB

Create a new contract.

Args: data: Contract data (name, clientId, startDate, endDate, value, status, etc.)

Inputs: data (required). Returns: result

cor_create_contract_positionA

Create a position in a contract.

Args: contract_id: The contract ID data: Position data (title, rate, hours, etc.)

Inputs: data (required), contract_id (required). Returns: result

cor_create_productB

Create a new product.

Args: data: Product data (name, description, rate, etc.)

Inputs: data (required). Returns: result

cor_create_projectB

Create a new project.

Args: data: Project data (name, clientId, description, startDate, endDate, status, etc.)

Inputs: data (required). Returns: result

cor_create_ratecardC

Create a new ratecard.

Args: data: Ratecard data (name, description, rates, etc.)

Inputs: data (required). Returns: result

cor_create_taskA

Create a new task.

Args: data: Task data (title, projectId, description, deadline, priority, etc.)

Inputs: data (required). Returns: result

cor_create_teamC

Create a new team.

Args: data: Team data (name, description, etc.)

Inputs: data (required). Returns: result

cor_delete_allocationB

Delete a resource allocation.

Args: allocation_id: The allocation ID

Inputs: allocation_id (required). Returns: result

cor_delete_clientC

Delete a client.

Args: client_id: The client ID

Inputs: client_id (required). Returns: result

cor_delete_projectB

Delete a project.

Args: project_id: The project ID

Inputs: project_id (required). Returns: result

cor_delete_taskC

Delete a task.

Args: task_id: The task ID

Inputs: task_id (required). Returns: result

cor_get_allocations_by_projectC

Get resource allocations for a project.

Args: project_id: The project ID

Inputs: project_id (required). Returns: result

cor_get_clientB

Get a single client by ID.

Args: client_id: The client ID

Inputs: client_id (required). Returns: result

cor_get_client_feesC

Get fees for a client.

Args: client_id: The client ID

Inputs: client_id (required). Returns: result

cor_get_contractA

Get a single contract by ID.

Args: contract_id: The contract ID

Inputs: contract_id (required). Returns: result

cor_get_contract_positionsC

Get positions for a contract.

Args: contract_id: The contract ID

Inputs: contract_id (required). Returns: result

cor_get_hours_by_dateB

Get time entries for a specific date.

Args: datetime_unix: Unix timestamp for the date

Inputs: datetime_unix (required). Returns: result

cor_get_labelsC

Get labels, optionally filtered by entity type.

Args: entity_type: Entity type filter - "project", "task", "user", or empty for all

Inputs: entity_type. Returns: result

cor_get_my_pending_tasksB

Get my pending tasks.

Inputs: no named parameters. Returns: result

cor_get_my_profileA

Get the current user's profile.

Inputs: no named parameters. Returns: result

cor_get_projectB

Get a single project by ID.

Args: project_id: The project ID

Inputs: project_id (required). Returns: result

cor_get_project_collaboratorsB

Get collaborators for a project.

Args: project_id: The project ID

Inputs: project_id (required). Returns: result

cor_get_project_costsC

Get costs/estimates for a project.

Args: project_id: The project ID

Inputs: project_id (required). Returns: result

cor_get_project_labelsC

Get labels assigned to a project.

Args: project_id: The project ID

Inputs: project_id (required). Returns: result

cor_get_project_messagesC

Get messages for a project.

Args: project_id: The project ID

Inputs: project_id (required). Returns: result

cor_get_project_profitabilityC

Get profitability data for a project.

Args: project_id: The project ID

Inputs: project_id (required). Returns: result

cor_get_project_ratecardC

Get the ratecard assigned to a project.

Args: project_id: The project ID

Inputs: project_id (required). Returns: result

cor_get_project_templatesC

Get available project templates.

Args: page: Page number (default: 1) per_page: Results per page (default: 20)

Inputs: page, per_page. Returns: result

cor_get_ratecardB

Get a single ratecard by ID.

Args: ratecard_id: The ratecard ID

Inputs: ratecard_id (required). Returns: result

cor_get_taskB

Get a single task by ID.

Args: task_id: The task ID

Inputs: task_id (required). Returns: result

cor_get_task_collaboratorsC

Get collaborators for a task.

Args: task_id: The task ID

Inputs: task_id (required). Returns: result

cor_get_task_messagesC

Get messages for a task.

Args: task_id: The task ID

Inputs: task_id (required). Returns: result

cor_get_userA

Get a single user by ID.

Args: user_id: The user ID

Inputs: user_id (required). Returns: result

cor_get_working_timeC

Get working time for users.

Inputs: no named parameters. Returns: result

cor_list_clientsB

List clients.

Args: page: Page number (default: 1) per_page: Results per page (default: 20)

Inputs: page, per_page. Returns: result

cor_list_contractsA

List contracts.

Args: page: Page number (default: 1) per_page: Results per page (default: 20)

Inputs: page, per_page. Returns: result

cor_list_productsC

List products.

Args: page: Page number (default: 1) per_page: Results per page (default: 20)

Inputs: page, per_page. Returns: result

cor_list_projectsA

List projects with optional filters (clientId, status, health, dates).

Args: page: Page number (default: 1) per_page: Results per page (default: 20) filters: Optional dict with filter fields

Inputs: page, filters, per_page. Returns: result

cor_list_ratecardsC

List ratecards.

Args: page: Page number (default: 1) per_page: Results per page (default: 20)

Inputs: page, per_page. Returns: result

cor_list_teamsC

List teams.

Args: page: Page number (default: 1) per_page: Results per page (default: 20)

Inputs: page, per_page. Returns: result

cor_list_usersC

List users with optional filters.

Args: page: Page number (default: 1) per_page: Results per page (default: 20) filters: Optional dict with filter fields

Inputs: page, filters, per_page. Returns: result

cor_log_hoursC

Log hours against a task.

Args: task_id: The task ID to log hours against data: Time entry data (hours, date, notes, etc.)

Inputs: data (required), task_id (required). Returns: result

cor_post_project_messageB

Post a message on a project (with optional @mentions in content).

Args: project_id: The project ID content: Message content text

Inputs: content (required), project_id (required). Returns: result

cor_post_task_messageB

Post a message on a task (with optional @mentions in content).

Args: task_id: The task ID content: Message content text

Inputs: content (required), task_id (required). Returns: result

cor_remove_project_collaboratorC

Remove a collaborator from a project.

Args: project_id: The project ID user_id: The user ID to remove

Inputs: user_id (required), project_id (required). Returns: result

cor_remove_task_labelB

Remove a label from a task.

Args: task_id: The task ID label_id: The label ID to remove

Inputs: task_id (required), label_id (required). Returns: result

cor_remove_team_usersC

Remove users from a team.

Args: team_id: The team ID user_ids: List of user IDs to remove

Inputs: team_id (required), user_ids (required). Returns: result

cor_save_allocationC

Create or update a resource allocation.

Args: data: Allocation data (projectId, userId, allocationPercentage, startDate, endDate, etc.)

Inputs: data (required). Returns: result

cor_search_tasksA

Search tasks with filters (projectId, clientId, status, text, dates, labels).

Args: filters: Dict with filter fields like projectId, status, text, etc.

Inputs: filters. Returns: result

cor_search_time_entriesC

Search time entries with filters (userId, projectId, dates, status).

Args: filters: Dict with filter fields

Inputs: filters. Returns: result

cor_sync_task_collaboratorsA

Sync (replace) collaborators for a task.

Args: task_id: The task ID user_ids: List of user IDs to set as collaborators

Inputs: task_id (required), user_ids (required). Returns: result

cor_update_clientC

Update an existing client.

Args: client_id: The client ID data: Fields to update

Inputs: data (required), client_id (required). Returns: result

cor_update_projectB

Update an existing project (partial update).

Args: project_id: The project ID data: Fields to update

Inputs: data (required), project_id (required). Returns: result

cor_update_taskC

Update an existing task (partial update).

Args: task_id: The task ID data: Fields to update

Inputs: data (required), task_id (required). Returns: result

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

Loading tool schema changelog…

    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.

    Maintenance

    ActivityMaintained
    ResponsivenessNo recent community issues

    Related MCP Connectors

    Related MCP Servers

    • A
      license
      A
      quality
      D
      maintenance
      Exposes the OpenProject REST API as MCP tools for project management, including creating and managing projects, work packages, relations, attachments, users, notifications, watchers, boards, and reference data.
      37
      59 npm
      MIT
    • A
      license
      B
      quality
      D
      maintenance
      MCP server for Redmine project management, enabling tools for managing projects, issues, users, time entries, groups, memberships, versions, wiki, news, attachments, search, and Agile sprints via the Redmine REST API.
      89
      21 npm
      1
      MIT
    • A
      license
      B
      quality
      D
      maintenance
      MCP server to interact with the Shortcut project management API, providing 73 tools for managing stories, epics, iterations, milestones, projects, and more.
      73
      MIT