Metadata-Version: 2.4
Name: pointguard-mcp
Version: 0.4.0
Summary: MCP server for Creative Market's Pointguard search platform — discover fonts, graphics, and creative assets through natural conversation.
Project-URL: Homepage, https://github.com/creativemarket/pointguard-mcp
Project-URL: Repository, https://github.com/creativemarket/pointguard-mcp
Author: Creative Market
License: MIT
Keywords: ai,creative-market,fonts,mcp,search
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Requires-Python: >=3.10
Requires-Dist: httpx>=0.27
Requires-Dist: mcp>=1.0
Requires-Dist: pydantic>=2.0
Provides-Extra: http
Requires-Dist: starlette>=0.40; extra == 'http'
Requires-Dist: uvicorn>=0.30; extra == 'http'
Description-Content-Type: text/markdown

# Pointguard MCP Server

An [MCP (Model Context Protocol)](https://modelcontextprotocol.io/) server that exposes Creative Market's Pointguard search platform to AI assistants. Discover fonts, graphics, templates, and creative assets through natural conversation.

## Quick Start — Cursor

**1. Install from PyPI:**

```bash
pip install pointguard-mcp
```

**2. Find where it was installed:**

```bash
which pointguard-mcp
```

Copy the full path (e.g. `/usr/local/bin/pointguard-mcp` or `/Users/you/miniforge3/envs/myenv/bin/pointguard-mcp`).

**3. Add to Cursor:**

Go to **Cursor → Settings → Features → MCP → Add new global MCP server**, or create/edit `~/.cursor/mcp.json`:

```json
{
  "mcpServers": {
    "pointguard": {
      "command": "/full/path/to/pointguard-mcp",
      "env": {
        "POINTGUARD_BASE_URL": "https://your-pointguard-api-host.com",
        "CM_API_USER": "your-cm-api-user",
        "CM_API_PASS": "your-cm-api-pass"
      }
    }
  }
}
```

**4. Restart Cursor** (or go to Settings → Features → MCP and click the ↺ refresh icon next to `pointguard`).

**5. Open a chat and try it:**

> *"Find me a condensed sans-serif for a sports brand logo"*

---

## Features

| Tool | Description |
|------|-------------|
| `search_products` | Search by style, mood, subject, or keyword with natural language |
| `get_product` | Internal Pointguard index metadata for agent reasoning — numeric shop ID, category IDs, file type IDs, visibility, staff pick flag |
| `browse_category` | Explore products by classification using the full CM category taxonomy |
| `browse_shop` | Browse a designer or studio's full catalog by shop ID |
| `find_similar` | Find visually/stylistically similar products using embeddings |
| `autocomplete` | Get search suggestions from partial input |
| `suggested_searches` | Discover related searches and trending terms |
| `get_product_details` | Display-ready product data from the CM API — title, URL, full image gallery, description, shop name, formatted price. Accepts up to 50 IDs |

## Example Queries

Once connected to an MCP-compatible client (Claude, Cursor, etc.), you can ask:

- *"Find me a condensed sans-serif for a sports brand logo."*
- *"Show me retro display fonts for a vinyl record label."*
- *"I need textures and grunge graphics for a concert poster."*
- *"Find script fonts under $30 that work for wedding stationery."*
- *"Show me font alternatives to Filson Pro Font Family (16 fonts)."* (uses `find_similar`)
- *"What's trending in the Fonts category?"*
- *"Show me everything by Loudoun Design Co."* (uses `browse_shop`)
- *"Browse sans-serif fonts."* (uses `browse_category` with ID 81)

## Installation

```bash
pip install pointguard-mcp
```

Or in development mode:

```bash
cd pointguard-mcp
pip install -e .
```

## Configuration

Set environment variables before running:

| Variable | Default | Description |
|----------|---------|-------------|
| `POINTGUARD_BASE_URL` | `http://localhost:5000` | Pointguard API base URL |
| `POINTGUARD_TIMEOUT` | `30` | Request timeout in seconds |
| `CM_API_USER` | _(required)_ | Creative Market API basic auth username |
| `CM_API_PASS` | _(required)_ | Creative Market API basic auth password |

## Usage

### Standalone (stdio)

```bash
pointguard-mcp
```

### With Claude Desktop

Add to your `claude_desktop_config.json`:

```json
{
  "mcpServers": {
    "pointguard": {
      "command": "pointguard-mcp",
      "env": {
        "POINTGUARD_BASE_URL": "https://your-api-host.com",
        "CM_API_USER": "your-cm-api-user",
        "CM_API_PASS": "your-cm-api-pass"
      }
    }
  }
}
```

### With Cursor

Add to `.cursor/mcp.json` in your project:

```json
{
  "mcpServers": {
    "pointguard": {
      "command": "pointguard-mcp",
      "env": {
        "POINTGUARD_BASE_URL": "https://your-api-host.com",
        "CM_API_USER": "your-cm-api-user",
        "CM_API_PASS": "your-cm-api-pass"
      }
    }
  }
}
```

## Agent Guide (Full Project Map)

This section is a practical guide for coding agents and maintainers working on the full MCP project.

### Repository Layout

- `src/pointguard_mcp/server.py`: MCP server entrypoint, tool schemas, tool dispatch, HTTP/SSE transport routes.
- `src/pointguard_mcp/client.py`: Async HTTP client for Pointguard API and Creative Market detail API.
- `pyproject.toml`: Package metadata and CLI entrypoint (`pointguard-mcp = pointguard_mcp.server:main`).
- `terraform/`: AWS infra for ECR + ECS Fargate + ALB deployment.
- `Dockerfile`: Container image used for remote deployment.

### Runtime Modes

- Default mode is stdio (for Claude Desktop, Cursor command-based config, and local agent use).
- Set `MCP_HTTP=1` to run as an HTTP server with:
  - `GET/POST/DELETE /mcp` for Streamable HTTP transport
  - `GET /sse` + `POST /messages/` for legacy SSE transport

### Transport and Auth Behavior

- If `MCP_API_KEY` is set, HTTP calls to `/mcp` and `/sse` must include header `x-api-key`.
- In stdio mode, no HTTP auth is involved because transport is local process pipes.
- For remote clients:
  - Streamable HTTP clients should use `/mcp`.
  - SSE clients should use `/sse`.

### Tooling Contract

- Search and discovery tools (`search_products`, `browse_category`, `browse_shop`, `find_similar`) return Pointguard index-oriented results.
- `get_product` returns internal metadata useful for agent planning (shop/category/file-type IDs, flags).
- `get_product_details` returns display-ready Creative Market data (title, URL, description, images, shop).

### Typical Agent Flow

1. Run `search_products` with natural language query.
2. Use `get_product` when internal metadata is needed for reasoning/filtering.
3. Use `browse_shop` or `browse_category` for expansion.
4. Use `get_product_details` to produce user-facing final answers.

### Deployment Notes for Agents

- ECS/ALB deployment lives in `terraform/`.
- ALB TLS now depends on `acm_certificate_arn`:
  - empty value: HTTP-only listener on port 80
  - non-empty value: HTTPS listener on 443 + HTTP 301 redirect to HTTPS
- Cloudflare setup for custom domain usually needs:
  - ACM validation CNAME (DNS only)
  - service CNAME `mcp` -> ALB DNS name (typically proxied)

### Local Validation Checklist

```bash
# package and imports
pip install -e .

# stdio server starts
python -m pointguard_mcp.server

# HTTP server starts
MCP_HTTP=1 MCP_PORT=8080 python -m pointguard_mcp.server

# health check
curl -i http://localhost:8080/health
```

### Common Failure Modes

- `301 Moved Permanently` from internal API host: use HTTPS base URL or allow redirects.
- TLS hostname mismatch on internal ELB: certificate does not match host; use a valid host/cert path for production.
- Remote client `Invalid URL`: missing scheme (must be `https://...`, not bare host).
- Remote client `ECONNREFUSED` on `https://...`: no HTTPS listener configured on origin ALB or DNS/proxy mismatch.

### Deployment Runbook (GitHub Actions + Terraform)

Use this runbook when shipping code or infrastructure changes for the MCP service.

#### A) Application Deploy via GitHub Actions

This repo includes workflows under `.github/workflows/`:

- `build.yml`: builds and pushes Docker images to ECR (on `main` pushes, releases, or manual dispatch)
- `publish.yml`: package publishing flow

Recommended flow for runtime changes:

1. Merge code changes to `main`.
2. Confirm `Build and Push Container` succeeds in GitHub Actions.
3. Record the image tag from workflow output (`sha-xxxxxxx` on push, release tag on release event).
4. Trigger infrastructure rollout using Terraform (next section).

Required GitHub environment secrets (as used by current workflows):

- `ECS_ADMIN_DEPLOY_KEY` (for `build.yml`)
- `ECS_ADMIN_DEPLOY_SECRET` (for `build.yml`)

Recommended additional CI checks before merge:

1. Run `python -m pointguard_mcp.server` (stdio smoke test).
2. Run `MCP_HTTP=1 MCP_PORT=8080 python -m pointguard_mcp.server` and verify `/health`.
3. Confirm no tool schema regressions in `server.py`.

#### B) Infrastructure Deploy via Terraform (ECS/ALB path)

Terraform config is in `terraform/` and is intended for ECS + ALB deployments.

Recommended Terraform workflow:

1. Update Terraform code in `terraform/`.
2. Run plan in Terraform Cloud (or local CLI) and review listener/DNS/cert diffs carefully.
3. Apply in the target workspace.
4. Validate outputs and ALB listeners.

Important variables for HTTPS custom domain setup:

- `acm_certificate_arn`: when set, enables ALB HTTPS listener `:443` and HTTP `:80` redirect to HTTPS
- `pointguard_base_url`
- `cm_api_user`
- `cm_api_pass`
- `mcp_api_key`

Post-apply validation checklist:

1. ALB listener `443/HTTPS` exists and uses expected ACM cert.
2. ALB listener `80/HTTP` redirects to `443`.
3. Cloudflare DNS includes:
  - ACM validation CNAME (DNS only)
  - `mcp` CNAME to ALB (proxied, unless your network policy requires DNS-only)
4. Health endpoint is reachable:

```bash
curl -i https://mcp.creativemarket.com/health
```

#### C) End-to-end Release Order (safe sequence)

1. Merge and let `build.yml` push a fresh image to ECR.
2. Apply Terraform changes (or run ECS force-new-deployment if only code changed and task definition already references latest image).
3. Verify ALB listeners and health endpoint.
4. Verify MCP transport in Inspector, then in client apps.

### PyPI Release Process

This repo uses GitHub Trusted Publishing for PyPI via `.github/workflows/publish.yml`.
Publishing is triggered by creating a GitHub Release with status `published`.

#### 1) Bump package version

Update `version` in `pyproject.toml` (must be higher than current PyPI version).

#### 2) Merge to main

Open/merge a PR with the version bump and release changes.

#### 3) Create GitHub release

In GitHub UI:

1. Go to Releases -> Draft a new release.
2. Create tag `vX.Y.Z` matching the version in `pyproject.toml`.
3. Publish the release.

This triggers `publish.yml` which:

1. Runs smoke tests.
2. Builds wheel/sdist.
3. Publishes to PyPI with OIDC trusted publishing.

#### 4) Verify publish

```bash
python -m pip index versions pointguard-mcp
```

You should see the new version listed as latest.

#### Notes

- Re-publishing the same version will fail on PyPI.
- If publish fails, check GitHub Action logs for `publish.yml` first (test stage or trusted publishing permissions).

### MCP Inspector UI Debugging Guide

Use MCP Inspector to isolate transport/auth issues before testing in Claude or Cursor.

#### 1) Open Inspector

```bash
npx @modelcontextprotocol/inspector
```

#### 2) Test Streamable HTTP endpoint

- Transport: `Streamable HTTP`
- URL: `https://mcp.creativemarket.com/mcp`
- Header: `x-api-key: <MCP_API_KEY>` (if API key protection is enabled)

Expected result:

- Connect succeeds
- Tools list loads
- You can call `search_products`

#### 3) If Streamable HTTP fails, test SSE endpoint

- Transport: `SSE`
- URL: `https://mcp.creativemarket.com/sse`
- Header: `x-api-key: <MCP_API_KEY>`

#### 4) Error-to-cause quick map

- `Invalid URL`: URL missing scheme (`https://`)
- `ECONNREFUSED`: origin not accepting requested protocol/port (often no ALB HTTPS listener)
- `401 Unauthorized`: wrong or missing `x-api-key`
- `301 redirect loops/unexpected redirects`: mismatch between client endpoint and listener redirect policy

### Operational Notes

- Keep `MCP_API_KEY` in secret stores only; do not hardcode in repo files.
- For internal Pointguard APIs, prefer `https://` base URLs to avoid redirect overhead.
- If using internal ELB hostnames with cert mismatch, use proper cert/hostname pairing for production-grade security.
- When troubleshooting client startup, inspect local logs first (for Claude Desktop: `~/Library/Logs/Claude/mcp.log`).

## Category Reference

### Top-level Categories

| ID | Category |
|----|----------|
| 1 | Graphics |
| 2 | Templates |
| 3 | Fonts |
| 4 | Add-ons (Brushes & More) |
| 6 | Photos |
| 7 | 3D |
| 8 | Stock Video / Footage |
| 23 | Illustrations |
| 26 | Icons |
| 1800 | Mockups |

### Font Subcategories

| ID | Subcategory |
|----|-------------|
| 80 | Serif |
| 81 | Sans Serif |
| 82 | Script |
| 84 | Symbols |
| 85 | Blackletter |
| 86 | Slab Serif |
| 88 | Handwritten |

### Template Subcategories

| ID | Subcategory |
|----|-------------|
| 50 | Logos |
| 19 | Posters |
| 42 | Flyers |
| 46 | Invitations |
| 45 | Business Cards |
| 49 | Presentations |
| 52 | Social Media |
| 44 | Stationery |
| 41 | Brochures |
| 43 | Resumes |

### Other Notable Subcategories

| ID | Subcategory |
|----|-------------|
| 27 | Textures (Graphics) |
| 28 | Patterns (Graphics) |
| 24 | Objects (Graphics) |
| 131 | Nature (Photos) |
| 132 | People (Photos) |
| 127 | Food & Drink (Photos) |
| 21 | Product Mockups |
| 1801 | Device Mockups |
| 1805 | Apparel Mockups |
| 2601 | Solid Icons |
| 2602 | Outline Icons |
| 2603 | Duotone Icons |

See `CATEGORY_MAP` in `server.py` for the full list of 170+ category and subcategory mappings.

## Development

```bash
# Install in development mode
pip install -e .

# Run directly (stdio)
python -m pointguard_mcp.server

# Run as HTTP/SSE server locally
MCP_HTTP=1 MCP_API_KEY=secret python -m pointguard_mcp.server

# Test with MCP inspector (browser UI)
npx @modelcontextprotocol/inspector pointguard-mcp
```

## Deploying to AWS (HTTP/SSE mode)

The server supports HTTP/SSE transport for remote deployment. Set `MCP_HTTP=1` to switch from stdio to HTTP.

### Environment Variables (HTTP mode)

| Variable | Default | Description |
|----------|---------|-------------|
| `MCP_HTTP` | `0` | Set to `1` to enable HTTP/SSE transport |
| `MCP_HOST` | `0.0.0.0` | Host to bind to |
| `MCP_PORT` | `8080` | Port to listen on |
| `MCP_API_KEY` | _(optional)_ | If set, all SSE connections must include `X-API-Key: <value>` |

### ECS + ALB Deployment (production)

This repository deploys to AWS ECS Fargate behind an ALB using the Terraform in `terraform/`.

#### 1) Build and publish image to ECR

Use GitHub Actions `build.yml` (recommended), or build manually:

```bash
aws ecr get-login-password --region us-east-1 | docker login --username AWS --password-stdin <account-id>.dkr.ecr.us-east-1.amazonaws.com
docker build -t pointguard-mcp .
docker tag pointguard-mcp:latest <account-id>.dkr.ecr.us-east-1.amazonaws.com/pointguard-mcp:latest
docker push <account-id>.dkr.ecr.us-east-1.amazonaws.com/pointguard-mcp:latest
```

#### 2) Apply Terraform infrastructure and service configuration

Ensure workspace variables are set (or defaults are correct):

- `acm_certificate_arn` (for HTTPS listener on 443)
- `pointguard_base_url`
- `cm_api_user`
- `cm_api_pass`
- `mcp_api_key`

Then run Terraform plan/apply in `terraform/`.

#### 3) Force ECS service rollout when needed

If only image tag/state changed and you need immediate recycle:

```bash
aws ecs update-service \
  --region us-east-1 \
  --cluster pointguard-mcp \
  --service pointguard-mcp \
  --force-new-deployment
```

Optional wait:

```bash
aws ecs wait services-stable \
  --region us-east-1 \
  --cluster pointguard-mcp \
  --services pointguard-mcp
```

#### 4) DNS and TLS

For `mcp.creativemarket.com`:

1. Keep ACM validation CNAME record as DNS-only in Cloudflare.
2. Point `mcp` CNAME to the ALB DNS name.
3. Keep `mcp` record proxied unless your network policy requires direct origin access.

#### 5) Smoke tests

```bash
curl -i https://mcp.creativemarket.com/health
curl -i -H "x-api-key: <MCP_API_KEY>" https://mcp.creativemarket.com/mcp
```

### Remote Client Setup Example (mcp.creativemarket.com/mcp)

For clients with native remote Streamable HTTP support:

```json
{
  "mcpServers": {
    "pointguard-public": {
      "type": "http",
      "url": "https://mcp.creativemarket.com/mcp",
      "headers": {
        "x-api-key": "<MCP_API_KEY>"
      }
    }
  }
}
```

For Claude Desktop, if direct remote transport is skipped in your build, use an `mcp-remote` bridge:

```json
{
  "mcpServers": {
    "pointguard-public": {
      "command": "npx",
      "args": [
        "-y",
        "mcp-remote",
        "https://mcp.creativemarket.com/mcp",
        "--transport",
        "http-only",
        "--header",
        "x-api-key:${POINTGUARD_PUBLIC_API_KEY}"
      ],
      "env": {
        "POINTGUARD_PUBLIC_API_KEY": "<MCP_API_KEY>"
      }
    }
  }
}
```

## Architecture

```
┌─────────────────┐     stdio      ┌──────────────────┐     HTTP/JSON     ┌──────────────────────┐
│  AI Assistant   │◄──────────────►│  pointguard-mcp  │◄────────────────►│  Pointguard API      │
│  (Claude, etc.) │                │  (MCP Server)    │                   │  (Flask + ES)        │
└─────────────────┘                └──────────────────┘                   └──────────────────────┘
                                           │
                                           │ HTTPS
                                           ▼
                                   ┌──────────────────┐
                                   │  creativemarket  │
                                   │  .com (search +  │
                                   │  product detail) │
                                   └──────────────────┘
```

The MCP server has two data sources:

1. **Pointguard API** (`POINTGUARD_BASE_URL`) — powers `search_products`, `get_product`, `browse_category`, `browse_shop`, `find_similar`, `autocomplete`, `suggested_searches`
2. **creativemarket.com API** — powers `get_product_details` (full image gallery, descriptions, licenses via authenticated API)

## Endpoints Used

| Tool | Endpoint | Returns |
|------|----------|---------|
| `search_products` | `POST /creative-market/products/search` | Product IDs + relevance tokens |
| `get_product` | `GET /creative-market/product/{id}` | Internal metadata — shop ID, category IDs, file types, flags |
| `browse_category` | `POST /creative-market/products/category` | Product IDs |
| `browse_shop` | `POST /creative-market/products/shop` | Product IDs |
| `find_similar` | `POST /creative-market/product/{id}/similar` | Product IDs |
| `autocomplete` | `GET /creative-market/completions/suggest` | Suggestion strings |
| `suggested_searches` | `POST /creative-market/suggested-searches` | Related search terms |
| `get_product_details` | `GET https://creativemarket.com/api/external/newsletter/product-details/{ids}` | Display-ready detail (title, URL, images, description, shop URL) |
