@arcede/air-mcp
Advanced tools
+146
-14
| #!/usr/bin/env node | ||
| "use strict"; | ||
| var __create = Object.create; | ||
| var __defProp = Object.defineProperty; | ||
| var __getOwnPropDesc = Object.getOwnPropertyDescriptor; | ||
| var __getOwnPropNames = Object.getOwnPropertyNames; | ||
| var __getProtoOf = Object.getPrototypeOf; | ||
| var __hasOwnProp = Object.prototype.hasOwnProperty; | ||
| var __copyProps = (to, from, except, desc) => { | ||
| if (from && typeof from === "object" || typeof from === "function") { | ||
| for (let key of __getOwnPropNames(from)) | ||
| if (!__hasOwnProp.call(to, key) && key !== except) | ||
| __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); | ||
| } | ||
| return to; | ||
| }; | ||
| var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( | ||
| // If the importer is in node compatibility mode or this is not an ESM | ||
| // file that has been converted to a CommonJS file using a Babel- | ||
| // compatible transform (i.e. "__esModule" has not been set), then set | ||
| // "default" to the CommonJS "module.exports" for node compatibility. | ||
| isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, | ||
| mod | ||
| )); | ||
@@ -48,8 +70,12 @@ // src/server.ts | ||
| url: import_zod.z.string().url().describe("The URL to extract data from"), | ||
| force: import_zod.z.boolean().optional().describe("Bypass cache for fresh extraction (costs 1.0 credit vs 0.1 for cached)") | ||
| force: import_zod.z.boolean().optional().describe("Bypass cache for fresh extraction (costs 1.0 credit vs 0.1 for cached)"), | ||
| full_text: import_zod.z.boolean().optional().describe("Return the full page plaintext in content.text (default: false). Useful for document import pipelines.") | ||
| }, | ||
| async ({ url, force }) => { | ||
| async ({ url, force, full_text }) => { | ||
| const options = {}; | ||
| if (force) options.force = true; | ||
| if (full_text) options.fullText = true; | ||
| const result = await apiRequest("POST", "/v1/extract", { | ||
| url, | ||
| options: { force: force || false } | ||
| options | ||
| }); | ||
@@ -66,3 +92,3 @@ if (!result.success) { | ||
| const data = result.data; | ||
| const summary = [ | ||
| const lines = [ | ||
| `# ${data.title}`, | ||
@@ -79,7 +105,11 @@ data.description ? ` | ||
| Credits used: ${result.credits_used} | Remaining: ${result.credits_remaining}` | ||
| ].filter(Boolean).join("\n"); | ||
| return { | ||
| content: [ | ||
| { type: "text", text: summary }, | ||
| { type: "text", text: ` | ||
| ].filter(Boolean); | ||
| if (full_text && data.content?.text) { | ||
| lines.push("", "## Full Text", "", data.content.text); | ||
| } | ||
| const contentBlocks = [ | ||
| { type: "text", text: lines.join("\n") } | ||
| ]; | ||
| if (!full_text) { | ||
| contentBlocks.push({ type: "text", text: ` | ||
@@ -89,5 +119,5 @@ ## Raw Data | ||
| ${JSON.stringify(data, null, 2)} | ||
| \`\`\`` } | ||
| ] | ||
| }; | ||
| \`\`\`` }); | ||
| } | ||
| return { content: contentBlocks }; | ||
| } | ||
@@ -191,2 +221,103 @@ ); | ||
| ); | ||
| server.tool( | ||
| "extract_content", | ||
| "Extract text from a file (PDF, DOCX, PPTX, XLSX, CSV, TXT, MD, HTML, EML). Returns structured sections and full text. Any agent can use this \u2014 no browser needed. Use for document import: extract_content \u2192 research_import.", | ||
| { | ||
| file_url: import_zod.z.string().optional().describe("URL to fetch the file from (HTTP/HTTPS). Provide file_url or file_path, not both."), | ||
| file_path: import_zod.z.string().optional().describe("Local file path (for desktop agents). Provide file_url or file_path, not both."), | ||
| force_ocr: import_zod.z.boolean().optional().describe("Force OCR for scanned PDFs (default: false)") | ||
| }, | ||
| async ({ file_url, file_path, force_ocr }) => { | ||
| if (!file_url && !file_path) { | ||
| return { | ||
| content: [{ type: "text", text: "Error: Provide either file_url or file_path." }], | ||
| isError: true | ||
| }; | ||
| } | ||
| let fileBlob; | ||
| let fileName; | ||
| if (file_url) { | ||
| try { | ||
| const resp = await fetch(file_url, { signal: AbortSignal.timeout(3e4) }); | ||
| if (!resp.ok) { | ||
| return { | ||
| content: [{ type: "text", text: `Failed to fetch file: ${resp.status} ${resp.statusText}` }], | ||
| isError: true | ||
| }; | ||
| } | ||
| fileBlob = await resp.blob(); | ||
| fileName = new URL(file_url).pathname.split("/").pop() || "document"; | ||
| } catch (err) { | ||
| return { | ||
| content: [{ type: "text", text: `Failed to fetch file: ${err?.message || "Unknown error"}` }], | ||
| isError: true | ||
| }; | ||
| } | ||
| } else { | ||
| try { | ||
| const { promises: fsp } = await import("fs"); | ||
| const path = await import("path"); | ||
| const buffer = await fsp.readFile(file_path); | ||
| fileBlob = new Blob([buffer]); | ||
| fileName = path.basename(file_path); | ||
| } catch (err) { | ||
| return { | ||
| content: [{ type: "text", text: `Failed to read file: ${err?.message || "Unknown error"}` }], | ||
| isError: true | ||
| }; | ||
| } | ||
| } | ||
| const formData = new FormData(); | ||
| formData.append("file", fileBlob, fileName); | ||
| if (force_ocr) formData.append("force_ocr", "true"); | ||
| let result; | ||
| try { | ||
| const resp = await fetch(`${API_BASE}/v1/extract-content`, { | ||
| method: "POST", | ||
| headers: { "Authorization": `Bearer ${API_KEY}` }, | ||
| body: formData, | ||
| signal: AbortSignal.timeout(12e4) | ||
| }); | ||
| try { | ||
| result = await resp.json(); | ||
| } catch { | ||
| return { | ||
| content: [{ type: "text", text: `Extraction failed: API returned ${resp.status} with non-JSON response` }], | ||
| isError: true | ||
| }; | ||
| } | ||
| } catch (err) { | ||
| return { | ||
| content: [{ type: "text", text: `Extraction failed: ${err?.message || "Service unavailable"}` }], | ||
| isError: true | ||
| }; | ||
| } | ||
| if (!result.success) { | ||
| return { | ||
| content: [{ type: "text", text: `Extraction failed: ${result.error || result.message}` }], | ||
| isError: true | ||
| }; | ||
| } | ||
| const data = result.data; | ||
| const diag = data.diagnostics; | ||
| const lines = [ | ||
| `# ${data.title || fileName}`, | ||
| "", | ||
| `Source: ${file_url || file_path}`, | ||
| `Parser: ${diag?.extractionMethod || "unknown"}`, | ||
| `Confidence: ${diag?.confidenceScore ? (diag.confidenceScore * 100).toFixed(0) + "%" : "unknown"}`, | ||
| `Sections: ${diag?.itemsExtracted || 0}`, | ||
| `Time: ${diag?.extractionTimeMs || 0}ms` | ||
| ]; | ||
| if (data.metadata?.pageCount) lines.push(`Pages: ${data.metadata.pageCount}`); | ||
| if (data.metadata?.wordCount) lines.push(`Words: ${data.metadata.wordCount}`); | ||
| if (result.credits_used !== void 0) { | ||
| lines.push(`Credits: ${result.credits_used} used | ${result.credits_remaining ?? "?"} remaining`); | ||
| } | ||
| if (data.content?.text) { | ||
| lines.push("", "## Full Text", "", data.content.text); | ||
| } | ||
| return { content: [{ type: "text", text: lines.join("\n") }] }; | ||
| } | ||
| ); | ||
| server.resource( | ||
@@ -204,4 +335,5 @@ "api-status", | ||
| "Endpoints:", | ||
| " POST /v1/extract \u2014 Extract structured data from a URL (1.0 credits)", | ||
| " POST /v1/capabilities \u2014 Query site capabilities (0.25 credits)", | ||
| " POST /v1/extract \u2014 Extract structured data from a URL (1.0 credits)", | ||
| " POST /v1/extract-content \u2014 Extract text from files (PDF, DOCX, etc.)", | ||
| " POST /v1/capabilities \u2014 Query site capabilities (0.25 credits)", | ||
| "", | ||
@@ -208,0 +340,0 @@ "Pricing:", |
+26
-12
| { | ||
| "name": "@arcede/air-mcp", | ||
| "version": "0.1.0", | ||
| "version": "0.2.0", | ||
| "description": "MCP server for the Agent Internet Runtime — extract web data and query site capabilities from any AI coding tool", | ||
@@ -14,19 +14,33 @@ "bin": { | ||
| "dependencies": { | ||
| "@modelcontextprotocol/sdk": "^1.0.0", | ||
| "zod": "^3.23.0" | ||
| "@modelcontextprotocol/sdk": "^1.28.0", | ||
| "zod": "^3.25.76" | ||
| }, | ||
| "devDependencies": { | ||
| "tsup": "^8.0.0", | ||
| "tsx": "^4.7.0", | ||
| "typescript": "^5.3.0", | ||
| "@types/node": "^20.11.0" | ||
| "tsup": "^8.5.1", | ||
| "tsx": "^4.21.0", | ||
| "typescript": "^5.9.3", | ||
| "@types/node": "^20.19.37" | ||
| }, | ||
| "keywords": [ | ||
| "mcp", "model-context-protocol", "air", "extract", "capabilities", | ||
| "web-extraction", "ai-agent", "agent-internet-runtime", | ||
| "claude-code", "cursor", "windsurf" | ||
| "mcp", | ||
| "model-context-protocol", | ||
| "air", | ||
| "extract", | ||
| "capabilities", | ||
| "web-extraction", | ||
| "ai-agent", | ||
| "agent-internet-runtime", | ||
| "claude-code", | ||
| "cursor", | ||
| "windsurf" | ||
| ], | ||
| "license": "MIT", | ||
| "engines": { "node": ">=18" }, | ||
| "files": ["dist/", "README.md", "LICENSE"] | ||
| "engines": { | ||
| "node": ">=18" | ||
| }, | ||
| "files": [ | ||
| "dist/", | ||
| "README.md", | ||
| "LICENSE" | ||
| ] | ||
| } |
16710
51.22%357
58.67%3
200%Updated