feat(mcp): upgrade tools to v1.0.3 with outputSchema, annotations and EN descriptions
All checks were successful
Deploy QR Rapido / test (push) Successful in 1m4s
Deploy QR Rapido / build-and-push (push) Successful in 18m56s
Deploy QR Rapido / deploy-staging (push) Has been skipped
Deploy QR Rapido / deploy-production (push) Successful in 3m28s

- 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 <noreply@anthropic.com>
This commit is contained in:
Ricardo Carneiro 2026-05-08 22:58:33 -03:00
parent 9161977f85
commit 40cdc51e70
4 changed files with 373 additions and 89 deletions

View File

@ -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 (1002000)" },
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: 1002000. 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: 1002000. 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`);

View File

@ -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"

View File

@ -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"
}
}

View File

@ -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: 1002000. 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: 1002000. 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`);
});