Skip to main content
Glama
osherai

AgencyAnalytics MCP Server

by osherai
README.md
# AgencyAnalytics MCP Server

A Python [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) server for [AgencyAnalytics](https://agencyanalytics.com) that enables AI assistants to manage campaigns, users, backlinks, keywords, and SEO rankings through natural language.

**Works with:** Claude Desktop, Claude Code, Cursor, Windsurf, Cline, Continue, Zed, and any MCP-compatible client.

> **Brought to you by [Osher Digital](https://osher.com.au)** - Specialist AI consultants helping businesses harness the power of artificial intelligence.

## Features

- **21 Tools** covering the full AgencyAnalytics API surface
- **Campaign Management** - Create, read, update, and delete campaigns (clients)
- **User Management** - Full CRUD for platform users with role and access control
- **Backlink Tracking** - Manage custom backlinks per campaign with domain/page authority
- **SEO Rankings** - Pull keyword-level and campaign-level ranking data across Google, Google Mobile, Google Places, and Bing
- **Keyword Monitoring** - Read tracked keywords per campaign
- **Competitor Tracking** - Add competitor URLs to campaigns for rank comparison
- **SSO Login Grants** - Generate one-time login tokens for client portal access
- **Tag Management** - Create organizational tags

## Prerequisites

- Python 3.10+
- [uv](https://github.com/astral-sh/uv) (recommended) or pip
- An AgencyAnalytics account on a **Premier plan** (API access required)
- An API key (Admin users only)

## Getting Your API Key

1. Log in to AgencyAnalytics as an **Admin** user
2. Go to **Admin Settings > Company Details**
3. Your API key is at the bottom right of the page

> **Note**: Only account Admin users can access the API key. API access is only available on Premier plans. The API key does not expire.

## Installation

### 1. Clone the Repository

```bash
git clone https://github.com/osherai/mcp-agencyanalytics-python.git
cd mcp-agencyanalytics-python
```

### 2. Install Dependencies

Using uv (recommended):

```bash
uv venv && uv pip install -e .
```

Or using pip:

```bash
python -m venv .venv
source .venv/bin/activate  # On Windows: .venv\Scripts\activate
pip install -e .
```

### 3. Configure Credentials

Copy the example environment file and add your API key:

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

Edit `.env`:

```env
AGENCYANALYTICS_API_KEY=your_api_key_here
```

### 4. Test the Connection

```bash
uv run agencyanalytics-mcp
```

The server should start without errors (it will wait for input on stdin).

## Client Configuration

This MCP server works with any MCP-compatible client. Below are setup instructions for popular clients.

> **Note**: Replace `/path/to/mcp-agencyanalytics-python` with your actual installation path in all examples below.

---

### Claude Desktop

Add to your Claude Desktop configuration file:

**macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json`
**Windows**: `%APPDATA%\Claude\claude_desktop_config.json`

```json
{
  "mcpServers": {
    "agencyanalytics": {
      "command": "/path/to/mcp-agencyanalytics-python/.venv/bin/python",
      "args": ["-m", "agencyanalytics_mcp.server"],
      "cwd": "/path/to/mcp-agencyanalytics-python"
    }
  }
}
```

Restart Claude Desktop (fully quit and reopen) for changes to take effect.

---

### Claude Code (CLI)

Add the server using the Claude Code CLI:

```bash
claude mcp add agencyanalytics \
  -e AGENCYANALYTICS_API_KEY=your_api_key_here \
  -- /path/to/mcp-agencyanalytics-python/.venv/bin/python -m agencyanalytics_mcp.server
```

Or add to your `~/.claude/settings.json`:

```json
{
  "mcpServers": {
    "agencyanalytics": {
      "command": "/path/to/mcp-agencyanalytics-python/.venv/bin/python",
      "args": ["-m", "agencyanalytics_mcp.server"],
      "cwd": "/path/to/mcp-agencyanalytics-python"
    }
  }
}
```

---

### Cursor

Add to your Cursor MCP configuration:

**macOS**: `~/.cursor/mcp.json`
**Windows**: `%USERPROFILE%\.cursor\mcp.json`

```json
{
  "mcpServers": {
    "agencyanalytics": {
      "command": "/path/to/mcp-agencyanalytics-python/.venv/bin/python",
      "args": ["-m", "agencyanalytics_mcp.server"],
      "cwd": "/path/to/mcp-agencyanalytics-python"
    }
  }
}
```

Restart Cursor for changes to take effect.

---

### Windsurf (Codeium)

Add to your Windsurf MCP configuration:

**macOS**: `~/.codeium/windsurf/mcp_config.json`
**Windows**: `%USERPROFILE%\.codeium\windsurf\mcp_config.json`

```json
{
  "mcpServers": {
    "agencyanalytics": {
      "command": "/path/to/mcp-agencyanalytics-python/.venv/bin/python",
      "args": ["-m", "agencyanalytics_mcp.server"],
      "cwd": "/path/to/mcp-agencyanalytics-python"
    }
  }
}
```

Restart Windsurf for changes to take effect.

---

### VS Code with Cline Extension

Add to your Cline MCP settings:

**macOS**: `~/Library/Application Support/Code/User/globalStorage/saoudrizwan.claude-dev/settings/cline_mcp_settings.json`
**Windows**: `%APPDATA%\Code\User\globalStorage\saoudrizwan.claude-dev\settings\cline_mcp_settings.json`

```json
{
  "mcpServers": {
    "agencyanalytics": {
      "command": "/path/to/mcp-agencyanalytics-python/.venv/bin/python",
      "args": ["-m", "agencyanalytics_mcp.server"],
      "cwd": "/path/to/mcp-agencyanalytics-python"
    }
  }
}
```

---

### VS Code with Continue Extension

Add to your Continue configuration at `~/.continue/config.json`:

```json
{
  "experimental": {
    "modelContextProtocolServers": [
      {
        "transport": {
          "type": "stdio",
          "command": "/path/to/mcp-agencyanalytics-python/.venv/bin/python",
          "args": ["-m", "agencyanalytics_mcp.server"],
          "cwd": "/path/to/mcp-agencyanalytics-python"
        }
      }
    ]
  }
}
```

---

### Zed Editor

Add to your Zed settings at `~/.config/zed/settings.json`:

```json
{
  "context_servers": {
    "agencyanalytics": {
      "command": {
        "path": "/path/to/mcp-agencyanalytics-python/.venv/bin/python",
        "args": ["-m", "agencyanalytics_mcp.server"]
      },
      "settings": {}
    }
  }
}
```

---

### Example Queries

Once configured, you can ask natural language questions about your AgencyAnalytics data:

- "List all my campaigns"
- "Show me the keyword rankings for campaign 123"
- "Create a new client user for jane@acme.com"
- "What backlinks do we have for the Acme Corp campaign?"
- "Generate an SSO login link for user 456"
- "Add competitor example.com to campaign 123"

## Tools Reference

### Campaign Tools

Campaigns are called "Clients" in the AgencyAnalytics UI (renamed February 2025), but the API still uses the term "campaign".

#### `list_campaigns`

List campaigns with pagination.

| Parameter | Type | Required | Default | Description |
|-----------|------|----------|---------|-------------|
| `offset` | integer | No | 0 | Pagination offset |
| `limit` | integer | No | 50 | Max results per page |

#### `get_campaign`

Get a single campaign by ID.

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `campaign_id` | integer | Yes | The campaign ID |

#### `create_campaign`

Create a new campaign.

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `url` | string | Yes | Website URL for the campaign |
| `company` | string | Yes | Company/client name |

#### `update_campaign`

Update an existing campaign.

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `campaign_id` | integer | Yes | The campaign ID to update |
| `url` | string | No | New website URL |
| `company` | string | No | New company name |

#### `delete_campaign`

Delete a campaign by ID.

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `campaign_id` | integer | Yes | The campaign ID to delete |

---

### User Tools

#### `list_users`

List all platform users with pagination.

| Parameter | Type | Required | Default | Description |
|-----------|------|----------|---------|-------------|
| `offset` | integer | No | 0 | Pagination offset |
| `limit` | integer | No | 50 | Max results per page |

**Returned fields:** `id`, `date_created`, `date_modified`, `email`, `username`, `first_name`, `last_name`, `role`, `campaign_access`, `status`, `account_id`, `campaign_id`

#### `get_user`

Get a single user by ID.

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `user_id` | integer | Yes | The user ID |

#### `create_user`

Create a new user.

| Parameter | Type | Required | Default | Description |
|-----------|------|----------|---------|-------------|
| `email` | string | Yes | - | User email address |
| `first_name` | string | Yes | - | First name |
| `last_name` | string | Yes | - | Last name |
| `role` | string | No | `client` | User role |
| `campaign_access` | string | No | `restricted` | `restricted` or `all` |
| `campaign_id` | integer | No | - | Campaign to grant access to |

#### `update_user`

Update an existing user.

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `user_id` | integer | Yes | The user ID to update |
| `email` | string | No | New email |
| `first_name` | string | No | New first name |
| `last_name` | string | No | New last name |
| `role` | string | No | New role |
| `campaign_access` | string | No | New access level |

#### `delete_user`

Delete a user by ID.

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `user_id` | integer | Yes | The user ID to delete |

---

### Backlink Tools

#### `list_backlinks`

List backlinks with optional campaign filtering.

| Parameter | Type | Required | Default | Description |
|-----------|------|----------|---------|-------------|
| `campaign_id` | integer | No | - | Filter by campaign |
| `offset` | integer | No | 0 | Pagination offset |
| `limit` | integer | No | 50 | Max results per page |

**Returned fields:** `id`, `title`, `status`, `date_created`, `date_published`, `url`, `link_type`, `target_anchor`, `target_url`, `notes`, `campaign_id`, `domain_authority`, `page_authority`

#### `create_backlink`

Create a new backlink.

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `campaign_id` | integer | Yes | Campaign to associate with |
| `title` | string | Yes | Backlink title |
| `url` | string | Yes | Page URL where link appears |
| `target_url` | string | Yes | URL the link points to |
| `target_anchor` | string | No | Anchor text |
| `link_type` | string | No | Type of link |
| `status` | string | No | Backlink status |
| `notes` | string | No | Additional notes |

#### `update_backlink`

Update an existing backlink.

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `backlink_id` | integer | Yes | The backlink ID to update |
| `title` | string | No | New title |
| `url` | string | No | New page URL |
| `target_url` | string | No | New target URL |
| `target_anchor` | string | No | New anchor text |
| `status` | string | No | New status |
| `notes` | string | No | New notes |

#### `delete_backlink`

Delete a backlink by ID.

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `backlink_id` | integer | Yes | The backlink ID to delete |

---

### Keyword & Ranking Tools

#### `list_keywords`

List tracked keywords for a campaign. Keywords can only be queried one campaign at a time.

| Parameter | Type | Required | Default | Description |
|-----------|------|----------|---------|-------------|
| `campaign_id` | integer | Yes | - | The campaign ID |
| `offset` | integer | No | 0 | Pagination offset |
| `limit` | integer | No | 50 | Max results per page |

#### `get_keyword_rankings`

Get keyword-level ranking data for a campaign. Returns positions across Google, Google Mobile, Google Places, and Bing with change tracking, SERP URLs, and monthly search volume.

| Parameter | Type | Required | Default | Description |
|-----------|------|----------|---------|-------------|
| `campaign_id` | integer | Yes | - | The campaign ID |
| `offset` | integer | No | 0 | Pagination offset |
| `limit` | integer | No | 50 | Max results per page |

**Returned fields include:** `keywordId`, `keywordPhrase`, `googleRanking`, `googleRankingChange`, `googleRankingUrl`, `googleMobileRanking`, `googleMobileRankingChange`, `googlePlacesRanking`, `bingRanking`, `bingRankingChange`, `localMonthlySearches`, and more.

#### `get_campaign_rankings`

Get aggregate campaign ranking data over time. Returns historical trends with ranking distribution (positions 1-3, 4-10, 11-20, 21-50, 51+) and competitor data.

| Parameter | Type | Required | Default | Description |
|-----------|------|----------|---------|-------------|
| `campaign_id` | integer | Yes | - | The campaign ID |
| `offset` | integer | No | 0 | Pagination offset |
| `limit` | integer | No | 50 | Max results per page |

---

### Other Tools

#### `create_competitor`

Add a competitor URL to a campaign for rank comparison.

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `campaign_id` | integer | Yes | Campaign to add competitor to |
| `url` | string | Yes | Competitor website URL |

#### `create_tag`

Create an organizational tag.

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `name` | string | Yes | Tag name |

#### `create_login_grant`

Generate a one-time SSO login URL for a user. The token allows logging into AgencyAnalytics without a password. Useful for embedding reports or building custom login pages.

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `user_id` | integer | Yes | User to generate login token for |

**Returns:** `token`, `login_url`, `time`, `user_id`, `origin_user_id`

## Architecture

### How the AgencyAnalytics API Works

Unlike traditional REST APIs, the AgencyAnalytics API uses a **single POST endpoint** (`https://apirequest.app/query`) for all operations. Every request includes a JSON body specifying:

- `provider` - Always `"agency-analytics-v2"`
- `asset` - The resource type (`campaign`, `user`, `backlink`, `keyword`, etc.)
- `operation` - The action (`create`, `read`, `update`, `delete`)
- `fields` - Which fields to return (for read operations)
- `rows` - Data for create/update/delete operations
- `offset` / `limit` - Pagination

Authentication uses HTTP Basic Auth with an empty username and your API key as the password.

### API Assets and Supported Operations

| Asset | Create | Read | Update | Delete | Notes |
|-------|--------|------|--------|--------|-------|
| `campaign` | Yes | Yes | Yes | Yes | Called "Client" in UI since Feb 2025 |
| `user` | Yes | Yes | Yes | Yes | Platform users with roles |
| `backlink` | Yes | Yes | Yes | Yes | Custom backlink tracking |
| `keyword` | - | Yes | - | - | Read only; requires `campaign_id` |
| `keyword-rankings` | - | Yes | - | - | Read only; ranking feed data |
| `campaign-rankings` | - | Yes | - | - | Read only; aggregate ranking feed |
| `competitor` | Yes | - | - | - | Create only |
| `tag` | Yes | - | - | - | Create only |
| `login-grant` | Yes | - | - | - | Create only; SSO tokens |

### API Limitations

The AgencyAnalytics API is designed for **platform management and SEO data retrieval**. It does **not** provide access to:

- Third-party integration data (Google Ads, Facebook Ads, GA4, social media, etc.)
- Report generation or export
- Dashboard/widget data
- PPC, email marketing, or eCommerce metrics

While AgencyAnalytics connects to 90+ integrations in its UI, none of that data is exposed through the API.

### Request Lifecycle

```
MCP Tool Call
  -> AgencyAnalyticsClient._build_payload()  (construct JSON body)
  -> AgencyAnalyticsClient._request()         (POST to https://apirequest.app/query)
  -> Parse response { metadata, data }
  -> format_response()                        (JSON with summary)
  -> Return to MCP client
```

### Project Structure

```
mcp-agencyanalytics-python/
├── pyproject.toml                  # Project config, dependencies, entry point
├── .env.example                    # Environment variables template
├── .gitignore                      # Python/IDE/OS ignores
├── README.md                       # This file
├── src/
│   └── agencyanalytics_mcp/
│       ├── __init__.py             # Package version
│       ├── server.py               # MCP server with 21 tool definitions
│       ├── config.py               # Dataclass config loaded from environment
│       └── client.py               # AgencyAnalytics API client wrapper
└── tests/
    ├── __init__.py
    ├── conftest.py                 # Shared pytest fixtures
    ├── test_config.py              # Configuration tests
    ├── test_client.py              # API client tests
    └── test_server.py              # MCP tool tests
```

### Key Design Decisions

- **Lazy client initialization** - The API client is created on first use and cached globally, avoiding unnecessary auth on startup
- **Synchronous HTTP** - Uses `httpx.Client` (sync) since the AgencyAnalytics API is a single endpoint with no parallelism benefit
- **Error strings** - Tools return `"ERROR: ..."` strings rather than raising exceptions, following MCP conventions for graceful error handling
- **JSON responses** - All tool responses use `json.dumps(data, indent=2, default=str)` for consistent, readable output

## Environment Variables

| Variable | Required | Default | Description |
|----------|----------|---------|-------------|
| `AGENCYANALYTICS_API_KEY` | Yes | - | Your AgencyAnalytics API key (Admin Settings > Company Details) |
| `AGENCYANALYTICS_BASE_URL` | No | `https://apirequest.app/query` | API endpoint URL (override for testing) |

## Testing

### Running Tests

```bash
# Install dev dependencies
uv pip install -e ".[dev]"

# Run all tests
uv run pytest

# Run with verbose output
uv run pytest -v

# Run a specific test file
uv run pytest tests/test_client.py

# Run a specific test class
uv run pytest tests/test_server.py::TestListCampaigns
```

### Test Structure

```
tests/
├── conftest.py         # Fixtures: sample_config, sample_campaign, sample_user,
│                       #   sample_backlink, sample_keyword_ranking, sample_api_response
├── test_config.py      # Tests for environment loading and validation (5 tests)
├── test_client.py      # Tests for auth headers, payload building, HTTP requests,
│                       #   and client methods (15 tests)
└── test_server.py      # Tests for each MCP tool with mocked client (14 tests)
```

All tests use `unittest.mock` to mock the API client. No real API calls are made during testing.

## Troubleshooting

### "Missing required environment variable: AGENCYANALYTICS_API_KEY"

Ensure your `.env` file exists in the project root and contains your API key:

```env
AGENCYANALYTICS_API_KEY=your_api_key_here
```

### "Authentication failed. Check your API key."

1. Verify your API key is correct (Admin Settings > Company Details)
2. Confirm your account is on a Premier plan with API access
3. Ensure you are an Admin user

### "API error" responses

The AgencyAnalytics API may return errors for:

- Invalid asset names or operations
- Missing required fields in create/update operations
- Invalid filter values (e.g., non-existent campaign IDs)
- Rate limiting (specific limits are not publicly disclosed)

### MCP server not appearing in your client

1. Ensure the config file path is correct for your client (see Client Configuration section)
2. Verify the Python path in the config points to the `.venv` directory
3. Fully quit and restart your client application
4. Check your client's logs for error messages
5. Test the server manually:
   ```bash
   cd /path/to/mcp-agencyanalytics-python
   .venv/bin/python -m agencyanalytics_mcp.server
   ```
   The server should start without errors (it will wait for input on stdin)

### Keywords returning empty results

Keywords can only be queried one campaign at a time. You must provide a `campaign_id`:

```
list_keywords(campaign_id=123)
```

## Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

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

## License

This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.

## Acknowledgments

- [AgencyAnalytics](https://agencyanalytics.com/) for their API
- [Model Context Protocol](https://modelcontextprotocol.io/) for the MCP specification
- [Anthropic](https://www.anthropic.com/) for Claude and the MCP Python SDK
- The teams behind [Cursor](https://cursor.sh/), [Windsurf](https://codeium.com/windsurf), [Cline](https://github.com/saoudrizwan/claude-dev), [Continue](https://continue.dev/), and [Zed](https://zed.dev/) for MCP support

---

## About Osher Digital

This project is maintained by [Osher Digital](https://osher.com.au), specialist AI consultants based in Australia. We help businesses integrate AI solutions to streamline operations and drive growth.

**Need help with AI integration?** [Get in touch](https://osher.com.au)

## Disclaimer

This is an unofficial, community-maintained project. It is not affiliated with, officially maintained, or endorsed by AgencyAnalytics.

TDQS

A3.8/5.0

Scored across 20 tools

Disambiguation5/5

Each tool targets a distinct resource and action (users, campaigns, backlinks, keywords, rankings, competitors, tags, login grants). The only potentially overlapping pair (get_keyword_rankings vs get_campaign_rankings) is clearly distinguished by descriptions referring to keyword-level vs aggregate historical data.

Naming Consistency5/5

All tools follow a consistent verb_noun pattern (list_, get_, create_, update_, delete_) with clear resource names. No mixed conventions or vague verbs.

Tool Count3/5

At 20 tools, the set leans heavy. While each tool has a clear purpose, the number exceeds the typical well-scoped range and includes several one-off operations (create_tag, create_competitor, create_login_grant) that could suggest either missing related operations or a broader scope than necessary.

Completeness3/5

Full CRUD exists for users and campaigns, and backlinks have create/update/delete/list. However, keywords only support listing, competitors only support creation, and tags only support creation. Notable gaps in managing core SEO entities make the surface feel incomplete.

Maintenance

ActivityInactive
ResponsivenessNo issues