From 40cdc51e70ee42c4d45afe44f652a76d8bfdab76 Mon Sep 17 00:00:00 2001 From: Ricardo Carneiro Date: Fri, 8 May 2026 22:58:33 -0300 Subject: [PATCH] feat(mcp): upgrade tools to v1.0.3 with outputSchema, annotations and EN descriptions - Add outputSchema to both generate_qr and generate_pix_qr tools - Add MCP annotations (title, readOnlyHint, idempotentHint, etc.) - Full English parameter descriptions for all inputs - Sync server-http.mjs (HTTP/Smithery endpoint) with index.mjs (stdio/npm) - Bump server version to 1.0.3 Co-Authored-By: Claude Sonnet 4.6 --- mcp-server/index.mjs | 212 ++++++++++++++++++++++++++-------- mcp-server/package-lock.json | 4 +- mcp-server/package.json | 29 ++++- mcp-server/server-http.mjs | 217 +++++++++++++++++++++++++++++------ 4 files changed, 373 insertions(+), 89 deletions(-) diff --git a/mcp-server/index.mjs b/mcp-server/index.mjs index 9720607..956612b 100644 --- a/mcp-server/index.mjs +++ b/mcp-server/index.mjs @@ -1,10 +1,11 @@ +#!/usr/bin/env node /** - * QR Rápido MCP Server (Node.js — test/dev) + * QR Rápido MCP Server (stdio transport) * - * Variáveis de ambiente: - * QR_API_KEY — sua API key (ex: qr_xxxx) - * QR_BASE_URL — base URL da API (default: https://qrrapido.site) - * NODE_TLS_REJECT_UNAUTHORIZED=0 — necessário para localhost com cert self-signed + * Environment variables: + * QR_API_KEY — your API key (e.g. qr_xxxx). Get one free at https://qrrapido.site/Developer + * QR_BASE_URL — API base URL (default: https://qrrapido.site) + * NODE_TLS_REJECT_UNAUTHORIZED=0 — set only for local dev with self-signed cert */ import { Server } from "@modelcontextprotocol/sdk/server/index.js"; @@ -19,7 +20,7 @@ import { exec } from "child_process"; const API_KEY = process.env.QR_API_KEY || ""; const BASE_URL = process.env.QR_BASE_URL || "https://qrrapido.site"; -if (!API_KEY) process.stderr.write("[qrrapido-mcp] AVISO: QR_API_KEY não definida\n"); +if (!API_KEY) process.stderr.write("[qrrapido-mcp] WARNING: QR_API_KEY not set\n"); // ── PIX EMV/BRCode builder (port do C# em PagamentoController) ──────────────── @@ -81,7 +82,7 @@ async function callApi(body) { } function errorContent(msg) { - return { content: [{ type: "text", text: `❌ ${msg}` }], isError: true }; + return { content: [{ type: "text", text: `Error: ${msg}` }], isError: true }; } function openImage(base64, label = "qr") { @@ -92,7 +93,7 @@ function openImage(base64, label = "qr") { exec(`start "" "${file}"`); return file; } catch (e) { - process.stderr.write(`[qrrapido-mcp] Não abriu imagem: ${e.message}\n`); + process.stderr.write(`[qrrapido-mcp] Could not open image: ${e.message}\n`); return null; } } @@ -105,13 +106,13 @@ function successContent(data, label = "qr") { { type: "text", text: [ - `✅ QR code gerado`, - file ? `🖼 Aberto em: ${file}` : "", - `⏱ ${data.generationTimeMs}ms`, - `💾 Cache: ${data.fromCache ? "hit" : "miss"}`, + `QR code generated successfully`, + file ? `Opened at: ${file}` : "", + `Generation time: ${data.generationTimeMs}ms`, + `Cache: ${data.fromCache ? "hit" : "miss"}`, data.monthlyQuotaLimit >= 0 - ? `📊 Quota mensal: ${data.monthlyQuotaRemaining}/${data.monthlyQuotaLimit}` - : `📊 Quota mensal: ilimitada` + ? `Monthly quota: ${data.monthlyQuotaRemaining}/${data.monthlyQuotaLimit}` + : `Monthly quota: unlimited` ].filter(Boolean).join("\n") } ] @@ -121,70 +122,181 @@ function successContent(data, label = "qr") { // ── Server ──────────────────────────────────────────────────────────────────── const server = new Server( - { name: "qrrapido", version: "0.2.0" }, + { + name: "qrrapido", + version: "1.0.3", + }, { capabilities: { tools: {} } } ); +const QR_OUTPUT_SCHEMA = { + type: "object", + properties: { + content: { + type: "array", + description: "Array with the QR code image (base64 PNG) and a text summary.", + items: { + oneOf: [ + { + type: "object", + description: "Base64-encoded QR code image.", + properties: { + type: { type: "string", const: "image" }, + data: { type: "string", description: "Base64-encoded image data." }, + mimeType: { type: "string", description: "MIME type, e.g. image/png." } + }, + required: ["type", "data", "mimeType"] + }, + { + type: "object", + description: "Text summary with timing, cache status, and quota info.", + properties: { + type: { type: "string", const: "text" }, + text: { type: "string" } + }, + required: ["type", "text"] + } + ] + } + } + }, + required: ["content"] +}; + // tools/list server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: [ { name: "generate_qr", description: - "Gera QR code para URL, Wi-Fi, vCard, WhatsApp, Email, SMS ou texto livre. " + - "Para PIX use generate_pix_qr.", + "Generate a QR code for a URL, Wi-Fi network, vCard contact, WhatsApp message, " + + "email, SMS, or free text. Returns an inline base64 PNG — no file hosting needed. " + + "For Brazilian PIX payments use generate_pix_qr instead.", + annotations: { + title: "Generate QR Code", + readOnlyHint: false, + destructiveHint: false, + idempotentHint: true, + openWorldHint: true + }, inputSchema: { type: "object", properties: { type: { type: "string", enum: ["url", "wifi", "vcard", "whatsapp", "email", "sms", "texto"], - description: "Tipo do QR code" + description: + "QR code type. Use 'url' for websites, 'wifi' for network credentials, " + + "'vcard' for contacts, 'whatsapp' to open a WhatsApp chat, " + + "'email' for pre-filled emails, 'sms' for text messages, 'texto' for free text." }, - content: { type: "string", description: "Conteúdo do QR code" }, - format: { type: "string", enum: ["png", "webp", "svg"], default: "png" }, - size: { type: "number", default: 400, description: "Tamanho em pixels (100–2000)" }, - primaryColor: { type: "string", default: "#000000", description: "Cor hex do QR" }, - backgroundColor: { type: "string", default: "#FFFFFF", description: "Cor hex de fundo" } + content: { + type: "string", + description: + "Content to encode. Examples by type — " + + "url: 'https://example.com'; " + + "wifi: 'WIFI:T:WPA;S:MyNet;P:pass123;;'; " + + "vcard: 'BEGIN:VCARD\\nFN:John\\nTEL:+1234\\nEND:VCARD'; " + + "whatsapp: '+5511999998888'; " + + "email: 'user@example.com'; " + + "sms: '+5511999998888'; " + + "texto: any plain text." + }, + format: { + type: "string", + enum: ["png", "webp", "svg"], + default: "png", + description: "Output image format. PNG is recommended for broadest compatibility." + }, + size: { + type: "number", + default: 400, + description: "Image size in pixels (width = height). Range: 100–2000. Default: 400." + }, + primaryColor: { + type: "string", + default: "#000000", + description: "QR module color as hex, e.g. '#1a1a1a'. Default: black (#000000)." + }, + backgroundColor: { + type: "string", + default: "#FFFFFF", + description: "Background color as hex, e.g. '#FFFFFF'. Default: white (#FFFFFF)." + } }, required: ["type", "content"] - } + }, + outputSchema: QR_OUTPUT_SCHEMA }, { name: "generate_pix_qr", description: - "Gera QR code PIX (BRCode/EMV) para recebimento de pagamento. " + - "Monte o payload automaticamente com chave, valor, nome e cidade.", + "Generate a Brazilian PIX payment QR code (BRCode/EMV standard). " + + "Builds the payload automatically from the PIX key, amount, recipient name, and city. " + + "Returns an inline base64 PNG ready to display or share.", + annotations: { + title: "Generate PIX QR Code", + readOnlyHint: false, + destructiveHint: false, + idempotentHint: true, + openWorldHint: true + }, inputSchema: { type: "object", properties: { pixKey: { type: "string", - description: "Chave PIX: CPF (somente dígitos), email, telefone (+5511...) ou chave aleatória" + description: + "PIX key of the recipient. Accepted formats: " + + "CPF (digits only, e.g. '12345678901'), " + + "email (e.g. 'user@example.com'), " + + "phone with country code (e.g. '+5511987654321'), " + + "or random key (UUID, e.g. '123e4567-e89b-12d3-a456-426614174000')." }, amount: { type: "number", - description: "Valor em reais (ex: 50.00). Omita para QR de valor aberto." + description: + "Payment amount in BRL (e.g. 49.90). " + + "Omit or set to 0 to create an open-value QR code where the payer enters the amount." }, merchantName: { type: "string", - description: "Nome do recebedor (max 25 chars, ex: RICARDO CARNEIRO)" + description: + "Recipient name as it appears in the payer's banking app. " + + "Max 25 characters. Accents are stripped automatically. Example: 'RICARDO CARNEIRO'." }, merchantCity: { type: "string", - description: "Cidade do recebedor (max 15 chars, ex: SAO PAULO)" + description: + "Recipient city. Max 15 characters. Accents are stripped automatically. " + + "Example: 'SAO PAULO'." }, txId: { type: "string", default: "***", - description: "ID da transação (opcional, max 25 chars)" + description: + "Optional transaction identifier for reconciliation. Max 25 characters. " + + "Use '***' (default) to omit. Example: 'PEDIDO123'." }, - size: { type: "number", default: 400, description: "Tamanho em pixels" }, - primaryColor: { type: "string", default: "#000000" }, - backgroundColor: { type: "string", default: "#FFFFFF" } + size: { + type: "number", + default: 400, + description: "Image size in pixels (width = height). Range: 100–2000. Default: 400." + }, + primaryColor: { + type: "string", + default: "#000000", + description: "QR module color as hex. Default: black (#000000)." + }, + backgroundColor: { + type: "string", + default: "#FFFFFF", + description: "Background color as hex. Default: white (#FFFFFF)." + } }, required: ["pixKey", "merchantName", "merchantCity"] - } + }, + outputSchema: QR_OUTPUT_SCHEMA } ] })); @@ -200,23 +312,23 @@ server.setRequestHandler(CallToolRequestSchema, async (req) => { size = 400, primaryColor = "#000000", backgroundColor = "#FFFFFF" } = args; if (!pixKey || !merchantName || !merchantCity) - return errorContent("pixKey, merchantName e merchantCity são obrigatórios."); + return errorContent("pixKey, merchantName and merchantCity are required."); let payload; try { payload = buildPixPayload({ pixKey, amount, merchantName, merchantCity, txId }); } catch (err) { - return errorContent(`Erro ao montar payload PIX: ${err.message}`); + return errorContent(`Failed to build PIX payload: ${err.message}`); } let data; try { data = await callApi({ type: "texto", content: payload, size, primaryColor, backgroundColor }); } catch (err) { - return errorContent(`Falha na requisição: ${err.message}`); + return errorContent(`API request failed: ${err.message}`); } - if (!data.success) return errorContent(data.message || data.error || "Erro desconhecido"); + if (!data.success) return errorContent(data.message || data.error || "Unknown error"); const pixFile = openImage(data.qrCodeBase64, "pix"); return { @@ -225,12 +337,12 @@ server.setRequestHandler(CallToolRequestSchema, async (req) => { { type: "text", text: [ - `✅ QR code PIX gerado`, - pixFile ? `🖼 Aberto em: ${pixFile}` : "", - `🔑 Chave: ${pixKey}`, - `💰 Valor: ${amount ? `R$ ${amount.toFixed(2)}` : "aberto"}`, - `👤 Recebedor: ${merchantName} — ${merchantCity}`, - `⏱ ${data.generationTimeMs}ms` + `PIX QR code generated successfully`, + pixFile ? `Opened at: ${pixFile}` : "", + `PIX key: ${pixKey}`, + `Amount: ${amount ? `BRL ${amount.toFixed(2)}` : "open (payer chooses)"}`, + `Recipient: ${merchantName} — ${merchantCity}`, + `Generation time: ${data.generationTimeMs}ms` ].filter(Boolean).join("\n") } ] @@ -242,24 +354,24 @@ server.setRequestHandler(CallToolRequestSchema, async (req) => { const { type, content, format = "png", size = 400, primaryColor = "#000000", backgroundColor = "#FFFFFF" } = args; - if (!type || !content) return errorContent("'type' e 'content' são obrigatórios."); + if (!type || !content) return errorContent("'type' and 'content' are required."); let data; try { data = await callApi({ type, content, outputFormat: format, size, primaryColor, backgroundColor }); } catch (err) { - return errorContent(`Falha na requisição: ${err.message}`); + return errorContent(`API request failed: ${err.message}`); } - if (!data.success) return errorContent(data.message || data.error || "Erro desconhecido"); + if (!data.success) return errorContent(data.message || data.error || "Unknown error"); return successContent(data); } - return errorContent(`Tool desconhecida: ${name}`); + return errorContent(`Unknown tool: ${name}`); }); // ── Start ───────────────────────────────────────────────────────────────────── const transport = new StdioServerTransport(); await server.connect(transport); -process.stderr.write(`[qrrapido-mcp] Servidor iniciado. Base URL: ${BASE_URL}\n`); +process.stderr.write(`[qrrapido-mcp] Server started. Base URL: ${BASE_URL}\n`); diff --git a/mcp-server/package-lock.json b/mcp-server/package-lock.json index 887eef6..d01de65 100644 --- a/mcp-server/package-lock.json +++ b/mcp-server/package-lock.json @@ -1,12 +1,12 @@ { "name": "qrrapido-mcp-server", - "version": "0.1.0", + "version": "1.0.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "qrrapido-mcp-server", - "version": "0.1.0", + "version": "1.0.1", "dependencies": { "@modelcontextprotocol/sdk": "^1.0.0", "undici": "^6.0.0" diff --git a/mcp-server/package.json b/mcp-server/package.json index 2fb16c5..51f23b7 100644 --- a/mcp-server/package.json +++ b/mcp-server/package.json @@ -1,17 +1,38 @@ { - "name": "qrrapido-mcp-server", - "version": "0.2.0", - "private": true, + "name": "qrrapido-mcp", + "mcpName": "site.qrrapido/qr-generator", + "version": "1.0.3", + "description": "MCP server for QR Rápido — generate QR codes for URLs, PIX, Wi-Fi, vCards, WhatsApp and more", "type": "module", "main": "index.mjs", + "bin": { + "qrrapido-mcp": "index.mjs" + }, + "files": [ + "index.mjs" + ], "scripts": { "start:stdio": "node index.mjs", "start:http": "node server-http.mjs", "start": "node server-http.mjs" }, + "keywords": [ + "mcp", + "qrcode", + "qr-code", + "pix", + "ai-agent", + "model-context-protocol" + ], + "author": "Ricardo Carneiro", + "license": "MIT", + "homepage": "https://qrrapido.site", + "repository": { + "type": "git", + "url": "https://github.com/modelcontextprotocol/registry" + }, "dependencies": { "@modelcontextprotocol/sdk": "^1.0.0", - "express": "^4.18.0", "undici": "^6.0.0" } } diff --git a/mcp-server/server-http.mjs b/mcp-server/server-http.mjs index cce07e9..17b6dfa 100644 --- a/mcp-server/server-http.mjs +++ b/mcp-server/server-http.mjs @@ -84,9 +84,43 @@ function errorContent(msg) { // ── MCP server factory (uma instância por sessão/request) ───────────────────── +const QR_OUTPUT_SCHEMA = { + type: "object", + properties: { + content: { + type: "array", + description: "Array with the QR code image (base64 PNG) and a text summary.", + items: { + oneOf: [ + { + type: "object", + description: "Base64-encoded QR code image.", + properties: { + type: { type: "string", const: "image" }, + data: { type: "string", description: "Base64-encoded image data." }, + mimeType: { type: "string", description: "MIME type, e.g. image/png." } + }, + required: ["type", "data", "mimeType"] + }, + { + type: "object", + description: "Text summary with timing, cache status, and quota info.", + properties: { + type: { type: "string", const: "text" }, + text: { type: "string" } + }, + required: ["type", "text"] + } + ] + } + } + }, + required: ["content"] +}; + function createMcpServer(apiKey) { const server = new Server( - { name: "qrrapido", version: "0.2.0" }, + { name: "qrrapido", version: "1.0.3" }, { capabilities: { tools: {} } } ); @@ -94,37 +128,135 @@ function createMcpServer(apiKey) { tools: [ { name: "generate_qr", - description: "Gera QR code para URL, Wi-Fi, vCard, WhatsApp, Email, SMS ou texto. Para PIX use generate_pix_qr.", + description: + "Generate a QR code for a URL, Wi-Fi network, vCard contact, WhatsApp message, " + + "email, SMS, or free text. Returns an inline base64 PNG — no file hosting needed. " + + "For Brazilian PIX payments use generate_pix_qr instead.", + annotations: { + title: "Generate QR Code", + readOnlyHint: false, + destructiveHint: false, + idempotentHint: true, + openWorldHint: true + }, inputSchema: { type: "object", properties: { - type: { type: "string", enum: ["url", "wifi", "vcard", "whatsapp", "email", "sms", "texto"] }, - content: { type: "string" }, - format: { type: "string", enum: ["png", "webp", "svg"], default: "png" }, - size: { type: "number", default: 400 }, - primaryColor: { type: "string", default: "#000000" }, - backgroundColor: { type: "string", default: "#FFFFFF" } + type: { + type: "string", + enum: ["url", "wifi", "vcard", "whatsapp", "email", "sms", "texto"], + description: + "QR code type. Use 'url' for websites, 'wifi' for network credentials, " + + "'vcard' for contacts, 'whatsapp' to open a WhatsApp chat, " + + "'email' for pre-filled emails, 'sms' for text messages, 'texto' for free text." + }, + content: { + type: "string", + description: + "Content to encode. Examples by type — " + + "url: 'https://example.com'; " + + "wifi: 'WIFI:T:WPA;S:MyNet;P:pass123;;'; " + + "vcard: 'BEGIN:VCARD\\nFN:John\\nTEL:+1234\\nEND:VCARD'; " + + "whatsapp: '+5511999998888'; " + + "email: 'user@example.com'; " + + "sms: '+5511999998888'; " + + "texto: any plain text." + }, + format: { + type: "string", + enum: ["png", "webp", "svg"], + default: "png", + description: "Output image format. PNG is recommended for broadest compatibility." + }, + size: { + type: "number", + default: 400, + description: "Image size in pixels (width = height). Range: 100–2000. Default: 400." + }, + primaryColor: { + type: "string", + default: "#000000", + description: "QR module color as hex, e.g. '#1a1a1a'. Default: black (#000000)." + }, + backgroundColor: { + type: "string", + default: "#FFFFFF", + description: "Background color as hex, e.g. '#FFFFFF'. Default: white (#FFFFFF)." + } }, required: ["type", "content"] - } + }, + outputSchema: QR_OUTPUT_SCHEMA }, { name: "generate_pix_qr", - description: "Gera QR code PIX (BRCode/EMV) com payload completo para recebimento.", + description: + "Generate a Brazilian PIX payment QR code (BRCode/EMV standard). " + + "Builds the payload automatically from the PIX key, amount, recipient name, and city. " + + "Returns an inline base64 PNG ready to display or share.", + annotations: { + title: "Generate PIX QR Code", + readOnlyHint: false, + destructiveHint: false, + idempotentHint: true, + openWorldHint: true + }, inputSchema: { type: "object", properties: { - pixKey: { type: "string", description: "Chave PIX: CPF, email, telefone ou aleatória" }, - amount: { type: "number", description: "Valor em reais (omita para valor aberto)" }, - merchantName: { type: "string", description: "Nome do recebedor (max 25 chars)" }, - merchantCity: { type: "string", description: "Cidade (max 15 chars)" }, - txId: { type: "string", default: "***" }, - size: { type: "number", default: 400 }, - primaryColor: { type: "string", default: "#000000" }, - backgroundColor: { type: "string", default: "#FFFFFF" } + pixKey: { + type: "string", + description: + "PIX key of the recipient. Accepted formats: " + + "CPF (digits only, e.g. '12345678901'), " + + "email (e.g. 'user@example.com'), " + + "phone with country code (e.g. '+5511987654321'), " + + "or random key (UUID, e.g. '123e4567-e89b-12d3-a456-426614174000')." + }, + amount: { + type: "number", + description: + "Payment amount in BRL (e.g. 49.90). " + + "Omit or set to 0 to create an open-value QR code where the payer enters the amount." + }, + merchantName: { + type: "string", + description: + "Recipient name as it appears in the payer's banking app. " + + "Max 25 characters. Accents are stripped automatically. Example: 'RICARDO CARNEIRO'." + }, + merchantCity: { + type: "string", + description: + "Recipient city. Max 15 characters. Accents are stripped automatically. " + + "Example: 'SAO PAULO'." + }, + txId: { + type: "string", + default: "***", + description: + "Optional transaction identifier for reconciliation. Max 25 characters. " + + "Use '***' (default) to omit. Example: 'PEDIDO123'." + }, + size: { + type: "number", + default: 400, + description: "Image size in pixels (width = height). Range: 100–2000. Default: 400." + }, + primaryColor: { + type: "string", + default: "#000000", + description: "QR module color as hex. Default: black (#000000)." + }, + backgroundColor: { + type: "string", + default: "#FFFFFF", + description: "Background color as hex. Default: white (#FFFFFF)." + } }, required: ["pixKey", "merchantName", "merchantCity"] - } + }, + outputSchema: QR_OUTPUT_SCHEMA } ] })); @@ -137,18 +269,27 @@ function createMcpServer(apiKey) { const { pixKey, amount, merchantName, merchantCity, txId = "***", size = 400, primaryColor = "#000000", backgroundColor = "#FFFFFF" } = args; if (!pixKey || !merchantName || !merchantCity) - return errorContent("pixKey, merchantName e merchantCity são obrigatórios."); + return errorContent("pixKey, merchantName and merchantCity are required."); let payload; try { payload = buildPixPayload({ pixKey, amount, merchantName, merchantCity, txId }); } - catch (e) { return errorContent(`Erro payload PIX: ${e.message}`); } + catch (e) { return errorContent(`Failed to build PIX payload: ${e.message}`); } let data; try { data = await callApi(apiKey, { type: "texto", content: payload, size, primaryColor, backgroundColor }); } - catch (e) { return errorContent(`Falha na requisição: ${e.message}`); } - if (!data.success) return errorContent(data.message || "Erro desconhecido"); + catch (e) { return errorContent(`API request failed: ${e.message}`); } + if (!data.success) return errorContent(data.message || "Unknown error"); return { content: [ { type: "image", data: data.qrCodeBase64, mimeType: data.mimeType || "image/png" }, - { type: "text", text: `✅ PIX gerado\n🔑 ${pixKey}\n💰 ${amount ? `R$ ${amount.toFixed(2)}` : "aberto"}\n👤 ${merchantName} — ${merchantCity}\n⏱ ${data.generationTimeMs}ms` } + { + type: "text", + text: [ + `PIX QR code generated successfully`, + `PIX key: ${pixKey}`, + `Amount: ${amount ? `BRL ${amount.toFixed(2)}` : "open (payer chooses)"}`, + `Recipient: ${merchantName} — ${merchantCity}`, + `Generation time: ${data.generationTimeMs}ms` + ].join("\n") + } ] }; } @@ -156,20 +297,30 @@ function createMcpServer(apiKey) { if (name === "generate_qr") { const { type, content, format = "png", size = 400, primaryColor = "#000000", backgroundColor = "#FFFFFF" } = args; - if (!type || !content) return errorContent("'type' e 'content' obrigatórios."); + if (!type || !content) return errorContent("'type' and 'content' are required."); let data; try { data = await callApi(apiKey, { type, content, outputFormat: format, size, primaryColor, backgroundColor }); } - catch (e) { return errorContent(`Falha: ${e.message}`); } - if (!data.success) return errorContent(data.message || "Erro desconhecido"); + catch (e) { return errorContent(`API request failed: ${e.message}`); } + if (!data.success) return errorContent(data.message || "Unknown error"); return { content: [ { type: "image", data: data.qrCodeBase64, mimeType: data.mimeType || "image/png" }, - { type: "text", text: `✅ QR gerado (${type})\n⏱ ${data.generationTimeMs}ms\n💾 cache: ${data.fromCache ? "hit" : "miss"}\n📊 quota: ${data.monthlyQuotaRemaining >= 0 ? `${data.monthlyQuotaRemaining}/${data.monthlyQuotaLimit}` : "ilimitada"}` } + { + type: "text", + text: [ + `QR code generated successfully`, + `Generation time: ${data.generationTimeMs}ms`, + `Cache: ${data.fromCache ? "hit" : "miss"}`, + data.monthlyQuotaLimit >= 0 + ? `Monthly quota: ${data.monthlyQuotaRemaining}/${data.monthlyQuotaLimit}` + : `Monthly quota: unlimited` + ].join("\n") + } ] }; } - return errorContent(`Tool desconhecida: ${name}`); + return errorContent(`Unknown tool: ${name}`); }); return server; @@ -180,8 +331,8 @@ function createMcpServer(apiKey) { const app = express(); app.use(express.json()); -// Health check (sem auth) -app.get("/health", (_, res) => res.json({ status: "ok", service: "qrrapido-mcp", baseUrl: BASE_URL })); +// Health check (no auth required) +app.get("/health", (_, res) => res.json({ status: "ok", service: "qrrapido-mcp", version: "1.0.3", baseUrl: BASE_URL })); // MCP endpoint — cada POST cria sessão stateless app.all("/mcp", async (req, res) => { @@ -213,7 +364,7 @@ app.all("/mcp", async (req, res) => { }); app.listen(PORT, () => { - console.log(`[qrrapido-mcp-http] Servidor HTTP na porta ${PORT}`); + console.log(`[qrrapido-mcp-http] HTTP server on port ${PORT}`); console.log(`[qrrapido-mcp-http] Base URL: ${BASE_URL}`); - console.log(`[qrrapido-mcp-http] Endpoint n8n: http://localhost:${PORT}/mcp`); + console.log(`[qrrapido-mcp-http] MCP endpoint: http://localhost:${PORT}/mcp`); });