codify-mcp 0.2.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 +339 -0
- package/bin/start.js +19 -0
- package/lib/index.js +70 -0
- package/lib/mcp/actor_caller.js +55 -0
- package/lib/mcp/auth.js +33 -0
- package/lib/mcp/resolver.js +19 -0
- package/lib/mcp/server_setup.js +162 -0
- package/manifest.json +74 -0
- package/media/icons/play.png +0 -0
- package/package.json +59 -0
- package/server.json +24 -0
package/README.md
ADDED
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
# Codify MCP
|
|
2
|
+
|
|
3
|
+
**MCP server for custom automation tools using [Apify Agent](https://apify.com/cyberfly/apify-agent)**
|
|
4
|
+
|
|
5
|
+
> Part of [Project Codify](https://codify.codey.eu.org)
|
|
6
|
+
|
|
7
|
+
## Project Codify
|
|
8
|
+
- [Login](https://apify.com/cyberfly) - reusable authentication sessions - TBD
|
|
9
|
+
|
|
10
|
+
- [Agent](https://apify.com/cyberfly) - turn scripts into browser actions
|
|
11
|
+
- [Coder](https://apify.com/cyberfly) - turn browser actions into scripts (TBD - currently integrated)
|
|
12
|
+
- [Robot](https://apify.com/cyberfly) - automation engine for scalability (TBD - currently integrated)
|
|
13
|
+
|
|
14
|
+
More is on the way... Give it a [shot](https://codify.codey.eu.org) 🎬 or join the list to follow the project! 🔔
|
|
15
|
+
|
|
16
|
+
## What It Does
|
|
17
|
+
|
|
18
|
+
Model Context Protocol (MCP) server that lets AI assistants (e.g. Claude, Cursor, VS Code) execute browser automation tasks via Apify Agent. Tools are passed as clean JSON arguments — one per line. No manual setup or file creation.
|
|
19
|
+
|
|
20
|
+
## Usage
|
|
21
|
+
|
|
22
|
+
Run directly using `npx`:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
npx codify-mcp {{MCP_TOOL_JSON}}
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Or install locally:
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
npm install -g codify-mcp
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Quick Start
|
|
35
|
+
|
|
36
|
+
### 1. Apify Token
|
|
37
|
+
|
|
38
|
+
Optional - you can also place the token inside the MCP server JSON later.
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
apify login
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
This saves your token to `~/.apify/auth.json`.
|
|
45
|
+
|
|
46
|
+
Alternatively, set the environment variable:
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
export APIFY_TOKEN="your_token_here"
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### 2. Create a Tool
|
|
53
|
+
|
|
54
|
+
[Apify Coder](https://apify.com/cyberfly) can turn casual browser actions into reusable AI tools.
|
|
55
|
+
Currently, this feature is also integrated in [Apify Agent](https://apify.com/cyberfly/apify-agent)
|
|
56
|
+
|
|
57
|
+
Export a tool and append the result as a JSON string to the MCP server `args` field or ask your AI to do it for you.
|
|
58
|
+
|
|
59
|
+
```json
|
|
60
|
+
{
|
|
61
|
+
"name": "scrape_product",
|
|
62
|
+
"description": "Scrape product info from a page",
|
|
63
|
+
"inputSchema": {
|
|
64
|
+
"type": "object",
|
|
65
|
+
"properties": {
|
|
66
|
+
"url": {
|
|
67
|
+
"type": "string",
|
|
68
|
+
"description": "Product page URL"
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
"required": ["url"]
|
|
72
|
+
},
|
|
73
|
+
"implementation": {
|
|
74
|
+
"type": "apify-actor",
|
|
75
|
+
"actorId": "cyberfly/apify-agent",
|
|
76
|
+
"script": "await page.goto(inputs.url); const title = await page.textContent('h1'); return {title};"
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### 3. Run the Server
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
npx codify-mcp '{"name":"scrape_product","description":"...","inputSchema":{...},"implementation":{...}}'
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Or with multiple tools:
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
npx codify-mcp \
|
|
91
|
+
'{"name":"tool1",...}' \
|
|
92
|
+
'{"name":"tool2",...}' \
|
|
93
|
+
'{"name":"tool3",...}'
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### 4. Connect to Claude Desktop (or Cursor/VS Code)
|
|
97
|
+
|
|
98
|
+
Edit your Claude Desktop config:
|
|
99
|
+
|
|
100
|
+
**macOS/Linux:** `~/.config/Claude/claude_desktop_config.json`
|
|
101
|
+
|
|
102
|
+
**Windows:** `%APPDATA%\Claude\claude_desktop_config.json`
|
|
103
|
+
|
|
104
|
+
```json
|
|
105
|
+
{
|
|
106
|
+
"mcpServers": {
|
|
107
|
+
"apify-agent": {
|
|
108
|
+
"command": "npx",
|
|
109
|
+
"args": [
|
|
110
|
+
"codify-mcp",
|
|
111
|
+
"{\"name\":\"scrape_product\",\"description\":\"...\",\"inputSchema\":{...},\"implementation\":{...}}"
|
|
112
|
+
],
|
|
113
|
+
"env": {
|
|
114
|
+
"APIFY_TOKEN": "your_token_or_leave_empty_to_use_auth_file"
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
Restart Claude. Your tools are now available to the AI assistant.
|
|
122
|
+
|
|
123
|
+
## Tool Definition Reference
|
|
124
|
+
|
|
125
|
+
### Basic Structure
|
|
126
|
+
|
|
127
|
+
```javascript
|
|
128
|
+
{
|
|
129
|
+
// Required
|
|
130
|
+
"name": "tool_name", // alphanumeric + underscore/dash
|
|
131
|
+
"description": "What the tool does", // shown to AI
|
|
132
|
+
"inputSchema": {
|
|
133
|
+
"type": "object",
|
|
134
|
+
"properties": {
|
|
135
|
+
"paramName": {
|
|
136
|
+
"type": "string",
|
|
137
|
+
"description": "Parameter description"
|
|
138
|
+
}
|
|
139
|
+
},
|
|
140
|
+
"required": ["paramName"]
|
|
141
|
+
},
|
|
142
|
+
"implementation": {
|
|
143
|
+
"type": "apify-actor",
|
|
144
|
+
"actorId": "cyberfly/apify-agent", // actor to run
|
|
145
|
+
"script": "await page.goto(inputs.url); ..." // Playwright code
|
|
146
|
+
},
|
|
147
|
+
|
|
148
|
+
// Optional
|
|
149
|
+
"version": "1.0.0",
|
|
150
|
+
"metadata": { "custom": "fields" }
|
|
151
|
+
}
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
### Implementation Details
|
|
155
|
+
|
|
156
|
+
- **script**: Playwright automation code. Receives `inputs` object with user-provided parameters and `page` object for browser automation.
|
|
157
|
+
- **actorId**: Apify actor to execute. Defaults to `cyberfly/apify-agent`.
|
|
158
|
+
|
|
159
|
+
### Input Schema Examples
|
|
160
|
+
|
|
161
|
+
**Simple text input:**
|
|
162
|
+
```json
|
|
163
|
+
{
|
|
164
|
+
"url": {
|
|
165
|
+
"type": "string",
|
|
166
|
+
"description": "Website URL"
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
**Optional field:**
|
|
172
|
+
```json
|
|
173
|
+
{
|
|
174
|
+
"timeout": {
|
|
175
|
+
"type": "integer",
|
|
176
|
+
"description": "Timeout in seconds",
|
|
177
|
+
"default": 30
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
**Enum (dropdown):**
|
|
183
|
+
```json
|
|
184
|
+
{
|
|
185
|
+
"format": {
|
|
186
|
+
"type": "string",
|
|
187
|
+
"enum": ["json", "csv", "markdown"],
|
|
188
|
+
"description": "Output format"
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
## Usage Patterns
|
|
194
|
+
|
|
195
|
+
### Single Tool (Development)
|
|
196
|
+
|
|
197
|
+
```bash
|
|
198
|
+
npx codify-mcp '{"name":"test","description":"Test tool","inputSchema":{"type":"object","properties":{}},"implementation":{"type":"apify-actor","script":"console.log('hello')"}}'
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
### Multiple Tools (Production)
|
|
202
|
+
|
|
203
|
+
```bash
|
|
204
|
+
npx codify-mcp \
|
|
205
|
+
"$(cat tools/scraper.json)" \
|
|
206
|
+
"$(cat tools/logger.json)" \
|
|
207
|
+
"$(cat tools/analyzer.json)"
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
### With Environment Variable
|
|
211
|
+
|
|
212
|
+
```bash
|
|
213
|
+
APIFY_TOKEN="apk_..." npx codify-mcp '{"name":"...","description":"...","inputSchema":{},"implementation":{"type":"apify-actor","script":"..."}}'
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
### With npm link (Local Testing)
|
|
217
|
+
|
|
218
|
+
```bash
|
|
219
|
+
cd /path/to/codify-mcp
|
|
220
|
+
npm link
|
|
221
|
+
|
|
222
|
+
# Now use anywhere
|
|
223
|
+
codify-mcp '{"name":"...","description":"...","inputSchema":{},"implementation":{"type":"apify-actor","script":"..."}}'
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
## Authentication
|
|
227
|
+
|
|
228
|
+
Token resolution order:
|
|
229
|
+
|
|
230
|
+
1. **APIFY_TOKEN environment variable** (if set and not empty)
|
|
231
|
+
2. **~/.apify/auth.json** (from `apify login` CLI command)
|
|
232
|
+
3. **Error**: No token found, tool execution will fail with clear message
|
|
233
|
+
|
|
234
|
+
## Troubleshooting
|
|
235
|
+
|
|
236
|
+
### "No valid tools in arguments"
|
|
237
|
+
|
|
238
|
+
Ensure you're passing valid JSON strings as arguments:
|
|
239
|
+
|
|
240
|
+
```bash
|
|
241
|
+
# ✓ Correct
|
|
242
|
+
npx codify-mcp '{"name":"test","description":"Test","inputSchema":{"type":"object","properties":{}},"implementation":{"type":"apify-actor","script":"return {ok:true}"}}'
|
|
243
|
+
|
|
244
|
+
# ✗ Wrong (missing quotes around JSON)
|
|
245
|
+
npx codify-mcp {name:"test"...}
|
|
246
|
+
|
|
247
|
+
# ✗ Wrong (single quotes around JSON on Linux/Mac may need escaping)
|
|
248
|
+
npx codify-mcp '{name:"test"...}' # Use double quotes inside
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
### "Invalid or missing Apify token"
|
|
252
|
+
|
|
253
|
+
Ensure authentication is set up:
|
|
254
|
+
|
|
255
|
+
```bash
|
|
256
|
+
# Option 1: Login via CLI
|
|
257
|
+
apify login
|
|
258
|
+
|
|
259
|
+
# Option 2: Set environment variable
|
|
260
|
+
export APIFY_TOKEN="apk_your_token_here"
|
|
261
|
+
apify token
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
### "Tool execution failed"
|
|
265
|
+
|
|
266
|
+
Check your Playwright script syntax. The script must be valid JavaScript that:
|
|
267
|
+
|
|
268
|
+
- Has access to `inputs` (user-provided parameters)
|
|
269
|
+
- Has access to `page` (Playwright page object)
|
|
270
|
+
- Returns a value or object
|
|
271
|
+
|
|
272
|
+
```javascript
|
|
273
|
+
// ✓ Valid
|
|
274
|
+
await page.goto(inputs.url);
|
|
275
|
+
const title = await page.textContent('h1');
|
|
276
|
+
return { title };
|
|
277
|
+
|
|
278
|
+
// ✗ Invalid (missing await)
|
|
279
|
+
page.goto(inputs.url);
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
### Large Tool Sets (50+ tools)
|
|
283
|
+
|
|
284
|
+
If you have many tools, consider splitting into multiple MCP servers:
|
|
285
|
+
|
|
286
|
+
```json
|
|
287
|
+
{
|
|
288
|
+
"mcpServers": {
|
|
289
|
+
"apify-scraper": {
|
|
290
|
+
"command": "npx",
|
|
291
|
+
"args": ["codify-mcp", "...tool1...", "...tool2..."]
|
|
292
|
+
},
|
|
293
|
+
"apify-analyzer": {
|
|
294
|
+
"command": "npx",
|
|
295
|
+
"args": ["codify-mcp", "...tool3...", "...tool4..."]
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
## Development
|
|
302
|
+
|
|
303
|
+
### Running Locally
|
|
304
|
+
|
|
305
|
+
```bash
|
|
306
|
+
npm link
|
|
307
|
+
codify-mcp '{"name":"test",...}'
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
### Structure
|
|
311
|
+
|
|
312
|
+
```
|
|
313
|
+
lib/
|
|
314
|
+
index.js # Main entry: assembleWrapperCode(), start()
|
|
315
|
+
mcp/
|
|
316
|
+
resolver.js # Module path bootstrapping
|
|
317
|
+
auth.js # Token resolution
|
|
318
|
+
actor_caller.js # Apify actor execution
|
|
319
|
+
server_setup.js # MCP server + tool registration
|
|
320
|
+
|
|
321
|
+
bin/
|
|
322
|
+
start.js # Executable entry point (bin field in package.json)
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
## Key Design Principles
|
|
326
|
+
|
|
327
|
+
- **No files**: Tools passed entirely via argv; no config files or manual setup.
|
|
328
|
+
- **No base64**: Clean, readable command lines; no obfuscation.
|
|
329
|
+
- **Self-contained**: All dependencies bundled; works offline once installed.
|
|
330
|
+
- **Stateless**: Each invocation is independent; easy horizontal scaling.
|
|
331
|
+
- **Token from env/CLI**: Seamless auth experience; respects Apify ecosystem conventions.
|
|
332
|
+
|
|
333
|
+
## License
|
|
334
|
+
|
|
335
|
+
Apache-2.0
|
|
336
|
+
|
|
337
|
+
## Contributing
|
|
338
|
+
|
|
339
|
+
Issues and PRs welcome at [github.com/cybairfly/apify-agent](https://github.com/cybairfly/apify-agent)
|
package/bin/start.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* codify-mcp executable entry point
|
|
5
|
+
* Starts MCP server with tools provided as command-line arguments
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* codify-mcp '{"name":"tool1",...}'
|
|
9
|
+
*
|
|
10
|
+
* Environment:
|
|
11
|
+
* APIFY_TOKEN - Apify API token (optional, falls back to ~/.apify/auth.json)
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
const { start } = require('../lib/index.js');
|
|
15
|
+
|
|
16
|
+
start().catch(err => {
|
|
17
|
+
console.error('[Startup Error]', err.message);
|
|
18
|
+
process.exit(1);
|
|
19
|
+
});
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* codify-mcp
|
|
3
|
+
* Standalone MCP server library for executing Apify Agent tools
|
|
4
|
+
*
|
|
5
|
+
* Usage:
|
|
6
|
+
* npx codify-mcp '{"name":"tool1",...}' '{"name":"tool2",...}'
|
|
7
|
+
*
|
|
8
|
+
* Requires:
|
|
9
|
+
* - APIFY_TOKEN env var OR ~/.apify/auth.json
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const { getToken } = require('./mcp/auth');
|
|
13
|
+
const { fixModulePaths } = require('./mcp/resolver');
|
|
14
|
+
const { setupServer } = require('./mcp/server_setup');
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Parse tools from process.argv
|
|
18
|
+
* @returns {Array} Array of tool objects
|
|
19
|
+
*/
|
|
20
|
+
const parseToolsFromArgv = () => {
|
|
21
|
+
const tools = [];
|
|
22
|
+
const toolJsonArgs = process.argv.slice(2);
|
|
23
|
+
|
|
24
|
+
for (const arg of toolJsonArgs) {
|
|
25
|
+
try {
|
|
26
|
+
const tool = JSON.parse(arg);
|
|
27
|
+
tools.push(tool);
|
|
28
|
+
console.error('[MCP Parser] Loaded tool:', tool.name || 'unnamed');
|
|
29
|
+
} catch (e) {
|
|
30
|
+
console.error('[MCP Parser] Failed to parse tool arg:', e.message);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (tools.length === 0) {
|
|
35
|
+
console.error('[MCP Error] No valid tools in arguments. Expected JSON strings.');
|
|
36
|
+
process.exit(1);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return tools;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Start the MCP server with tools from argv
|
|
44
|
+
* Initializes everything and connects to MCP client via stdio
|
|
45
|
+
*/
|
|
46
|
+
const start = async () => {
|
|
47
|
+
try {
|
|
48
|
+
// 1. Fix module paths for npx environments
|
|
49
|
+
fixModulePaths();
|
|
50
|
+
|
|
51
|
+
// 2. Get Apify token
|
|
52
|
+
const apifyToken = getToken();
|
|
53
|
+
if (!apifyToken) {
|
|
54
|
+
console.error('[Auth Error] No Apify token found.');
|
|
55
|
+
console.error('Set APIFY_TOKEN env var or run: apify login');
|
|
56
|
+
process.exit(1);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// 3. Parse tools from command-line arguments
|
|
60
|
+
const tools = parseToolsFromArgv();
|
|
61
|
+
|
|
62
|
+
// 4. Setup and start MCP server
|
|
63
|
+
await setupServer({ tools, apifyToken });
|
|
64
|
+
} catch (e) {
|
|
65
|
+
console.error('[MCP Fatal Error]', e);
|
|
66
|
+
process.exit(1);
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
module.exports = { start, parseToolsFromArgv };
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Apify Actor Caller
|
|
3
|
+
* Executes actor and returns results
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const performActorCall = async ({ client, actorId, input, server }) => {
|
|
7
|
+
// Save original stderr.write
|
|
8
|
+
const originalStderrWrite = process.stderr.write;
|
|
9
|
+
|
|
10
|
+
// Override to intercept actor logs
|
|
11
|
+
process.stderr.write = (chunk, encoding, callback) => {
|
|
12
|
+
const str = chunk.toString();
|
|
13
|
+
|
|
14
|
+
if (str.includes('apify-agent runId:')) {
|
|
15
|
+
// Extract log part after ' -> ' (preserves all original formatting)
|
|
16
|
+
const match = str.match(/apify-agent runId:[^>]+> (.+)/);
|
|
17
|
+
if (match) {
|
|
18
|
+
const logLine = match[1];
|
|
19
|
+
// Send as MCP logging message with original formatting intact
|
|
20
|
+
if (server && server.sendLoggingMessage) {
|
|
21
|
+
server.sendLoggingMessage('info', logLine);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Don't write to stderr
|
|
26
|
+
if (callback) callback();
|
|
27
|
+
return true;
|
|
28
|
+
} else {
|
|
29
|
+
// Normal stderr output
|
|
30
|
+
return originalStderrWrite.call(process.stderr, chunk, encoding, callback);
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
try {
|
|
35
|
+
// Start the run
|
|
36
|
+
const run = await client.actor(actorId).call(input);
|
|
37
|
+
|
|
38
|
+
// Fetch results
|
|
39
|
+
const { items } = await client.dataset(run.defaultDatasetId).listItems();
|
|
40
|
+
|
|
41
|
+
return {
|
|
42
|
+
content: [
|
|
43
|
+
{
|
|
44
|
+
type: 'text',
|
|
45
|
+
text: JSON.stringify(items.length > 0 ? items : run, null, 2)
|
|
46
|
+
}
|
|
47
|
+
]
|
|
48
|
+
};
|
|
49
|
+
} finally {
|
|
50
|
+
// Restore stderr
|
|
51
|
+
process.stderr.write = originalStderrWrite;
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
module.exports = { performActorCall };
|
package/lib/mcp/auth.js
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Authentication Helper
|
|
3
|
+
* Returns the Apify token from env or ~/.apify/auth.json
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const fs = require('fs');
|
|
7
|
+
const os = require('os');
|
|
8
|
+
const path = require('path');
|
|
9
|
+
|
|
10
|
+
const getToken = () => {
|
|
11
|
+
// 1. Priority: Valid environment variable (not the placeholder)
|
|
12
|
+
if (process.env.APIFY_TOKEN &&
|
|
13
|
+
process.env.APIFY_TOKEN !== '{{Authorization}}' &&
|
|
14
|
+
process.env.APIFY_TOKEN !== 'YOUR_APIFY_TOKEN_OR_USE_ENV_VAR' &&
|
|
15
|
+
process.env.APIFY_TOKEN.trim() !== '') {
|
|
16
|
+
return process.env.APIFY_TOKEN;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// 2. Fallback: Apify CLI auth file
|
|
20
|
+
try {
|
|
21
|
+
const authPath = path.join(os.homedir(), '.apify', 'auth.json');
|
|
22
|
+
if (fs.existsSync(authPath)) {
|
|
23
|
+
const auth = JSON.parse(fs.readFileSync(authPath, 'utf8'));
|
|
24
|
+
if (auth.token) return auth.token;
|
|
25
|
+
}
|
|
26
|
+
} catch (e) {
|
|
27
|
+
// Silently ignore auth file read errors
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return null;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
module.exports = { getToken };
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Module Path Resolver
|
|
3
|
+
* Fixes NODE_PATH for npx environments
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const path = require('path');
|
|
7
|
+
const fs = require('fs');
|
|
8
|
+
|
|
9
|
+
const fixModulePaths = () => {
|
|
10
|
+
process.env.PATH.split(path.delimiter).forEach(d => {
|
|
11
|
+
const b = path.join(d, '../');
|
|
12
|
+
if (d.includes('node_modules') || (fs.existsSync(b) && fs.readdirSync(b).includes('node_modules'))) {
|
|
13
|
+
const nm = d.includes('node_modules') ? d.split('node_modules')[0] + 'node_modules' : path.join(d, '../node_modules');
|
|
14
|
+
if (!module.paths.includes(nm)) module.paths.push(nm);
|
|
15
|
+
}
|
|
16
|
+
});
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
module.exports = { fixModulePaths };
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP Server Setup and Connection
|
|
3
|
+
* Initializes the MCP server with tools and connects via stdio
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const { ActorsMcpServer } = require('@apify/actors-mcp-server');
|
|
7
|
+
const { StdioServerTransport } = require('@modelcontextprotocol/sdk/server/stdio.js');
|
|
8
|
+
const { ApifyClient } = require('apify-client');
|
|
9
|
+
const { randomUUID } = require('node:crypto');
|
|
10
|
+
const Ajv = require('ajv');
|
|
11
|
+
const { performActorCall } = require('./actor_caller');
|
|
12
|
+
|
|
13
|
+
const setupServer = async ({ tools, apifyToken }) => {
|
|
14
|
+
// Configure logging to stderr to avoid protocol corruption
|
|
15
|
+
try {
|
|
16
|
+
const log = require('@apify/log');
|
|
17
|
+
if (log && log.setLevel) log.setLevel(log.LEVELS.ERROR);
|
|
18
|
+
} catch (e) {
|
|
19
|
+
// Ignore if @apify/log is not found
|
|
20
|
+
}
|
|
21
|
+
console.log = (...args) => console.error(...args);
|
|
22
|
+
console.info = (...args) => console.error(...args);
|
|
23
|
+
|
|
24
|
+
// Create AJV instance for JSON Schema validation
|
|
25
|
+
const ajv = new Ajv({ allErrors: true, strict: false });
|
|
26
|
+
|
|
27
|
+
// Create MCP server
|
|
28
|
+
const server = new ActorsMcpServer({
|
|
29
|
+
transportType: 'stdio',
|
|
30
|
+
token: apifyToken,
|
|
31
|
+
telemetry: { enabled: false }
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
// Register all tools
|
|
35
|
+
if (!Array.isArray(tools) || tools.length === 0) {
|
|
36
|
+
console.error('[Server Error] No tools provided to server');
|
|
37
|
+
process.exit(1);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Reconstruct tools: Apify SDK expects tool objects with methods
|
|
41
|
+
const reconstructedTools = tools.map(toolData => {
|
|
42
|
+
const tool = {
|
|
43
|
+
name: toolData.name,
|
|
44
|
+
description: toolData.description,
|
|
45
|
+
inputSchema: toolData.inputSchema || { type: 'object', properties: {} },
|
|
46
|
+
type: 'internal',
|
|
47
|
+
|
|
48
|
+
// ajvValidate: required by Apify SDK for input validation
|
|
49
|
+
ajvValidate: (input) => {
|
|
50
|
+
if (!tool.inputSchema) {
|
|
51
|
+
// If no schema, accept any input
|
|
52
|
+
return { valid: true };
|
|
53
|
+
}
|
|
54
|
+
try {
|
|
55
|
+
const validate = ajv.compile(tool.inputSchema);
|
|
56
|
+
const valid = validate(input);
|
|
57
|
+
return {
|
|
58
|
+
valid,
|
|
59
|
+
errors: validate.errors
|
|
60
|
+
};
|
|
61
|
+
} catch (error) {
|
|
62
|
+
console.error('Schema compilation error:', error);
|
|
63
|
+
return {
|
|
64
|
+
valid: false,
|
|
65
|
+
errors: [{ message: `Schema compilation failed: ${error.message}` }]
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
|
|
70
|
+
// call: the actual tool handler
|
|
71
|
+
call: async (context) => {
|
|
72
|
+
// For tools with apify-actor implementation
|
|
73
|
+
if (toolData.implementation?.type === 'apify-actor') {
|
|
74
|
+
const client = new ApifyClient({ token: apifyToken });
|
|
75
|
+
const actorId = toolData.implementation.actorId || 'cyberfly/apify-agent';
|
|
76
|
+
const script = toolData.implementation.script;
|
|
77
|
+
|
|
78
|
+
if (!script) {
|
|
79
|
+
return {
|
|
80
|
+
content: [{
|
|
81
|
+
type: 'text',
|
|
82
|
+
text: 'Error: No script provided in tool implementation'
|
|
83
|
+
}],
|
|
84
|
+
isError: true
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const input = {
|
|
89
|
+
...context.args,
|
|
90
|
+
script: script
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
try {
|
|
94
|
+
return await performActorCall({ client, actorId, input, server });
|
|
95
|
+
} catch (e) {
|
|
96
|
+
console.error('[Tool Error]', e.message);
|
|
97
|
+
if (e.statusCode === 401 || (e.message && e.message.includes('token'))) {
|
|
98
|
+
return {
|
|
99
|
+
content: [{
|
|
100
|
+
type: 'text',
|
|
101
|
+
text: '[Authentication Required] Invalid or missing Apify token.'
|
|
102
|
+
}],
|
|
103
|
+
isError: true
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
return {
|
|
107
|
+
content: [{
|
|
108
|
+
type: 'text',
|
|
109
|
+
text: `Error: ${e.message}`
|
|
110
|
+
}],
|
|
111
|
+
isError: true
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Unsupported implementation type
|
|
117
|
+
return {
|
|
118
|
+
content: [{
|
|
119
|
+
type: 'text',
|
|
120
|
+
text: `Unsupported implementation type: ${toolData.implementation?.type}`
|
|
121
|
+
}],
|
|
122
|
+
isError: true
|
|
123
|
+
};
|
|
124
|
+
},
|
|
125
|
+
|
|
126
|
+
// Preserve optional fields
|
|
127
|
+
...(toolData.metadata && { metadata: toolData.metadata }),
|
|
128
|
+
...(toolData.version && { version: toolData.version }),
|
|
129
|
+
};
|
|
130
|
+
return tool;
|
|
131
|
+
});
|
|
132
|
+
server.upsertTools(reconstructedTools);
|
|
133
|
+
console.error(`[Server Setup] Registered ${reconstructedTools.length} tool(s)`);
|
|
134
|
+
|
|
135
|
+
// Connect server via stdio
|
|
136
|
+
try {
|
|
137
|
+
const transport = new StdioServerTransport();
|
|
138
|
+
const mcpSessionId = randomUUID();
|
|
139
|
+
|
|
140
|
+
// Wrap onmessage to inject mcpSessionId
|
|
141
|
+
const originalOnMessage = transport.onmessage;
|
|
142
|
+
transport.onmessage = (message) => {
|
|
143
|
+
if (message.method === 'initialize') {
|
|
144
|
+
server.options.initializeRequestData = message;
|
|
145
|
+
}
|
|
146
|
+
// Inject session ID into _meta
|
|
147
|
+
const params = message.params || {};
|
|
148
|
+
params._meta = params._meta || {};
|
|
149
|
+
params._meta.mcpSessionId = mcpSessionId;
|
|
150
|
+
message.params = params;
|
|
151
|
+
|
|
152
|
+
if (originalOnMessage) originalOnMessage(message);
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
await server.connect(transport);
|
|
156
|
+
} catch (e) {
|
|
157
|
+
console.error('[MCP Error]', e);
|
|
158
|
+
process.exit(1);
|
|
159
|
+
}
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
module.exports = { setupServer };
|
package/manifest.json
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
{
|
|
2
|
+
"manifest_version": "0.2",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"name": "codify-mcp",
|
|
5
|
+
"icon": "media/icons/play.png",
|
|
6
|
+
"display_name": "Codify MCP Server",
|
|
7
|
+
"description": "MCP server for custom automation tools using Apify Agent. Easily create reusable automations from casual browser actions.",
|
|
8
|
+
"author": "Apify & Community",
|
|
9
|
+
"license": "Apache-2.0",
|
|
10
|
+
"resources": {
|
|
11
|
+
"readme": "README.md"
|
|
12
|
+
},
|
|
13
|
+
"repository": {
|
|
14
|
+
"type": "git",
|
|
15
|
+
"url": "https://github.com/cybairfly/apify-agent"
|
|
16
|
+
},
|
|
17
|
+
"homepage": "https://codify.codey.eu.org",
|
|
18
|
+
"support": "https://github.com/cybairfly/apify-agent/issues",
|
|
19
|
+
"keywords": [
|
|
20
|
+
"ai",
|
|
21
|
+
"mcp",
|
|
22
|
+
"apify",
|
|
23
|
+
"agent",
|
|
24
|
+
"automation",
|
|
25
|
+
"claude",
|
|
26
|
+
"cursor",
|
|
27
|
+
"vscode",
|
|
28
|
+
"tools",
|
|
29
|
+
"model-context-protocol"
|
|
30
|
+
],
|
|
31
|
+
"server": {
|
|
32
|
+
"type": "node",
|
|
33
|
+
"entry_point": "bin/start.js",
|
|
34
|
+
"mcp_config": {
|
|
35
|
+
"command": "node",
|
|
36
|
+
"args": [
|
|
37
|
+
"${__dirname}/bin/start.js",
|
|
38
|
+
"${user_config.tools}"
|
|
39
|
+
],
|
|
40
|
+
"env": {
|
|
41
|
+
"APIFY_TOKEN": "${user_config.apify_token}"
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
"compatibility": {
|
|
46
|
+
"platforms": [
|
|
47
|
+
"darwin",
|
|
48
|
+
"linux",
|
|
49
|
+
"win32"
|
|
50
|
+
],
|
|
51
|
+
"node_version": ">=20.0.0",
|
|
52
|
+
"clients": [
|
|
53
|
+
"claude_desktop",
|
|
54
|
+
"cursor"
|
|
55
|
+
]
|
|
56
|
+
},
|
|
57
|
+
"user_config": {
|
|
58
|
+
"properties": {
|
|
59
|
+
"APIFY_TOKEN": {
|
|
60
|
+
"type": "string",
|
|
61
|
+
"description": "Apify API token for authentication",
|
|
62
|
+
"required": false,
|
|
63
|
+
"sensitive": true,
|
|
64
|
+
"masked": true
|
|
65
|
+
},
|
|
66
|
+
"tools": {
|
|
67
|
+
"type": "string",
|
|
68
|
+
"title": "Custom tools",
|
|
69
|
+
"description": "Export a tool and append the result as a JSON string to the MCP server `args` field or ask your AI to do it for you.",
|
|
70
|
+
"required": false
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
Binary file
|
package/package.json
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "codify-mcp",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "MCP server for custom automation tools using Apify Agent. Easily create reusable automations from casual browser actions.",
|
|
5
|
+
"main": "lib/index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"codify-mcp": "bin/start.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"bin/",
|
|
11
|
+
"lib/",
|
|
12
|
+
"media/",
|
|
13
|
+
"server.json",
|
|
14
|
+
"manifest.json",
|
|
15
|
+
"README.md"
|
|
16
|
+
],
|
|
17
|
+
"scripts": {
|
|
18
|
+
"start": "node bin/start.js"
|
|
19
|
+
},
|
|
20
|
+
"keywords": [
|
|
21
|
+
"ai",
|
|
22
|
+
"mcp",
|
|
23
|
+
"apify",
|
|
24
|
+
"agent",
|
|
25
|
+
"apify-agent",
|
|
26
|
+
"automation",
|
|
27
|
+
"claude",
|
|
28
|
+
"cursor",
|
|
29
|
+
"vscode",
|
|
30
|
+
"tools",
|
|
31
|
+
"model-context-protocol"
|
|
32
|
+
],
|
|
33
|
+
"author": "Apify & Community",
|
|
34
|
+
"license": "Apache-2.0",
|
|
35
|
+
"homepage": "https://codify.codey.eu.org",
|
|
36
|
+
"repository": {
|
|
37
|
+
"type": "git",
|
|
38
|
+
"url": "git+https://github.com/cybairfly/apify-agent.git",
|
|
39
|
+
"directory": "libraries/codify-mcp"
|
|
40
|
+
},
|
|
41
|
+
"engines": {
|
|
42
|
+
"node": ">=20.0.0"
|
|
43
|
+
},
|
|
44
|
+
"compatibility": {
|
|
45
|
+
"claude_desktop": ">=0.2.16",
|
|
46
|
+
"platforms": [
|
|
47
|
+
"darwin",
|
|
48
|
+
"win32",
|
|
49
|
+
"linux"
|
|
50
|
+
]
|
|
51
|
+
},
|
|
52
|
+
"dependencies": {
|
|
53
|
+
"@apify/actors-mcp-server": "^0.7.1",
|
|
54
|
+
"@modelcontextprotocol/sdk": "^0.7.0",
|
|
55
|
+
"apify-client": "^2.1.0",
|
|
56
|
+
"@apify/log": "^1.0.0",
|
|
57
|
+
"ajv": "^8.12.0"
|
|
58
|
+
}
|
|
59
|
+
}
|
package/server.json
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://raw.githubusercontent.com/modelcontextprotocol/modelcontextprotocol/main/schema/2025-11-25/schema.json",
|
|
3
|
+
"name": "com.apify/codify-mcp",
|
|
4
|
+
"version": "0.1.0",
|
|
5
|
+
"description": "Easily create reusable automation tools from casual browser actions using Apify Agent",
|
|
6
|
+
"transport": {
|
|
7
|
+
"type": "stdio",
|
|
8
|
+
"command": "codify-mcp",
|
|
9
|
+
"args": []
|
|
10
|
+
},
|
|
11
|
+
"authentication": {
|
|
12
|
+
"type": "environment",
|
|
13
|
+
"env_var": "APIFY_TOKEN"
|
|
14
|
+
},
|
|
15
|
+
"capabilities": {
|
|
16
|
+
"tools": true,
|
|
17
|
+
"prompts": false,
|
|
18
|
+
"resources": false
|
|
19
|
+
},
|
|
20
|
+
"implementation": {
|
|
21
|
+
"name": "Codify MCP Server",
|
|
22
|
+
"version": "0.1.0"
|
|
23
|
+
}
|
|
24
|
+
}
|