Skip to main content
Glama
isaganiesteron

Contractor Scale Context MCP Server

README.md
# Contractor Scale Context MCP Server

Internal MCP server for Contractor Scale client context, meeting records, and strategy summaries. Built on Cloudflare Workers with Supabase and the Contractor Scale context API.

[![Deploy to Cloudflare Workers](https://deploy.workers.cloudflare.com/button)](https://deploy.workers.cloudflare.com/?url=https://github.com/isaganiesteron/contractor-scale-context-mcp)

## Overview

This MCP (Model Context Protocol) server gives AI agents like TypingMind, ClickUp Brain, and other MCP clients access to Contractor Scale internal context β€” client background, meeting transcripts, and team huddles β€” through a single connection.

### Key Features

- πŸ“‹ **3 Context Tools** β€” List clients, fetch full client context, search meetings
- πŸ”Œ **Dual Transport** β€” Streamable HTTP (`POST /mcp`) for modern clients + legacy SSE (`GET /sse`) for TypingMind
- πŸš€ **Cloudflare Workers** β€” Serverless, stateless session handling across worker isolates
- πŸ—„οΈ **Supabase Integration** β€” Client lookup via `master_clients`, meeting search via `meetings`
- 🌐 **Context API** β€” Full client summaries from `contractor-scale-api.onrender.com`
- πŸ” **Dual API Key Auth** β€” `API_KEY` for MCP clients; `CONTEXT_API_KEY` for the context API

## Architecture

```
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  AI Clients                  β”‚
β”‚  (TypingMind, ClickUp, etc.) β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
               β”‚ MCP Protocol
               β”‚  β€’ Streamable HTTP β†’ POST /mcp
               β”‚  β€’ Legacy SSE      β†’ GET /sse
               β”‚
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Cloudflare Worker                     β”‚
β”‚  API_KEY (inbound)                    β”‚
β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚ β”‚ 3 Context Tools                   β”‚ β”‚
β”‚ β”‚  β€’ list_clients                   β”‚ β”‚
β”‚ β”‚  β€’ get_client_context             β”‚ β”‚
β”‚ β”‚  β€’ get_meetings                   β”‚ β”‚
β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                  β”‚
        β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
        β”‚                    β”‚
β”Œβ”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚   Supabase     β”‚  β”‚ Contractor Scale Context API          β”‚
β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚  β”‚ contractor-scale-api.onrender.com     β”‚
β”‚ β”‚master_     β”‚ β”‚  β”‚ GET /client-context/{client}          β”‚
β”‚ β”‚ clients    β”‚ β”‚  β”‚ Header: X-API-Key: CONTEXT_API_KEY    β”‚
β”‚ β”‚meetings    β”‚ β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
```

## Endpoints

| Endpoint | Method | Purpose |
|----------|--------|---------|
| `/` | GET | Health check (no API key required) |
| `/mcp` | POST | Streamable HTTP MCP transport (ClickUp, modern clients) |
| `/mcp` | DELETE | Terminate MCP session |
| `/sse` | GET | Legacy SSE connection (TypingMind) |
| `/sse` | POST | Direct MCP message (no session) |
| `/sse/message` | POST | MCP message with SSE session |

**Health check response** includes `version`, `toolCount`, `tools`, and available `endpoints`.

**MCP protocol version:** `2025-03-26`

**Authentication:**

- MCP endpoints (`/mcp`, `/sse`) require `X-API-Key: API_KEY` (except health check `/`)
- `get_client_context` calls the context API with `X-API-Key: CONTEXT_API_KEY`

**Session handling:** Streamable HTTP uses stateless `Mcp-Session-Id` headers (no in-memory session store), which is required for Cloudflare Workers where requests may hit different isolates.

## Available Tools (3)

### `list_clients`

List all available client names from the `master_clients` table. Use these names with `clientName` in other tools.

**Input:** none

### `get_client_context`

Get the full context summary for a client including meetings, messages, and overall strategy. Use when asked about a specific client's background, history, or current status.

**Input:**

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `clientName` | string | yes | Client name as stored in `master_clients` |

**Behavior:**

1. Validates the client exists in `master_clients`
2. Fetches `https://contractor-scale-api.onrender.com/client-context/{clientName}` (URL-encoded) with header `X-API-Key: CONTEXT_API_KEY`
3. Returns the text response from the API

### `get_meetings`

Search and retrieve meeting records including summaries, full transcripts, and recording URLs. Use when asked what was discussed in a meeting, what a client said, or what tasks were mentioned in a team huddle.

**Inputs:**

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `clientName` | string | no | Resolved to `location_id` via `master_clients` |
| `meetingTitle` | string | no | Partial text search on `meeting_title` |
| `meetingDate` | string | no | ISO date (`YYYY-MM-DD`) to filter meetings on that date |
| `inviteeName` | string | no | Search within the `invitees_name` array column |
| `category` | string | no | Filter on `category` or `category_auto`: `team`, `client`, or `other` |
| `output` | string | no | What to return: `summary` (default), `full_transcript`, or `recording_url` |

**Behavior:**

- Builds a Supabase query on the `meetings` table using whichever parameters are provided
- If no parameters are provided, returns the 10 most recent meetings ordered by `meeting_date` descending
- Always includes `meeting_title`, `meeting_date`, `invitees_name`, and `category` in results

## Prerequisites

- Node.js 18+ (for local development)
- Cloudflare Workers account
- Supabase account with `master_clients` and `meetings` tables
- TypingMind, ClickUp Brain, or another MCP-compatible client

### Database Schema

Your Supabase database must have these tables:

**`master_clients` table:**

```sql
CREATE TABLE master_clients (
  id SERIAL PRIMARY KEY,
  client_name TEXT UNIQUE NOT NULL,
  location_id TEXT NOT NULL,
  created_at TIMESTAMP DEFAULT NOW()
);
```

**`meetings` table:**

```sql
CREATE TABLE meetings (
  id SERIAL PRIMARY KEY,
  location_id TEXT,
  meeting_title TEXT,
  meeting_date TIMESTAMPTZ,
  invitees_name TEXT[],
  category TEXT,
  category_auto TEXT,
  summary TEXT,
  full_transcript TEXT,
  recording_url TEXT,
  created_at TIMESTAMP DEFAULT NOW()
);
```

## Installation

### 1. Clone the Repository

```bash
git clone https://github.com/isaganiesteron/contractor-scale-context-mcp.git
cd contractor-scale-context-mcp
```

### 2. Install Dependencies

```bash
npm install
```

### 3. Configure Environment Variables

Set credentials in `wrangler.jsonc` under `vars`:

```jsonc
"vars": {
  "API_KEY": "your-mcp-api-key",
  "CONTEXT_API_KEY": "your-context-api-key",
  "SUPABASE_URL": "https://your-project.supabase.co",
  "SUPABASE_SERVICE_KEY": "your-service-role-key"
}
```

| Variable | Purpose |
|----------|---------|
| `API_KEY` | Authenticates MCP clients **to this worker** (`X-API-Key` on `/mcp`, `/sse`) |
| `CONTEXT_API_KEY` | Authenticates this worker **to the context API** (`X-API-Key` on `contractor-scale-api.onrender.com`) |

For local dev you can also use `.dev.vars` (same variable names). On deploy, values in `wrangler.jsonc` β†’ `vars` are injected as worker environment variables.

> **Note:** `wrangler.jsonc` is gitignored β€” copy `wrangler.jsonc.example` and fill in your credentials locally.

## Development

### Local Development

```bash
npm run dev
```

The server will start on `http://localhost:8787`

### Run Tests

```bash
npm test
```

## Deployment

### Deploy to Cloudflare Workers

```bash
npm run deploy
```

### Configure Secrets

Credentials are set in `wrangler.jsonc` β†’ `vars`. Update `CONTEXT_API_KEY` there, then redeploy:

```bash
npm run deploy
```

### Verify Deployment

```bash
curl https://contractor-scale-context-mcp.isagani.workers.dev/
```

Expected response:

```json
{
  "name": "contractor-scale-context-mcp",
  "version": "1.0.2",
  "status": "running",
  "toolCount": 3,
  "tools": ["list_clients", "get_client_context", "get_meetings"],
  "endpoints": { "sse": "/sse", "mcp": "/mcp" }
}
```

**Production URL:** `https://contractor-scale-context-mcp.isagani.workers.dev`

## Usage

### Configure in ClickUp Brain (Streamable HTTP)

- **URL:** `https://contractor-scale-context-mcp.isagani.workers.dev/mcp`
- **Auth:** `X-API-Key` header with your `API_KEY` value

After connecting, disconnect and reconnect if tools don't appear β€” clients cache the tool list from `tools/list`.

### Configure in TypingMind (Legacy SSE)

Add to your TypingMind MCP configuration:

```json
{
	"mcpServers": {
		"contractor-scale-context": {
			"url": "https://contractor-scale-context-mcp.isagani.workers.dev/sse",
			"transport": "sse",
			"name": "Contractor Scale Context"
		}
	}
}
```

### Example Queries

**List available clients:**

```
User: "What clients are available?"
```

**Get client background and strategy:**

```
User: "What's the current status and strategy for Bear Construction?"
```

**Search recent meetings:**

```
User: "What was discussed in our last team huddle?"
```

**Find meetings for a specific client:**

```
User: "Show me recent meetings with ABC Remodeling"
```

**Get a full transcript:**

```
User: "Get the full transcript of yesterday's client call with Bear Construction"
```

(The AI will call `get_meetings` with `clientName`, `meetingDate`, and `output: "full_transcript"`.)

## Error Handling

The server provides clear error messages:

- `"Client 'ABC Remodeling' not found"` β€” Invalid client name (includes available clients when possible)
- `"No meetings matched the criteria."` β€” No results for the given filters
- `"Failed to fetch client context"` β€” Context API returned an error (check `CONTEXT_API_KEY`)
- `"CONTEXT_API_KEY is not configured on the worker"` β€” Missing `CONTEXT_API_KEY` in `wrangler.jsonc`
- `"API key required"` / `"Invalid API key"` β€” MCP authentication failure (`API_KEY`)

## Production Debugging

Structured JSON logs are emitted for `wrangler tail`:

```bash
wrangler tail
```

Key log events: `request.received`, `route.matched`, `mcp.method.parsed`, `mcp.tools.call`, `mcp.response.sent`, `mcp.session.created`, `auth.failed`.

## Troubleshooting

### Issue: "Session not found" (ClickUp / Streamable HTTP)

**Cause:** Client is using a stale tool list or an old server build.

**Fix:**

1. Verify health check shows `toolCount: 3` and the expected `tools` array
2. Disconnect and reconnect the MCP integration in ClickUp
3. Ensure you are using `POST /mcp`, not `/sse`

### Issue: "Client not found"

**Check:**

- Client exists in `master_clients` table
- `client_name` spelling matches (case-insensitive fallback is supported)

**Fix:**

- Add client to `master_clients` table
- Use `list_clients` to see exact names

### Issue: "No meetings matched the criteria"

**Check:**

- Filters are not too restrictive
- `location_id` in `meetings` matches the client's `location_id` in `master_clients`
- Date format is `YYYY-MM-DD`

**Fix:**

- Call `get_meetings` with no parameters to see the 10 most recent meetings
- Broaden filters (e.g. drop `meetingTitle` or `inviteeName`)

### Issue: Context API fetch fails (401 Unauthorized)

**Check:**

- `CONTEXT_API_KEY` is set in `wrangler.jsonc` β†’ `vars`
- The key matches what `contractor-scale-api.onrender.com` expects
- Client exists in `master_clients`
- Worker has been redeployed after updating the key

**Fix:**

```jsonc
// wrangler.jsonc
"vars": {
  "CONTEXT_API_KEY": "your-context-api-key"
}
```

Then run `npm run deploy`.

### Issue: Context API fetch fails (other errors)

- Client exists in `master_clients`
- `contractor-scale-api.onrender.com` is reachable
- The client has context data on the API

## Security

### Authentication

- **Inbound:** MCP clients authenticate with `API_KEY` via `X-API-Key` on `/mcp` and `/sse`
- **Outbound:** Worker authenticates to the context API with `CONTEXT_API_KEY` via `X-API-Key`
- Credentials are configured in `wrangler.jsonc` β†’ `vars` (gitignored, not committed)
- No credentials exposed in logs or error messages

### Data Privacy

- Meeting and client data served on demand from Supabase and the context API
- Logs do not contain PII beyond what is needed for debugging

## Project Structure

```
contractor-scale-context-mcp/
β”œβ”€β”€ src/
β”‚   β”œβ”€β”€ index.ts                 # MCP server, tool definitions, routing
β”‚   └── lib/
β”‚       β”œβ”€β”€ supabase.ts          # Supabase client factory
β”‚       β”œβ”€β”€ client-resolver.ts   # Client name β†’ location ID lookup
β”‚       β”œβ”€β”€ tool-result.ts       # MCP result helpers
β”‚       └── types.ts             # Shared type definitions
β”œβ”€β”€ test/
β”‚   └── index.spec.ts
β”œβ”€β”€ postman/                     # API test collections
β”œβ”€β”€ wrangler.jsonc.example       # Cloudflare Workers config template
β”œβ”€β”€ package.json
β”œβ”€β”€ tsconfig.json
└── README.md
```

## Contributing

Contributions are welcome! Please follow these guidelines:

1. Fork the repository
2. Create a feature branch (`git checkout -b feature/amazing-feature`)
3. Commit your changes (`git commit -m 'Add amazing feature'`)
4. Push to the branch (`git push origin feature/amazing-feature`)
5. Open a Pull Request

## License

MIT License - see [LICENSE](LICENSE) file for details

## Support

- **Issues:** [GitHub Issues](https://github.com/isaganiesteron/contractor-scale-context-mcp/issues)
- **Discussions:** [GitHub Discussions](https://github.com/isaganiesteron/contractor-scale-context-mcp/discussions)
- **Email:** isagani@contractorscale.com

## Acknowledgments

- Built with [Model Context Protocol](https://modelcontextprotocol.io/)
- Powered by [Cloudflare Workers](https://workers.cloudflare.com/)
- Database by [Supabase](https://supabase.com/)
- Template based on [typingmind-mcp-cloudflare-starter](https://github.com/isaganiesteron/typingmind-mcp-cloudflare-starter)

## Related Projects

- [TypingMind](https://typingmind.com/) β€” AI chat interface with MCP support (SSE)
- [ClickUp](https://clickup.com/) β€” Project management with Brain AI (Streamable HTTP)

---

**Built with ❀️ by [Isagani Esteron](https://github.com/isaganiesteron) at [Contractor Scale](https://contractorscale.com)**