@atfilter/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 +104 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +340 -0
- package/package.json +49 -0
package/README.md
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# @Filter MCP Server
|
|
2
|
+
|
|
3
|
+
MCP (Model Context Protocol) server for the @Filter Signal Engine API. Connect to Claude Desktop, Cursor, VS Code, or any MCP-compatible AI client to query content filtering intelligence using natural language.
|
|
4
|
+
|
|
5
|
+
## Setup
|
|
6
|
+
|
|
7
|
+
### 1. Get your API key
|
|
8
|
+
|
|
9
|
+
Subscribe to the Professional plan at [atfilter.com/portal/pricing](https://atfilter.com/portal/pricing) and copy your API key from the account page.
|
|
10
|
+
|
|
11
|
+
### 2. Configure your AI client
|
|
12
|
+
|
|
13
|
+
#### Claude Desktop
|
|
14
|
+
|
|
15
|
+
Edit `%APPDATA%\Claude\claude_desktop_config.json` (Windows) or `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS):
|
|
16
|
+
|
|
17
|
+
```json
|
|
18
|
+
{
|
|
19
|
+
"mcpServers": {
|
|
20
|
+
"atfilter": {
|
|
21
|
+
"command": "node",
|
|
22
|
+
"args": ["C:/path/to/atfilter/mcp-server/dist/index.js"],
|
|
23
|
+
"env": {
|
|
24
|
+
"ATFILTER_API_KEY": "atf_your_api_key_here"
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
#### Cursor
|
|
32
|
+
|
|
33
|
+
Add to `.cursor/mcp.json` in your project root:
|
|
34
|
+
|
|
35
|
+
```json
|
|
36
|
+
{
|
|
37
|
+
"mcpServers": {
|
|
38
|
+
"atfilter": {
|
|
39
|
+
"command": "node",
|
|
40
|
+
"args": ["C:/path/to/atfilter/mcp-server/dist/index.js"],
|
|
41
|
+
"env": {
|
|
42
|
+
"ATFILTER_API_KEY": "atf_your_api_key_here"
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
#### VS Code
|
|
50
|
+
|
|
51
|
+
Add to `.vscode/mcp.json`:
|
|
52
|
+
|
|
53
|
+
```json
|
|
54
|
+
{
|
|
55
|
+
"servers": {
|
|
56
|
+
"atfilter": {
|
|
57
|
+
"type": "stdio",
|
|
58
|
+
"command": "node",
|
|
59
|
+
"args": ["C:/path/to/atfilter/mcp-server/dist/index.js"],
|
|
60
|
+
"env": {
|
|
61
|
+
"ATFILTER_API_KEY": "atf_your_api_key_here"
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Available Tools
|
|
69
|
+
|
|
70
|
+
| Tool | Description |
|
|
71
|
+
|---|---|
|
|
72
|
+
| `signals_top_keywords` | Get the most suppressed keywords |
|
|
73
|
+
| `signals_keyword_timeseries` | Track a keyword's activity over time |
|
|
74
|
+
| `signals_geo_heat` | Geographic distribution of filtering activity |
|
|
75
|
+
| `signals_domain_timeseries` | Track a domain's filtering activity over time |
|
|
76
|
+
| `signals_query` | Flexible multi-dimension query (filter + group by any combination) |
|
|
77
|
+
| `signals_trending` | Detect fastest-growing keywords, domains, platforms |
|
|
78
|
+
| `signals_keyword_activity` | See which keywords users are adding or removing from filter lists |
|
|
79
|
+
|
|
80
|
+
## Example Prompts
|
|
81
|
+
|
|
82
|
+
Once connected, you can ask your AI assistant things like:
|
|
83
|
+
|
|
84
|
+
- "What are the top 20 keywords being filtered right now?"
|
|
85
|
+
- "Show me the trend for 'immigration' over the past week"
|
|
86
|
+
- "Which domains have the most filtering activity in the US?"
|
|
87
|
+
- "What keywords are trending fastest in the last 24 hours?"
|
|
88
|
+
- "Compare filtering activity on social media vs news sites in Pennsylvania this month"
|
|
89
|
+
- "Show me a breakdown of suppression activity by country and platform for the last 30 days"
|
|
90
|
+
|
|
91
|
+
## Environment Variables
|
|
92
|
+
|
|
93
|
+
| Variable | Required | Default | Description |
|
|
94
|
+
|---|---|---|---|
|
|
95
|
+
| `ATFILTER_API_KEY` | Yes | — | Your @Filter API key |
|
|
96
|
+
| `ATFILTER_API_URL` | No | `https://atfilter.com` | API base URL (for development) |
|
|
97
|
+
|
|
98
|
+
## Development
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
npm install
|
|
102
|
+
npm run build
|
|
103
|
+
npm start
|
|
104
|
+
```
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,340 @@
|
|
|
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 { z } from "zod";
|
|
5
|
+
const API_URL = process.env.ATFILTER_API_URL ?? "https://atfilter.com";
|
|
6
|
+
const API_KEY = process.env.ATFILTER_API_KEY ?? "";
|
|
7
|
+
// ---------------------------------------------------------------------------
|
|
8
|
+
// API helpers
|
|
9
|
+
// ---------------------------------------------------------------------------
|
|
10
|
+
async function apiGet(path, params) {
|
|
11
|
+
const url = new URL(path, API_URL);
|
|
12
|
+
for (const [k, v] of Object.entries(params)) {
|
|
13
|
+
if (v)
|
|
14
|
+
url.searchParams.set(k, v);
|
|
15
|
+
}
|
|
16
|
+
const res = await fetch(url.toString(), {
|
|
17
|
+
headers: {
|
|
18
|
+
Authorization: `Bearer ${API_KEY}`,
|
|
19
|
+
Accept: "application/json",
|
|
20
|
+
},
|
|
21
|
+
});
|
|
22
|
+
if (!res.ok) {
|
|
23
|
+
const text = await res.text();
|
|
24
|
+
throw new Error(`API error ${res.status}: ${text}`);
|
|
25
|
+
}
|
|
26
|
+
return res.json();
|
|
27
|
+
}
|
|
28
|
+
async function apiPost(path, body) {
|
|
29
|
+
const url = new URL(path, API_URL);
|
|
30
|
+
const res = await fetch(url.toString(), {
|
|
31
|
+
method: "POST",
|
|
32
|
+
headers: {
|
|
33
|
+
Authorization: `Bearer ${API_KEY}`,
|
|
34
|
+
"Content-Type": "application/json",
|
|
35
|
+
Accept: "application/json",
|
|
36
|
+
},
|
|
37
|
+
body: JSON.stringify(body),
|
|
38
|
+
});
|
|
39
|
+
if (!res.ok) {
|
|
40
|
+
const text = await res.text();
|
|
41
|
+
throw new Error(`API error ${res.status}: ${text}`);
|
|
42
|
+
}
|
|
43
|
+
return res.json();
|
|
44
|
+
}
|
|
45
|
+
function textResult(data) {
|
|
46
|
+
return {
|
|
47
|
+
content: [
|
|
48
|
+
{
|
|
49
|
+
type: "text",
|
|
50
|
+
text: JSON.stringify(data, null, 2),
|
|
51
|
+
},
|
|
52
|
+
],
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
// ---------------------------------------------------------------------------
|
|
56
|
+
// Server
|
|
57
|
+
// ---------------------------------------------------------------------------
|
|
58
|
+
const server = new McpServer({
|
|
59
|
+
name: "atfilter-signals",
|
|
60
|
+
version: "1.0.0",
|
|
61
|
+
});
|
|
62
|
+
// --- Tool: getTopKeywords ---
|
|
63
|
+
server.tool("signals_top_keywords", "Get the most suppressed keywords by @Filter users. " +
|
|
64
|
+
"Use this to discover what topics people are actively filtering out of their feeds. " +
|
|
65
|
+
"Returns ranked keywords with suppression counts and estimated unique user counts.", {
|
|
66
|
+
window: z
|
|
67
|
+
.enum(["1h", "6h", "12h", "24h", "7d", "30d"])
|
|
68
|
+
.default("24h")
|
|
69
|
+
.describe("Time window to aggregate over"),
|
|
70
|
+
limit: z
|
|
71
|
+
.number()
|
|
72
|
+
.int()
|
|
73
|
+
.min(1)
|
|
74
|
+
.max(500)
|
|
75
|
+
.default(50)
|
|
76
|
+
.describe("Maximum number of keywords to return"),
|
|
77
|
+
}, async ({ window, limit }) => {
|
|
78
|
+
const data = await apiGet("/api/v1/signals/top-keywords", {
|
|
79
|
+
window,
|
|
80
|
+
limit: limit.toString(),
|
|
81
|
+
});
|
|
82
|
+
return textResult(data);
|
|
83
|
+
});
|
|
84
|
+
// --- Tool: getKeywordTimeSeries ---
|
|
85
|
+
server.tool("signals_keyword_timeseries", "Get a time series showing how suppression activity for a specific keyword changes over time. " +
|
|
86
|
+
"Use this to track whether a topic is rising or falling in filtering activity, " +
|
|
87
|
+
"spot spikes after news events, or compare activity levels across different time periods.", {
|
|
88
|
+
keyword: z.string().min(1).describe("The keyword to query (e.g. 'trump', 'crypto')"),
|
|
89
|
+
window: z
|
|
90
|
+
.enum(["24h", "7d", "30d", "90d"])
|
|
91
|
+
.default("7d")
|
|
92
|
+
.describe("Time window to cover"),
|
|
93
|
+
bucket: z
|
|
94
|
+
.enum(["hour", "day", "week"])
|
|
95
|
+
.default("hour")
|
|
96
|
+
.describe("Time bucket granularity"),
|
|
97
|
+
}, async ({ keyword, window, bucket }) => {
|
|
98
|
+
const data = await apiGet("/api/v1/signals/keyword-timeseries", {
|
|
99
|
+
keyword,
|
|
100
|
+
window,
|
|
101
|
+
bucket,
|
|
102
|
+
});
|
|
103
|
+
return textResult(data);
|
|
104
|
+
});
|
|
105
|
+
// --- Tool: getGeoHeat ---
|
|
106
|
+
server.tool("signals_geo_heat", "Get geographic distribution of content filtering activity. " +
|
|
107
|
+
"Shows where suppression activity is concentrated at country, region (state), county, or city level. " +
|
|
108
|
+
"Use this for geographic analysis of filtering behavior patterns.", {
|
|
109
|
+
window: z
|
|
110
|
+
.enum(["1h", "6h", "12h", "24h", "7d", "30d"])
|
|
111
|
+
.default("24h")
|
|
112
|
+
.describe("Time window to aggregate over"),
|
|
113
|
+
level: z
|
|
114
|
+
.enum(["country", "region", "county", "city"])
|
|
115
|
+
.default("country")
|
|
116
|
+
.describe("Geographic granularity level"),
|
|
117
|
+
}, async ({ window, level }) => {
|
|
118
|
+
const data = await apiGet("/api/v1/signals/geo-heat", {
|
|
119
|
+
window,
|
|
120
|
+
level,
|
|
121
|
+
});
|
|
122
|
+
return textResult(data);
|
|
123
|
+
});
|
|
124
|
+
// --- Tool: getDomainTimeSeries ---
|
|
125
|
+
server.tool("signals_domain_timeseries", "Get a time series of suppression activity for a specific website domain. " +
|
|
126
|
+
"Use this to track filtering activity on specific platforms like twitter.com, cnn.com, reddit.com, etc.", {
|
|
127
|
+
domain: z.string().min(1).describe("The domain to query (e.g. 'cnn.com', 'reddit.com')"),
|
|
128
|
+
window: z
|
|
129
|
+
.enum(["24h", "7d", "30d", "90d"])
|
|
130
|
+
.default("7d")
|
|
131
|
+
.describe("Time window to cover"),
|
|
132
|
+
bucket: z
|
|
133
|
+
.enum(["hour", "day", "week"])
|
|
134
|
+
.default("hour")
|
|
135
|
+
.describe("Time bucket granularity"),
|
|
136
|
+
}, async ({ domain, window, bucket }) => {
|
|
137
|
+
const data = await apiGet("/api/v1/signals/domain-timeseries", {
|
|
138
|
+
domain,
|
|
139
|
+
window,
|
|
140
|
+
bucket,
|
|
141
|
+
});
|
|
142
|
+
return textResult(data);
|
|
143
|
+
});
|
|
144
|
+
// --- Tool: query ---
|
|
145
|
+
server.tool("signals_query", "Flexible query endpoint that lets you filter and group by any combination of dimensions. " +
|
|
146
|
+
"This is the most powerful tool — use it when you need to combine multiple dimensions " +
|
|
147
|
+
"(e.g. 'top keywords on social media in Pennsylvania this month'). " +
|
|
148
|
+
"Dimensions: keyword, domain, platform, page_context, country, region, county, city, time. " +
|
|
149
|
+
"Always returns three measures: suppressions, unique_users, events.", {
|
|
150
|
+
window: z
|
|
151
|
+
.enum(["1h", "6h", "12h", "24h", "7d", "30d", "90d"])
|
|
152
|
+
.describe("Time window to aggregate over"),
|
|
153
|
+
group_by: z
|
|
154
|
+
.array(z.enum([
|
|
155
|
+
"keyword",
|
|
156
|
+
"domain",
|
|
157
|
+
"platform",
|
|
158
|
+
"page_context",
|
|
159
|
+
"country",
|
|
160
|
+
"region",
|
|
161
|
+
"county",
|
|
162
|
+
"city",
|
|
163
|
+
"time",
|
|
164
|
+
]))
|
|
165
|
+
.max(5)
|
|
166
|
+
.default([])
|
|
167
|
+
.describe("Dimensions to group by (max 5). Empty array returns grand totals. " +
|
|
168
|
+
"Include 'time' to get time-bucketed results (requires bucket parameter)."),
|
|
169
|
+
bucket: z
|
|
170
|
+
.enum(["hour", "day", "week"])
|
|
171
|
+
.optional()
|
|
172
|
+
.describe("Time bucket size. Required when group_by includes 'time'."),
|
|
173
|
+
keywords: z
|
|
174
|
+
.array(z.string())
|
|
175
|
+
.max(50)
|
|
176
|
+
.optional()
|
|
177
|
+
.describe("Filter to specific keywords"),
|
|
178
|
+
domains: z
|
|
179
|
+
.array(z.string())
|
|
180
|
+
.max(50)
|
|
181
|
+
.optional()
|
|
182
|
+
.describe("Filter to specific domains"),
|
|
183
|
+
platforms: z
|
|
184
|
+
.array(z.enum(["social", "news", "video", "other"]))
|
|
185
|
+
.optional()
|
|
186
|
+
.describe("Filter to specific platform types"),
|
|
187
|
+
page_contexts: z
|
|
188
|
+
.array(z.enum(["homepage", "feed", "search", "article", "other"]))
|
|
189
|
+
.optional()
|
|
190
|
+
.describe("Filter to specific page contexts"),
|
|
191
|
+
countries: z
|
|
192
|
+
.array(z.string())
|
|
193
|
+
.max(50)
|
|
194
|
+
.optional()
|
|
195
|
+
.describe("Filter to specific country codes (e.g. 'US', 'GB')"),
|
|
196
|
+
regions: z
|
|
197
|
+
.array(z.string())
|
|
198
|
+
.max(50)
|
|
199
|
+
.optional()
|
|
200
|
+
.describe("Filter to specific region codes (e.g. 'US-PA', 'US-CA')"),
|
|
201
|
+
counties: z
|
|
202
|
+
.array(z.string())
|
|
203
|
+
.max(50)
|
|
204
|
+
.optional()
|
|
205
|
+
.describe("Filter to specific county IDs"),
|
|
206
|
+
cities: z
|
|
207
|
+
.array(z.string())
|
|
208
|
+
.max(50)
|
|
209
|
+
.optional()
|
|
210
|
+
.describe("Filter to specific city IDs"),
|
|
211
|
+
order_by: z
|
|
212
|
+
.enum(["suppressions", "unique_users", "events"])
|
|
213
|
+
.default("suppressions")
|
|
214
|
+
.describe("Measure to sort results by"),
|
|
215
|
+
order: z
|
|
216
|
+
.enum(["asc", "desc"])
|
|
217
|
+
.default("desc")
|
|
218
|
+
.describe("Sort direction"),
|
|
219
|
+
limit: z
|
|
220
|
+
.number()
|
|
221
|
+
.int()
|
|
222
|
+
.min(1)
|
|
223
|
+
.max(1000)
|
|
224
|
+
.default(100)
|
|
225
|
+
.describe("Maximum number of results"),
|
|
226
|
+
}, async (params) => {
|
|
227
|
+
// Build the filters object from individual params
|
|
228
|
+
const filters = {};
|
|
229
|
+
if (params.keywords?.length)
|
|
230
|
+
filters.keywords = params.keywords;
|
|
231
|
+
if (params.domains?.length)
|
|
232
|
+
filters.domains = params.domains;
|
|
233
|
+
if (params.platforms?.length)
|
|
234
|
+
filters.platforms = params.platforms;
|
|
235
|
+
if (params.page_contexts?.length)
|
|
236
|
+
filters.page_contexts = params.page_contexts;
|
|
237
|
+
if (params.countries?.length)
|
|
238
|
+
filters.countries = params.countries;
|
|
239
|
+
if (params.regions?.length)
|
|
240
|
+
filters.regions = params.regions;
|
|
241
|
+
if (params.counties?.length)
|
|
242
|
+
filters.counties = params.counties;
|
|
243
|
+
if (params.cities?.length)
|
|
244
|
+
filters.cities = params.cities;
|
|
245
|
+
const body = {
|
|
246
|
+
window: params.window,
|
|
247
|
+
group_by: params.group_by,
|
|
248
|
+
order_by: params.order_by,
|
|
249
|
+
order: params.order,
|
|
250
|
+
limit: params.limit,
|
|
251
|
+
};
|
|
252
|
+
if (Object.keys(filters).length > 0)
|
|
253
|
+
body.filters = filters;
|
|
254
|
+
if (params.bucket)
|
|
255
|
+
body.bucket = params.bucket;
|
|
256
|
+
const data = await apiPost("/api/v1/signals/query", body);
|
|
257
|
+
return textResult(data);
|
|
258
|
+
});
|
|
259
|
+
// --- Tool: trending ---
|
|
260
|
+
server.tool("signals_trending", "Detect which keywords, domains, platforms, or page contexts are growing fastest. " +
|
|
261
|
+
"Compares recent activity to a baseline period to calculate growth percentage. " +
|
|
262
|
+
"Use this to find what's newly trending in content filtering — great for breaking news detection.", {
|
|
263
|
+
window: z
|
|
264
|
+
.enum(["24h", "7d", "30d"])
|
|
265
|
+
.default("24h")
|
|
266
|
+
.describe("Analysis window. 24h compares last 6h vs 6-24h ago. " +
|
|
267
|
+
"7d compares last 1d vs 1-7d ago. 30d compares last 7d vs 7-30d ago."),
|
|
268
|
+
dimension: z
|
|
269
|
+
.enum(["keyword", "domain", "platform", "page_context"])
|
|
270
|
+
.default("keyword")
|
|
271
|
+
.describe("What dimension to find trends in"),
|
|
272
|
+
metric: z
|
|
273
|
+
.enum(["suppressions", "events", "unique_users"])
|
|
274
|
+
.default("suppressions")
|
|
275
|
+
.describe("Which metric to measure growth of"),
|
|
276
|
+
limit: z
|
|
277
|
+
.number()
|
|
278
|
+
.int()
|
|
279
|
+
.min(1)
|
|
280
|
+
.max(500)
|
|
281
|
+
.default(50)
|
|
282
|
+
.describe("Maximum number of trending items to return"),
|
|
283
|
+
min_baseline: z
|
|
284
|
+
.number()
|
|
285
|
+
.int()
|
|
286
|
+
.min(1)
|
|
287
|
+
.default(10)
|
|
288
|
+
.describe("Minimum baseline value to include (filters noise from low-activity items)"),
|
|
289
|
+
}, async ({ window, dimension, metric, limit, min_baseline }) => {
|
|
290
|
+
const data = await apiGet("/api/v1/signals/trending", {
|
|
291
|
+
window,
|
|
292
|
+
dimension,
|
|
293
|
+
metric,
|
|
294
|
+
limit: limit.toString(),
|
|
295
|
+
min_baseline: min_baseline.toString(),
|
|
296
|
+
});
|
|
297
|
+
return textResult(data);
|
|
298
|
+
});
|
|
299
|
+
// --- Tool: keyword activity ---
|
|
300
|
+
server.tool("signals_keyword_activity", "Shows which keywords users are adding, removing, or editing in their filter lists. " +
|
|
301
|
+
"Reveals user intent — which topics are gaining or losing interest. " +
|
|
302
|
+
"The net_adds field (adds minus removes) indicates whether a keyword is growing or shrinking in popularity.", {
|
|
303
|
+
window: z
|
|
304
|
+
.enum(["1h", "6h", "12h", "24h", "7d", "30d"])
|
|
305
|
+
.default("24h")
|
|
306
|
+
.describe("Time window to aggregate over"),
|
|
307
|
+
type: z
|
|
308
|
+
.enum(["added", "removed", "edited", "all"])
|
|
309
|
+
.default("all")
|
|
310
|
+
.describe("Filter by event type, or 'all' for combined view"),
|
|
311
|
+
limit: z
|
|
312
|
+
.number()
|
|
313
|
+
.int()
|
|
314
|
+
.min(1)
|
|
315
|
+
.max(500)
|
|
316
|
+
.default(50)
|
|
317
|
+
.describe("Maximum number of keywords to return"),
|
|
318
|
+
}, async ({ window, type, limit }) => {
|
|
319
|
+
const data = await apiGet("/api/v1/signals/keyword-activity", {
|
|
320
|
+
window,
|
|
321
|
+
type,
|
|
322
|
+
limit: limit.toString(),
|
|
323
|
+
});
|
|
324
|
+
return textResult(data);
|
|
325
|
+
});
|
|
326
|
+
// ---------------------------------------------------------------------------
|
|
327
|
+
// Start
|
|
328
|
+
// ---------------------------------------------------------------------------
|
|
329
|
+
async function main() {
|
|
330
|
+
if (!API_KEY) {
|
|
331
|
+
console.error("[atfilter-mcp] Warning: ATFILTER_API_KEY is not set. API calls will fail with 401.");
|
|
332
|
+
}
|
|
333
|
+
const transport = new StdioServerTransport();
|
|
334
|
+
await server.connect(transport);
|
|
335
|
+
console.error("[atfilter-mcp] Server started on stdio");
|
|
336
|
+
}
|
|
337
|
+
main().catch((err) => {
|
|
338
|
+
console.error("[atfilter-mcp] Fatal error:", err);
|
|
339
|
+
process.exit(1);
|
|
340
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@atfilter/mcp-server",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "MCP server for @Filter Signal Engine API — query content filtering intelligence via Claude Desktop, Cursor, or VS Code",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"atfilter-mcp": "dist/index.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"dist",
|
|
12
|
+
"README.md"
|
|
13
|
+
],
|
|
14
|
+
"scripts": {
|
|
15
|
+
"build": "tsc",
|
|
16
|
+
"start": "node dist/index.js",
|
|
17
|
+
"dev": "tsc --watch",
|
|
18
|
+
"prepublishOnly": "tsc"
|
|
19
|
+
},
|
|
20
|
+
"keywords": [
|
|
21
|
+
"mcp",
|
|
22
|
+
"model-context-protocol",
|
|
23
|
+
"atfilter",
|
|
24
|
+
"content-filtering",
|
|
25
|
+
"signal-api",
|
|
26
|
+
"claude",
|
|
27
|
+
"cursor",
|
|
28
|
+
"vscode",
|
|
29
|
+
"ai-tools"
|
|
30
|
+
],
|
|
31
|
+
"repository": {
|
|
32
|
+
"type": "git",
|
|
33
|
+
"url": "https://github.com/netcems2-cell/atfilter-ext.git",
|
|
34
|
+
"directory": "mcp-server"
|
|
35
|
+
},
|
|
36
|
+
"homepage": "https://atfilter.com/portal/docs",
|
|
37
|
+
"license": "MIT",
|
|
38
|
+
"engines": {
|
|
39
|
+
"node": ">=18"
|
|
40
|
+
},
|
|
41
|
+
"dependencies": {
|
|
42
|
+
"@modelcontextprotocol/sdk": "^1.26.0",
|
|
43
|
+
"zod": "^4.3.6"
|
|
44
|
+
},
|
|
45
|
+
"devDependencies": {
|
|
46
|
+
"@types/node": "^20",
|
|
47
|
+
"typescript": "^5"
|
|
48
|
+
}
|
|
49
|
+
}
|