atracker-mcp-server 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +113 -0
- package/package.json +17 -0
- package/src/client.js +48 -0
- package/src/index.js +43 -0
- package/src/tools/campaigns.js +47 -0
- package/src/tools/domains.js +21 -0
- package/src/tools/flows.js +33 -0
- package/src/tools/landings.js +34 -0
- package/src/tools/offers.js +37 -0
- package/src/tools/reports.js +22 -0
- package/src/tools/settings.js +20 -0
- package/src/tools/sources.js +33 -0
- package/src/tools/status.js +6 -0
package/README.md
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# ATracker MCP Server
|
|
2
|
+
|
|
3
|
+
[Model Context Protocol](https://modelcontextprotocol.io) server for managing your self-hosted ATracker instance from AI agents like Cursor and Claude Desktop.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install -g atracker-mcp-server
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Configuration
|
|
12
|
+
|
|
13
|
+
### Cursor (`~/.cursor/mcp.json`)
|
|
14
|
+
|
|
15
|
+
```json
|
|
16
|
+
{
|
|
17
|
+
"mcpServers": {
|
|
18
|
+
"atracker": {
|
|
19
|
+
"command": "npx",
|
|
20
|
+
"args": ["-y", "atracker-mcp-server"],
|
|
21
|
+
"env": {
|
|
22
|
+
"ATRACKER_URL": "http://your-tracker-ip",
|
|
23
|
+
"ATRACKER_TOKEN": "YOUR_API_TOKEN"
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### Claude Desktop (`claude_desktop_config.json`)
|
|
31
|
+
|
|
32
|
+
```json
|
|
33
|
+
{
|
|
34
|
+
"mcpServers": {
|
|
35
|
+
"atracker": {
|
|
36
|
+
"command": "npx",
|
|
37
|
+
"args": ["-y", "atracker-mcp-server"],
|
|
38
|
+
"env": {
|
|
39
|
+
"ATRACKER_URL": "http://your-tracker-ip",
|
|
40
|
+
"ATRACKER_TOKEN": "YOUR_API_TOKEN"
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Getting an API Token
|
|
48
|
+
|
|
49
|
+
1. Open your tracker in a browser.
|
|
50
|
+
2. Go to **Settings → API Tokens**.
|
|
51
|
+
3. Click **Create token**, give it a name, and select **All resources** (*) for full MCP access.
|
|
52
|
+
4. Copy the token (it is shown only once).
|
|
53
|
+
|
|
54
|
+
## Available Tools
|
|
55
|
+
|
|
56
|
+
### Campaigns
|
|
57
|
+
- `list_campaigns` — List all campaigns (optional status filter)
|
|
58
|
+
- `get_campaign` — Get campaign details by ID
|
|
59
|
+
- `create_campaign` — Create a new campaign
|
|
60
|
+
- `update_campaign` — Update an existing campaign
|
|
61
|
+
- `pause_campaign` — Pause a campaign
|
|
62
|
+
- `resume_campaign` — Resume a paused campaign
|
|
63
|
+
- `delete_campaign` — Archive a campaign
|
|
64
|
+
|
|
65
|
+
### Offers
|
|
66
|
+
- `list_offers` — List all offers
|
|
67
|
+
- `get_offer` — Get offer details by ID
|
|
68
|
+
- `create_offer` — Create a new offer
|
|
69
|
+
- `update_offer` — Update an existing offer
|
|
70
|
+
- `delete_offer` — Archive an offer
|
|
71
|
+
|
|
72
|
+
### Flows
|
|
73
|
+
- `list_flows` — List flows (optional campaign_id filter)
|
|
74
|
+
- `get_flow` — Get flow details by ID
|
|
75
|
+
- `create_flow` — Create a new flow
|
|
76
|
+
- `update_flow` — Update an existing flow
|
|
77
|
+
- `delete_flow` — Delete a flow
|
|
78
|
+
|
|
79
|
+
### Landing Pages
|
|
80
|
+
- `list_landings` — List landing pages
|
|
81
|
+
- `get_landing` — Get landing page details by ID
|
|
82
|
+
- `create_landing` — Create a new landing page
|
|
83
|
+
- `update_landing` — Update an existing landing page
|
|
84
|
+
- `delete_landing` — Archive a landing page
|
|
85
|
+
|
|
86
|
+
### Traffic Sources
|
|
87
|
+
- `list_sources` — List traffic sources
|
|
88
|
+
- `get_source` — Get traffic source details by ID
|
|
89
|
+
- `create_source` — Create a new traffic source
|
|
90
|
+
- `update_source` — Update a traffic source
|
|
91
|
+
- `delete_source` — Delete a traffic source
|
|
92
|
+
|
|
93
|
+
### Domains
|
|
94
|
+
- `list_domains` — List domains
|
|
95
|
+
- `get_domain` — Get domain details by ID
|
|
96
|
+
- `create_domain` — Create a new domain
|
|
97
|
+
- `delete_domain` — Delete a domain
|
|
98
|
+
|
|
99
|
+
### Reports & Conversions
|
|
100
|
+
- `get_reports` — Get report data with date range and grouping
|
|
101
|
+
- `get_conversions` — Get conversion data with filters
|
|
102
|
+
|
|
103
|
+
### System
|
|
104
|
+
- `get_system_status` — Get tracker system status
|
|
105
|
+
- `get_settings` — Get tracker settings
|
|
106
|
+
- `update_settings` — Update tracker settings
|
|
107
|
+
|
|
108
|
+
## Environment Variables
|
|
109
|
+
|
|
110
|
+
| Variable | Required | Description |
|
|
111
|
+
|----------|----------|-------------|
|
|
112
|
+
| `ATRACKER_URL` | Yes | Tracker URL (e.g. `http://213.176.94.21`) |
|
|
113
|
+
| `ATRACKER_TOKEN` | Yes | API token from Settings → API Tokens |
|
package/package.json
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "atracker-mcp-server",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "MCP Server for ATracker self-hosted ad tracker",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"atracker-mcp-server": "./src/index.js"
|
|
8
|
+
},
|
|
9
|
+
"files": ["src"],
|
|
10
|
+
"dependencies": {
|
|
11
|
+
"@modelcontextprotocol/sdk": "^1.12.0"
|
|
12
|
+
},
|
|
13
|
+
"engines": {
|
|
14
|
+
"node": ">=20"
|
|
15
|
+
},
|
|
16
|
+
"license": "UNLICENSED"
|
|
17
|
+
}
|
package/src/client.js
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
class ApiError extends Error {
|
|
2
|
+
constructor(status, body) {
|
|
3
|
+
super(body?.error || `HTTP ${status}`);
|
|
4
|
+
this.status = status;
|
|
5
|
+
this.body = body;
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function createClient(url, token) {
|
|
10
|
+
const base = (url || '').replace(/\/+$/, '');
|
|
11
|
+
|
|
12
|
+
async function request(method, path, { body, query } = {}) {
|
|
13
|
+
let fullUrl = `${base}/api/v1${path}`;
|
|
14
|
+
if (query) {
|
|
15
|
+
const params = new URLSearchParams();
|
|
16
|
+
for (const [k, v] of Object.entries(query)) {
|
|
17
|
+
if (v !== undefined && v !== null && v !== '') params.set(k, String(v));
|
|
18
|
+
}
|
|
19
|
+
const qs = params.toString();
|
|
20
|
+
if (qs) fullUrl += `?${qs}`;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const opts = {
|
|
24
|
+
method,
|
|
25
|
+
headers: {
|
|
26
|
+
'Authorization': `Bearer ${token}`,
|
|
27
|
+
'Content-Type': 'application/json',
|
|
28
|
+
'Accept': 'application/json',
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
if (body && method !== 'GET') opts.body = JSON.stringify(body);
|
|
32
|
+
|
|
33
|
+
const res = await fetch(fullUrl, opts);
|
|
34
|
+
if (res.status === 204) return null;
|
|
35
|
+
let data;
|
|
36
|
+
try { data = await res.json(); } catch { data = null; }
|
|
37
|
+
if (!res.ok) throw new ApiError(res.status, data);
|
|
38
|
+
return data;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return {
|
|
42
|
+
get: (path, opts) => request('GET', path, opts),
|
|
43
|
+
post: (path, body, opts) => request('POST', path, { ...opts, body }),
|
|
44
|
+
put: (path, body, opts) => request('PUT', path, { ...opts, body }),
|
|
45
|
+
patch: (path, body, opts) => request('PATCH', path, { ...opts, body }),
|
|
46
|
+
del: (path, opts) => request('DELETE', path, opts),
|
|
47
|
+
};
|
|
48
|
+
}
|
package/src/index.js
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
3
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
4
|
+
import { createClient } from './client.js';
|
|
5
|
+
import { registerCampaignTools } from './tools/campaigns.js';
|
|
6
|
+
import { registerOfferTools } from './tools/offers.js';
|
|
7
|
+
import { registerFlowTools } from './tools/flows.js';
|
|
8
|
+
import { registerLandingTools } from './tools/landings.js';
|
|
9
|
+
import { registerSourceTools } from './tools/sources.js';
|
|
10
|
+
import { registerDomainTools } from './tools/domains.js';
|
|
11
|
+
import { registerReportTools } from './tools/reports.js';
|
|
12
|
+
import { registerStatusTools } from './tools/status.js';
|
|
13
|
+
import { registerSettingsTools } from './tools/settings.js';
|
|
14
|
+
|
|
15
|
+
const url = process.env.ATRACKER_URL;
|
|
16
|
+
const token = process.env.ATRACKER_TOKEN;
|
|
17
|
+
|
|
18
|
+
if (!url || !token) {
|
|
19
|
+
process.stderr.write('Error: ATRACKER_URL and ATRACKER_TOKEN environment variables are required.\n');
|
|
20
|
+
process.stderr.write('Example:\n');
|
|
21
|
+
process.stderr.write(' ATRACKER_URL=http://your-tracker ATRACKER_TOKEN=atk_... npx atracker-mcp-server\n');
|
|
22
|
+
process.exit(1);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const client = createClient(url, token);
|
|
26
|
+
|
|
27
|
+
const server = new McpServer({
|
|
28
|
+
name: 'atracker',
|
|
29
|
+
version: '1.0.0',
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
registerCampaignTools(server, client);
|
|
33
|
+
registerOfferTools(server, client);
|
|
34
|
+
registerFlowTools(server, client);
|
|
35
|
+
registerLandingTools(server, client);
|
|
36
|
+
registerSourceTools(server, client);
|
|
37
|
+
registerDomainTools(server, client);
|
|
38
|
+
registerReportTools(server, client);
|
|
39
|
+
registerStatusTools(server, client);
|
|
40
|
+
registerSettingsTools(server, client);
|
|
41
|
+
|
|
42
|
+
const transport = new StdioServerTransport();
|
|
43
|
+
await server.connect(transport);
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
export function registerCampaignTools(server, client) {
|
|
2
|
+
server.tool('list_campaigns', 'List all campaigns. Optional filter by status.', { status: { type: 'string', description: 'active or paused', enum: ['active', 'paused'] } }, async ({ status }) => {
|
|
3
|
+
const rows = await client.get('/campaigns', { query: { status } });
|
|
4
|
+
return { content: [{ type: 'text', text: JSON.stringify(rows, null, 2) }] };
|
|
5
|
+
});
|
|
6
|
+
|
|
7
|
+
server.tool('get_campaign', 'Get campaign details by ID.', { id: { type: 'string', description: 'Campaign UUID' } }, async ({ id }) => {
|
|
8
|
+
const row = await client.get(`/campaigns/${id}`);
|
|
9
|
+
return { content: [{ type: 'text', text: JSON.stringify(row, null, 2) }] };
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
server.tool('create_campaign', 'Create a new campaign.', {
|
|
13
|
+
name: { type: 'string', description: 'Campaign name' },
|
|
14
|
+
alias: { type: 'string', description: 'URL alias (optional)' },
|
|
15
|
+
domain: { type: 'string', description: 'Domain (optional)' },
|
|
16
|
+
traffic_source_id: { type: 'string', description: 'Traffic source UUID (optional)' },
|
|
17
|
+
}, async (args) => {
|
|
18
|
+
const row = await client.post('/campaigns', args);
|
|
19
|
+
return { content: [{ type: 'text', text: JSON.stringify(row, null, 2) }] };
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
server.tool('update_campaign', 'Update an existing campaign.', {
|
|
23
|
+
id: { type: 'string', description: 'Campaign UUID' },
|
|
24
|
+
name: { type: 'string', description: 'New name (optional)' },
|
|
25
|
+
alias: { type: 'string', description: 'New alias (optional)' },
|
|
26
|
+
domain: { type: 'string', description: 'New domain (optional)' },
|
|
27
|
+
}, async ({ id, ...rest }) => {
|
|
28
|
+
const body = Object.fromEntries(Object.entries(rest).filter(([, v]) => v !== undefined));
|
|
29
|
+
const row = await client.put(`/campaigns/${id}`, body);
|
|
30
|
+
return { content: [{ type: 'text', text: JSON.stringify(row, null, 2) }] };
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
server.tool('pause_campaign', 'Pause a campaign.', { id: { type: 'string', description: 'Campaign UUID' } }, async ({ id }) => {
|
|
34
|
+
const row = await client.patch(`/campaigns/${id}/status`, { status: 'paused' });
|
|
35
|
+
return { content: [{ type: 'text', text: JSON.stringify(row, null, 2) }] };
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
server.tool('resume_campaign', 'Resume a paused campaign.', { id: { type: 'string', description: 'Campaign UUID' } }, async ({ id }) => {
|
|
39
|
+
const row = await client.patch(`/campaigns/${id}/status`, { status: 'active' });
|
|
40
|
+
return { content: [{ type: 'text', text: JSON.stringify(row, null, 2) }] };
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
server.tool('delete_campaign', 'Archive (soft-delete) a campaign.', { id: { type: 'string', description: 'Campaign UUID' } }, async ({ id }) => {
|
|
44
|
+
await client.del(`/campaigns/${id}`);
|
|
45
|
+
return { content: [{ type: 'text', text: 'Campaign archived.' }] };
|
|
46
|
+
});
|
|
47
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export function registerDomainTools(server, client) {
|
|
2
|
+
server.tool('list_domains', 'List domains. Optional filter by status.', { status: { type: 'string', description: 'active or paused', enum: ['active', 'paused'] } }, async ({ status }) => {
|
|
3
|
+
const rows = await client.get('/domains', { query: { status } });
|
|
4
|
+
return { content: [{ type: 'text', text: JSON.stringify(rows, null, 2) }] };
|
|
5
|
+
});
|
|
6
|
+
|
|
7
|
+
server.tool('get_domain', 'Get domain details by ID.', { id: { type: 'string', description: 'Domain UUID' } }, async ({ id }) => {
|
|
8
|
+
const row = await client.get(`/domains/${id}`);
|
|
9
|
+
return { content: [{ type: 'text', text: JSON.stringify(row, null, 2) }] };
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
server.tool('create_domain', 'Create a new domain.', { domain: { type: 'string', description: 'Domain name (e.g. trk.example.com)' } }, async (args) => {
|
|
13
|
+
const row = await client.post('/domains', args);
|
|
14
|
+
return { content: [{ type: 'text', text: JSON.stringify(row, null, 2) }] };
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
server.tool('delete_domain', 'Delete a domain.', { id: { type: 'string', description: 'Domain UUID' } }, async ({ id }) => {
|
|
18
|
+
await client.del(`/domains/${id}`);
|
|
19
|
+
return { content: [{ type: 'text', text: 'Domain deleted.' }] };
|
|
20
|
+
});
|
|
21
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export function registerFlowTools(server, client) {
|
|
2
|
+
server.tool('list_flows', 'List flows. Optional filter by campaign_id.', { campaign_id: { type: 'string', description: 'Campaign UUID (optional)' } }, async ({ campaign_id }) => {
|
|
3
|
+
const rows = await client.get('/flows', { query: { campaign_id } });
|
|
4
|
+
return { content: [{ type: 'text', text: JSON.stringify(rows, null, 2) }] };
|
|
5
|
+
});
|
|
6
|
+
|
|
7
|
+
server.tool('get_flow', 'Get flow details by ID.', { id: { type: 'string', description: 'Flow UUID' } }, async ({ id }) => {
|
|
8
|
+
const row = await client.get(`/flows/${id}`);
|
|
9
|
+
return { content: [{ type: 'text', text: JSON.stringify(row, null, 2) }] };
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
server.tool('create_flow', 'Create a new flow for a campaign.', {
|
|
13
|
+
campaign_id: { type: 'string', description: 'Campaign UUID' },
|
|
14
|
+
name: { type: 'string', description: 'Flow name' },
|
|
15
|
+
}, async (args) => {
|
|
16
|
+
const row = await client.post('/flows', args);
|
|
17
|
+
return { content: [{ type: 'text', text: JSON.stringify(row, null, 2) }] };
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
server.tool('update_flow', 'Update an existing flow.', {
|
|
21
|
+
id: { type: 'string', description: 'Flow UUID' },
|
|
22
|
+
name: { type: 'string', description: 'New name (optional)' },
|
|
23
|
+
}, async ({ id, ...rest }) => {
|
|
24
|
+
const body = Object.fromEntries(Object.entries(rest).filter(([, v]) => v !== undefined));
|
|
25
|
+
const row = await client.put(`/flows/${id}`, body);
|
|
26
|
+
return { content: [{ type: 'text', text: JSON.stringify(row, null, 2) }] };
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
server.tool('delete_flow', 'Delete a flow.', { id: { type: 'string', description: 'Flow UUID' } }, async ({ id }) => {
|
|
30
|
+
await client.del(`/flows/${id}`);
|
|
31
|
+
return { content: [{ type: 'text', text: 'Flow deleted.' }] };
|
|
32
|
+
});
|
|
33
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export function registerLandingTools(server, client) {
|
|
2
|
+
server.tool('list_landings', 'List landing pages. Optional filter by status.', { status: { type: 'string', description: 'active or paused', enum: ['active', 'paused'] } }, async ({ status }) => {
|
|
3
|
+
const rows = await client.get('/landings', { query: { status } });
|
|
4
|
+
return { content: [{ type: 'text', text: JSON.stringify(rows, null, 2) }] };
|
|
5
|
+
});
|
|
6
|
+
|
|
7
|
+
server.tool('get_landing', 'Get landing page details by ID.', { id: { type: 'string', description: 'Landing page UUID' } }, async ({ id }) => {
|
|
8
|
+
const row = await client.get(`/landings/${id}`);
|
|
9
|
+
return { content: [{ type: 'text', text: JSON.stringify(row, null, 2) }] };
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
server.tool('create_landing', 'Create a new landing page.', {
|
|
13
|
+
name: { type: 'string', description: 'Landing page name' },
|
|
14
|
+
url: { type: 'string', description: 'Landing page URL' },
|
|
15
|
+
}, async (args) => {
|
|
16
|
+
const row = await client.post('/landings', args);
|
|
17
|
+
return { content: [{ type: 'text', text: JSON.stringify(row, null, 2) }] };
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
server.tool('update_landing', 'Update an existing landing page.', {
|
|
21
|
+
id: { type: 'string', description: 'Landing page UUID' },
|
|
22
|
+
name: { type: 'string', description: 'New name (optional)' },
|
|
23
|
+
url: { type: 'string', description: 'New URL (optional)' },
|
|
24
|
+
}, async ({ id, ...rest }) => {
|
|
25
|
+
const body = Object.fromEntries(Object.entries(rest).filter(([, v]) => v !== undefined));
|
|
26
|
+
const row = await client.put(`/landings/${id}`, body);
|
|
27
|
+
return { content: [{ type: 'text', text: JSON.stringify(row, null, 2) }] };
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
server.tool('delete_landing', 'Archive (soft-delete) a landing page.', { id: { type: 'string', description: 'Landing page UUID' } }, async ({ id }) => {
|
|
31
|
+
await client.del(`/landings/${id}`);
|
|
32
|
+
return { content: [{ type: 'text', text: 'Landing page archived.' }] };
|
|
33
|
+
});
|
|
34
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export function registerOfferTools(server, client) {
|
|
2
|
+
server.tool('list_offers', 'List all offers. Optional filter by status.', { status: { type: 'string', description: 'active or paused', enum: ['active', 'paused'] } }, async ({ status }) => {
|
|
3
|
+
const rows = await client.get('/offers', { query: { status } });
|
|
4
|
+
return { content: [{ type: 'text', text: JSON.stringify(rows, null, 2) }] };
|
|
5
|
+
});
|
|
6
|
+
|
|
7
|
+
server.tool('get_offer', 'Get offer details by ID.', { id: { type: 'string', description: 'Offer UUID' } }, async ({ id }) => {
|
|
8
|
+
const row = await client.get(`/offers/${id}`);
|
|
9
|
+
return { content: [{ type: 'text', text: JSON.stringify(row, null, 2) }] };
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
server.tool('create_offer', 'Create a new offer.', {
|
|
13
|
+
name: { type: 'string', description: 'Offer name' },
|
|
14
|
+
url: { type: 'string', description: 'Offer URL' },
|
|
15
|
+
payout: { type: 'number', description: 'Payout amount (optional)' },
|
|
16
|
+
currency: { type: 'string', description: 'Currency code (optional)' },
|
|
17
|
+
}, async (args) => {
|
|
18
|
+
const row = await client.post('/offers', args);
|
|
19
|
+
return { content: [{ type: 'text', text: JSON.stringify(row, null, 2) }] };
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
server.tool('update_offer', 'Update an existing offer.', {
|
|
23
|
+
id: { type: 'string', description: 'Offer UUID' },
|
|
24
|
+
name: { type: 'string', description: 'New name (optional)' },
|
|
25
|
+
url: { type: 'string', description: 'New URL (optional)' },
|
|
26
|
+
payout: { type: 'number', description: 'New payout (optional)' },
|
|
27
|
+
}, async ({ id, ...rest }) => {
|
|
28
|
+
const body = Object.fromEntries(Object.entries(rest).filter(([, v]) => v !== undefined));
|
|
29
|
+
const row = await client.put(`/offers/${id}`, body);
|
|
30
|
+
return { content: [{ type: 'text', text: JSON.stringify(row, null, 2) }] };
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
server.tool('delete_offer', 'Archive (soft-delete) an offer.', { id: { type: 'string', description: 'Offer UUID' } }, async ({ id }) => {
|
|
34
|
+
await client.del(`/offers/${id}`);
|
|
35
|
+
return { content: [{ type: 'text', text: 'Offer archived.' }] };
|
|
36
|
+
});
|
|
37
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export function registerReportTools(server, client) {
|
|
2
|
+
server.tool('get_reports', 'Get tracker report data with date range and grouping.', {
|
|
3
|
+
date_from: { type: 'string', description: 'Start date YYYY-MM-DD (default: 7 days ago)' },
|
|
4
|
+
date_to: { type: 'string', description: 'End date YYYY-MM-DD (default: today)' },
|
|
5
|
+
group_by: { type: 'string', description: 'Group by field: campaign, offer, country, etc.' },
|
|
6
|
+
campaign_id: { type: 'string', description: 'Filter by campaign UUID (optional)' },
|
|
7
|
+
}, async ({ date_from, date_to, group_by, campaign_id }) => {
|
|
8
|
+
const data = await client.get('/reports', { query: { date_from, date_to, group_by, campaign_id } });
|
|
9
|
+
return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
server.tool('get_conversions', 'Get conversion data with date range and filters.', {
|
|
13
|
+
date_from: { type: 'string', description: 'Start date YYYY-MM-DD' },
|
|
14
|
+
date_to: { type: 'string', description: 'End date YYYY-MM-DD' },
|
|
15
|
+
campaign_id: { type: 'string', description: 'Filter by campaign UUID (optional)' },
|
|
16
|
+
status: { type: 'string', description: 'Conversion status (optional)' },
|
|
17
|
+
limit: { type: 'number', description: 'Max rows (default 100, max 10000)' },
|
|
18
|
+
}, async ({ date_from, date_to, campaign_id, status, limit }) => {
|
|
19
|
+
const data = await client.get('/conversions', { query: { date_from, date_to, campaign_id, status, limit } });
|
|
20
|
+
return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
|
|
21
|
+
});
|
|
22
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export function registerSettingsTools(server, client) {
|
|
2
|
+
server.tool('get_settings', 'Get all tracker settings. Optional filter by category.', {
|
|
3
|
+
category: { type: 'string', description: 'Settings category (optional)' },
|
|
4
|
+
}, async ({ category }) => {
|
|
5
|
+
const data = await client.get('/settings', { query: { category } });
|
|
6
|
+
return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
server.tool('update_settings', 'Update one or more tracker settings.', {
|
|
10
|
+
settings: {
|
|
11
|
+
type: 'string',
|
|
12
|
+
description: 'JSON array of { key, value } objects, e.g. [{"key":"default_currency","value":"EUR"}]',
|
|
13
|
+
},
|
|
14
|
+
}, async ({ settings }) => {
|
|
15
|
+
let items;
|
|
16
|
+
try { items = JSON.parse(settings); } catch { return { content: [{ type: 'text', text: 'Error: settings must be a valid JSON array of {key, value} objects.' }], isError: true }; }
|
|
17
|
+
const data = await client.put('/settings', items);
|
|
18
|
+
return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
|
|
19
|
+
});
|
|
20
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export function registerSourceTools(server, client) {
|
|
2
|
+
server.tool('list_sources', 'List traffic sources.', {}, async () => {
|
|
3
|
+
const rows = await client.get('/sources');
|
|
4
|
+
return { content: [{ type: 'text', text: JSON.stringify(rows, null, 2) }] };
|
|
5
|
+
});
|
|
6
|
+
|
|
7
|
+
server.tool('get_source', 'Get traffic source details by ID.', { id: { type: 'string', description: 'Traffic source UUID' } }, async ({ id }) => {
|
|
8
|
+
const row = await client.get(`/sources/${id}`);
|
|
9
|
+
return { content: [{ type: 'text', text: JSON.stringify(row, null, 2) }] };
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
server.tool('create_source', 'Create a new traffic source.', {
|
|
13
|
+
name: { type: 'string', description: 'Source name' },
|
|
14
|
+
type: { type: 'string', description: 'Source type (optional)' },
|
|
15
|
+
}, async (args) => {
|
|
16
|
+
const row = await client.post('/sources', args);
|
|
17
|
+
return { content: [{ type: 'text', text: JSON.stringify(row, null, 2) }] };
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
server.tool('update_source', 'Update a traffic source.', {
|
|
21
|
+
id: { type: 'string', description: 'Traffic source UUID' },
|
|
22
|
+
name: { type: 'string', description: 'New name (optional)' },
|
|
23
|
+
}, async ({ id, ...rest }) => {
|
|
24
|
+
const body = Object.fromEntries(Object.entries(rest).filter(([, v]) => v !== undefined));
|
|
25
|
+
const row = await client.put(`/sources/${id}`, body);
|
|
26
|
+
return { content: [{ type: 'text', text: JSON.stringify(row, null, 2) }] };
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
server.tool('delete_source', 'Delete a traffic source.', { id: { type: 'string', description: 'Traffic source UUID' } }, async ({ id }) => {
|
|
30
|
+
await client.del(`/sources/${id}`);
|
|
31
|
+
return { content: [{ type: 'text', text: 'Traffic source deleted.' }] };
|
|
32
|
+
});
|
|
33
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export function registerStatusTools(server, client) {
|
|
2
|
+
server.tool('get_system_status', 'Get tracker system status: hostname, CPU, memory, uptime, version.', {}, async () => {
|
|
3
|
+
const data = await client.get('/system-status');
|
|
4
|
+
return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
|
|
5
|
+
});
|
|
6
|
+
}
|