@anycast/mcp 0.1.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 ADDED
@@ -0,0 +1,135 @@
1
+ # @anycast/mcp
2
+
3
+ MCP (Model Context Protocol) server for the [Anycast Platform](https://anycast.net). Enables AI assistants like Claude, Cursor, and Copilot to manage agents, webhooks, and P2P connectivity.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install -g @anycast/mcp
9
+ ```
10
+
11
+ ## Configuration
12
+
13
+ Set your Anycast API key:
14
+
15
+ ```bash
16
+ export ANYCAST_API_KEY=your-api-key-here
17
+ export ANYCAST_API_URL=https://api.anycast.net # optional, defaults to localhost:3002
18
+ ```
19
+
20
+ ## Usage with Claude Desktop
21
+
22
+ Add to your `claude_desktop_config.json`:
23
+
24
+ ```json
25
+ {
26
+ "mcpServers": {
27
+ "anycast": {
28
+ "command": "anycast-mcp",
29
+ "env": {
30
+ "ANYCAST_API_KEY": "your-api-key-here",
31
+ "ANYCAST_API_URL": "https://api.anycast.net"
32
+ }
33
+ }
34
+ }
35
+ }
36
+ ```
37
+
38
+ ## Usage with Claude Code
39
+
40
+ Add to your project's `.claude/settings.json`:
41
+
42
+ ```json
43
+ {
44
+ "mcpServers": {
45
+ "anycast": {
46
+ "command": "npx",
47
+ "args": ["@anycast/mcp"],
48
+ "env": {
49
+ "ANYCAST_API_KEY": "your-api-key-here"
50
+ }
51
+ }
52
+ }
53
+ }
54
+ ```
55
+
56
+ ## Usage with Cursor
57
+
58
+ Add to Cursor settings > MCP Servers:
59
+
60
+ ```json
61
+ {
62
+ "anycast": {
63
+ "command": "anycast-mcp",
64
+ "env": {
65
+ "ANYCAST_API_KEY": "your-api-key-here"
66
+ }
67
+ }
68
+ }
69
+ ```
70
+
71
+ ## Available Tools
72
+
73
+ ### Agents
74
+ | Tool | Description |
75
+ |------|-------------|
76
+ | `list_agents` | List all agents with status, IP, version |
77
+ | `get_agent` | Get agent details by ID |
78
+ | `update_agent` | Update agent name or labels |
79
+ | `delete_agent` | Delete agent (disconnects connections) |
80
+ | `get_agent_connections` | Get connection history for an agent |
81
+
82
+ ### Tokens
83
+ | Tool | Description |
84
+ |------|-------------|
85
+ | `list_tokens` | List registration tokens (prefixes only) |
86
+ | `create_token` | Create new registration token |
87
+ | `revoke_token` | Revoke a token |
88
+
89
+ ### Groups
90
+ | Tool | Description |
91
+ |------|-------------|
92
+ | `list_groups` | List agent groups |
93
+ | `create_group` | Create a new group |
94
+ | `add_agent_to_group` | Add agent to group |
95
+ | `remove_agent_from_group` | Remove agent from group |
96
+
97
+ ### Connectivity
98
+ | Tool | Description |
99
+ |------|-------------|
100
+ | `get_discovery` | Get rendezvous server URL |
101
+ | `list_connections` | List P2P connections |
102
+ | `get_connection_stats` | Bandwidth and connection counts |
103
+
104
+ ### Webhooks (Ramps)
105
+ | Tool | Description |
106
+ |------|-------------|
107
+ | `list_destinations` | List webhook destinations |
108
+ | `create_destination` | Create webhook destination |
109
+ | `list_events` | List webhook events |
110
+
111
+ ### Resources
112
+
113
+ | URI | Description |
114
+ |-----|-------------|
115
+ | `anycast://agents` | All registered agents |
116
+ | `anycast://connections` | Recent connections (24h) |
117
+ | `anycast://stats` | Platform statistics |
118
+
119
+ ## Examples
120
+
121
+ Ask Claude:
122
+
123
+ > "List all my online agents"
124
+
125
+ > "Create a registration token called 'production' with a limit of 50 uses"
126
+
127
+ > "Show me the connection stats for the last week"
128
+
129
+ > "Create a webhook destination at https://api.example.com/webhooks"
130
+
131
+ > "Which agents are in the 'production' group?"
132
+
133
+ ## License
134
+
135
+ MIT
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Anycast Platform API client for the MCP server.
3
+ */
4
+ export interface AnycastConfig {
5
+ apiUrl: string;
6
+ apiKey: string;
7
+ }
8
+ export declare function loadConfig(): AnycastConfig;
9
+ export declare class AnycastClient {
10
+ private config;
11
+ constructor(config: AnycastConfig);
12
+ private request;
13
+ listAgents(params?: {
14
+ status?: string;
15
+ search?: string;
16
+ page?: number;
17
+ limit?: number;
18
+ }): Promise<unknown>;
19
+ getAgent(id: string): Promise<unknown>;
20
+ updateAgent(id: string, data: {
21
+ name?: string;
22
+ labels?: Record<string, string>;
23
+ }): Promise<unknown>;
24
+ deleteAgent(id: string): Promise<unknown>;
25
+ getAgentConnections(id: string, params?: {
26
+ status?: string;
27
+ page?: number;
28
+ }): Promise<unknown>;
29
+ listTokens(): Promise<unknown>;
30
+ createToken(data: {
31
+ name: string;
32
+ usageLimit?: number;
33
+ expiresAt?: string;
34
+ }): Promise<unknown>;
35
+ revokeToken(id: string): Promise<unknown>;
36
+ listGroups(): Promise<unknown>;
37
+ createGroup(data: {
38
+ name: string;
39
+ description?: string;
40
+ }): Promise<unknown>;
41
+ addAgentToGroup(groupId: string, agentId: string): Promise<unknown>;
42
+ removeAgentFromGroup(groupId: string, agentId: string): Promise<unknown>;
43
+ getDiscovery(): Promise<unknown>;
44
+ listConnections(params?: {
45
+ status?: string;
46
+ type?: string;
47
+ days?: number;
48
+ page?: number;
49
+ }): Promise<unknown>;
50
+ getStats(): Promise<unknown>;
51
+ listDestinations(): Promise<unknown>;
52
+ createDestination(data: {
53
+ name?: string;
54
+ url: string;
55
+ maxRetries?: number;
56
+ }): Promise<unknown>;
57
+ listEvents(params?: {
58
+ status?: string;
59
+ provider?: string;
60
+ page?: number;
61
+ limit?: number;
62
+ }): Promise<unknown>;
63
+ }
package/dist/client.js ADDED
@@ -0,0 +1,148 @@
1
+ "use strict";
2
+ /**
3
+ * Anycast Platform API client for the MCP server.
4
+ */
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.AnycastClient = void 0;
7
+ exports.loadConfig = loadConfig;
8
+ function loadConfig() {
9
+ const apiKey = process.env.ANYCAST_API_KEY || '';
10
+ const apiUrl = process.env.ANYCAST_API_URL || 'http://localhost:3002';
11
+ if (!apiKey) {
12
+ console.error('Warning: ANYCAST_API_KEY not set');
13
+ }
14
+ return { apiUrl, apiKey };
15
+ }
16
+ class AnycastClient {
17
+ config;
18
+ constructor(config) {
19
+ this.config = config;
20
+ }
21
+ async request(path, opts) {
22
+ const url = `${this.config.apiUrl}${path}`;
23
+ const res = await fetch(url, {
24
+ ...opts,
25
+ headers: {
26
+ 'Content-Type': 'application/json',
27
+ 'x-api-key': this.config.apiKey,
28
+ ...opts?.headers,
29
+ },
30
+ });
31
+ const text = await res.text();
32
+ let data;
33
+ try {
34
+ data = JSON.parse(text);
35
+ }
36
+ catch {
37
+ data = { raw: text };
38
+ }
39
+ if (!res.ok) {
40
+ const errMsg = data?.error || `HTTP ${res.status}`;
41
+ throw new Error(errMsg);
42
+ }
43
+ return data;
44
+ }
45
+ // Agents
46
+ async listAgents(params) {
47
+ const qs = new URLSearchParams();
48
+ if (params?.status)
49
+ qs.set('status', params.status);
50
+ if (params?.search)
51
+ qs.set('search', params.search);
52
+ if (params?.page)
53
+ qs.set('page', String(params.page));
54
+ if (params?.limit)
55
+ qs.set('limit', String(params.limit));
56
+ return this.request(`/api/agents?${qs}`);
57
+ }
58
+ async getAgent(id) {
59
+ return this.request(`/api/agents/${id}`);
60
+ }
61
+ async updateAgent(id, data) {
62
+ return this.request(`/api/agents/${id}`, { method: 'PATCH', body: JSON.stringify(data) });
63
+ }
64
+ async deleteAgent(id) {
65
+ return this.request(`/api/agents/${id}`, { method: 'DELETE' });
66
+ }
67
+ async getAgentConnections(id, params) {
68
+ const qs = new URLSearchParams();
69
+ if (params?.status)
70
+ qs.set('status', params.status);
71
+ if (params?.page)
72
+ qs.set('page', String(params.page));
73
+ return this.request(`/api/agents/${id}/connections?${qs}`);
74
+ }
75
+ // Tokens
76
+ async listTokens() {
77
+ return this.request('/api/agents/tokens');
78
+ }
79
+ async createToken(data) {
80
+ return this.request('/api/agents/tokens', { method: 'POST', body: JSON.stringify(data) });
81
+ }
82
+ async revokeToken(id) {
83
+ return this.request(`/api/agents/tokens/${id}`, { method: 'DELETE' });
84
+ }
85
+ // Groups
86
+ async listGroups() {
87
+ return this.request('/api/agents/groups');
88
+ }
89
+ async createGroup(data) {
90
+ return this.request('/api/agents/groups', { method: 'POST', body: JSON.stringify(data) });
91
+ }
92
+ async addAgentToGroup(groupId, agentId) {
93
+ return this.request(`/api/agents/groups/${groupId}/members`, {
94
+ method: 'POST',
95
+ body: JSON.stringify({ agentIds: [agentId] }),
96
+ });
97
+ }
98
+ async removeAgentFromGroup(groupId, agentId) {
99
+ return this.request(`/api/agents/groups/${groupId}/members`, {
100
+ method: 'DELETE',
101
+ body: JSON.stringify({ agentId }),
102
+ });
103
+ }
104
+ // Discovery
105
+ async getDiscovery() {
106
+ // Public endpoint, no auth needed
107
+ const url = `${this.config.apiUrl}/api/agents/discover`;
108
+ const res = await fetch(url);
109
+ return res.json();
110
+ }
111
+ // Connections
112
+ async listConnections(params) {
113
+ const qs = new URLSearchParams();
114
+ if (params?.status)
115
+ qs.set('status', params.status);
116
+ if (params?.type)
117
+ qs.set('type', params.type);
118
+ if (params?.days)
119
+ qs.set('days', String(params.days));
120
+ if (params?.page)
121
+ qs.set('page', String(params.page));
122
+ return this.request(`/api/agents/connections?${qs}`);
123
+ }
124
+ async getStats() {
125
+ return this.request('/api/agents/stats');
126
+ }
127
+ // Destinations (Ramps)
128
+ async listDestinations() {
129
+ return this.request('/api/destinations');
130
+ }
131
+ async createDestination(data) {
132
+ return this.request('/api/destinations', { method: 'POST', body: JSON.stringify(data) });
133
+ }
134
+ // Events
135
+ async listEvents(params) {
136
+ const qs = new URLSearchParams();
137
+ if (params?.status)
138
+ qs.set('status', params.status);
139
+ if (params?.provider)
140
+ qs.set('provider', params.provider);
141
+ if (params?.page)
142
+ qs.set('page', String(params.page));
143
+ if (params?.limit)
144
+ qs.set('limit', String(params.limit));
145
+ return this.request(`/api/events?${qs}`);
146
+ }
147
+ }
148
+ exports.AnycastClient = AnycastClient;
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * @anycast/mcp — MCP server for the Anycast Platform
4
+ *
5
+ * Enables AI assistants (Claude, Cursor, etc.) to manage agents,
6
+ * webhooks, and connectivity on the Anycast Edge Platform.
7
+ */
8
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,132 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ /**
4
+ * @anycast/mcp — MCP server for the Anycast Platform
5
+ *
6
+ * Enables AI assistants (Claude, Cursor, etc.) to manage agents,
7
+ * webhooks, and connectivity on the Anycast Edge Platform.
8
+ */
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ const mcp_js_1 = require("@modelcontextprotocol/sdk/server/mcp.js");
11
+ const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
12
+ const zod_1 = require("zod");
13
+ const client_js_1 = require("./client.js");
14
+ const config = (0, client_js_1.loadConfig)();
15
+ const client = new client_js_1.AnycastClient(config);
16
+ const server = new mcp_js_1.McpServer({
17
+ name: 'anycast',
18
+ version: '0.1.0',
19
+ });
20
+ // ---------------------------------------------------------------------------
21
+ // Tools — Agents
22
+ // ---------------------------------------------------------------------------
23
+ server.tool('list_agents', 'List all registered agents with their status, IP, and version', { status: zod_1.z.enum(['ONLINE', 'OFFLINE', 'CONNECTING']).optional().describe('Filter by agent status'), search: zod_1.z.string().optional().describe('Search by agent name'), limit: zod_1.z.number().optional().describe('Max results (default 20)') }, async ({ status, search, limit }) => {
24
+ const data = await client.listAgents({ status, search, limit });
25
+ return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
26
+ });
27
+ server.tool('get_agent', 'Get details for a specific agent including groups, connections, and labels', { id: zod_1.z.string().describe('Agent ID') }, async ({ id }) => {
28
+ const data = await client.getAgent(id);
29
+ return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
30
+ });
31
+ server.tool('update_agent', 'Update an agent name or labels', { id: zod_1.z.string().describe('Agent ID'), name: zod_1.z.string().optional().describe('New name'), labels: zod_1.z.record(zod_1.z.string()).optional().describe('Key-value labels') }, async ({ id, name, labels }) => {
32
+ const data = await client.updateAgent(id, { name, labels });
33
+ return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
34
+ });
35
+ server.tool('delete_agent', 'Delete an agent — disconnects active connections and removes from all groups', { id: zod_1.z.string().describe('Agent ID to delete') }, async ({ id }) => {
36
+ const data = await client.deleteAgent(id);
37
+ return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
38
+ });
39
+ server.tool('get_agent_connections', 'Get connection history for a specific agent', { id: zod_1.z.string().describe('Agent ID'), status: zod_1.z.enum(['PENDING', 'CONNECTED', 'DISCONNECTED', 'FAILED']).optional() }, async ({ id, status }) => {
40
+ const data = await client.getAgentConnections(id, { status });
41
+ return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
42
+ });
43
+ // ---------------------------------------------------------------------------
44
+ // Tools — Tokens
45
+ // ---------------------------------------------------------------------------
46
+ server.tool('list_tokens', 'List all agent registration tokens (prefixes only, not full tokens)', {}, async () => {
47
+ const data = await client.listTokens();
48
+ return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
49
+ });
50
+ server.tool('create_token', 'Create a new agent registration token — the full token is shown only once', { name: zod_1.z.string().describe('Token name'), usageLimit: zod_1.z.number().optional().describe('Max number of agents that can register with this token'), expiresAt: zod_1.z.string().optional().describe('ISO 8601 expiry date') }, async ({ name, usageLimit, expiresAt }) => {
51
+ const data = await client.createToken({ name, usageLimit, expiresAt });
52
+ return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
53
+ });
54
+ server.tool('revoke_token', 'Revoke an agent registration token — agents already registered are not affected', { id: zod_1.z.string().describe('Token ID to revoke') }, async ({ id }) => {
55
+ const data = await client.revokeToken(id);
56
+ return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
57
+ });
58
+ // ---------------------------------------------------------------------------
59
+ // Tools — Groups
60
+ // ---------------------------------------------------------------------------
61
+ server.tool('list_groups', 'List agent groups with member and policy counts', {}, async () => {
62
+ const data = await client.listGroups();
63
+ return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
64
+ });
65
+ server.tool('create_group', 'Create a new agent group for organizing agents and applying policies', { name: zod_1.z.string().describe('Group name'), description: zod_1.z.string().optional().describe('Group description') }, async ({ name, description }) => {
66
+ const data = await client.createGroup({ name, description });
67
+ return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
68
+ });
69
+ server.tool('add_agent_to_group', 'Add an agent to a group', { groupId: zod_1.z.string().describe('Group ID'), agentId: zod_1.z.string().describe('Agent ID to add') }, async ({ groupId, agentId }) => {
70
+ const data = await client.addAgentToGroup(groupId, agentId);
71
+ return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
72
+ });
73
+ server.tool('remove_agent_from_group', 'Remove an agent from a group', { groupId: zod_1.z.string().describe('Group ID'), agentId: zod_1.z.string().describe('Agent ID to remove') }, async ({ groupId, agentId }) => {
74
+ const data = await client.removeAgentFromGroup(groupId, agentId);
75
+ return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
76
+ });
77
+ // ---------------------------------------------------------------------------
78
+ // Tools — Connectivity
79
+ // ---------------------------------------------------------------------------
80
+ server.tool('get_discovery', 'Get the rendezvous server URL for agent connections (public, no auth needed)', {}, async () => {
81
+ const data = await client.getDiscovery();
82
+ return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
83
+ });
84
+ server.tool('list_connections', 'List P2P connections between agents', { status: zod_1.z.enum(['PENDING', 'CONNECTED', 'DISCONNECTED', 'FAILED']).optional(), type: zod_1.z.enum(['DIRECT', 'RELAYED']).optional(), days: zod_1.z.number().optional().describe('Lookback window in days (default 7)') }, async ({ status, type, days }) => {
85
+ const data = await client.listConnections({ status, type, days });
86
+ return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
87
+ });
88
+ server.tool('get_connection_stats', 'Get agent and connection statistics — online counts, bandwidth, totals', {}, async () => {
89
+ const data = await client.getStats();
90
+ return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
91
+ });
92
+ // ---------------------------------------------------------------------------
93
+ // Tools — Ramps (webhook delivery)
94
+ // ---------------------------------------------------------------------------
95
+ server.tool('list_destinations', 'List webhook delivery destinations with retry config and alert settings', {}, async () => {
96
+ const data = await client.listDestinations();
97
+ return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
98
+ });
99
+ server.tool('create_destination', 'Create a new webhook delivery destination', { url: zod_1.z.string().describe('Webhook endpoint URL'), name: zod_1.z.string().optional().describe('Display name'), maxRetries: zod_1.z.number().optional().describe('Max retry attempts (0-10)') }, async ({ url, name, maxRetries }) => {
100
+ const data = await client.createDestination({ url, name, maxRetries });
101
+ return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
102
+ });
103
+ server.tool('list_events', 'List webhook events with filtering by status, provider, and time', { status: zod_1.z.enum(['RECEIVED', 'PROCESSING', 'DELIVERED', 'FAILED', 'RETRYING', 'DEAD']).optional(), provider: zod_1.z.string().optional().describe('Filter by provider (moonpay, transak, etc.)'), limit: zod_1.z.number().optional().describe('Max results') }, async ({ status, provider, limit }) => {
104
+ const data = await client.listEvents({ status, provider, limit });
105
+ return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
106
+ });
107
+ // ---------------------------------------------------------------------------
108
+ // Resources
109
+ // ---------------------------------------------------------------------------
110
+ server.resource('agents', 'anycast://agents', async (uri) => {
111
+ const data = await client.listAgents({ limit: 100 });
112
+ return { contents: [{ uri: uri.href, mimeType: 'application/json', text: JSON.stringify(data, null, 2) }] };
113
+ });
114
+ server.resource('connections', 'anycast://connections', async (uri) => {
115
+ const data = await client.listConnections({ days: 1 });
116
+ return { contents: [{ uri: uri.href, mimeType: 'application/json', text: JSON.stringify(data, null, 2) }] };
117
+ });
118
+ server.resource('stats', 'anycast://stats', async (uri) => {
119
+ const data = await client.getStats();
120
+ return { contents: [{ uri: uri.href, mimeType: 'application/json', text: JSON.stringify(data, null, 2) }] };
121
+ });
122
+ // ---------------------------------------------------------------------------
123
+ // Start
124
+ // ---------------------------------------------------------------------------
125
+ async function main() {
126
+ const transport = new stdio_js_1.StdioServerTransport();
127
+ await server.connect(transport);
128
+ }
129
+ main().catch((err) => {
130
+ console.error('MCP server error:', err);
131
+ process.exit(1);
132
+ });
package/package.json ADDED
@@ -0,0 +1,29 @@
1
+ {
2
+ "name": "@anycast/mcp",
3
+ "version": "0.1.0",
4
+ "description": "MCP server for the Anycast Platform — enables AI assistants to manage agents, webhooks, and connectivity",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "bin": {
8
+ "anycast-mcp": "./dist/index.js"
9
+ },
10
+ "scripts": {
11
+ "build": "tsc",
12
+ "dev": "tsx watch src/index.ts",
13
+ "start": "node dist/index.js",
14
+ "test": "vitest run",
15
+ "clean": "rm -rf dist"
16
+ },
17
+ "dependencies": {
18
+ "@modelcontextprotocol/sdk": "^1.12.1"
19
+ },
20
+ "devDependencies": {
21
+ "@types/node": "^20.11.0",
22
+ "tsx": "^4.7.0",
23
+ "typescript": "^5.3.3",
24
+ "vitest": "^4.0.18"
25
+ },
26
+ "files": ["dist"],
27
+ "keywords": ["mcp", "anycast", "ai", "agents", "claude", "cursor"],
28
+ "license": "MIT"
29
+ }