#!/usr/bin/env node
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import {
CallToolRequestSchema,
ListToolsRequestSchema,
} from '@modelcontextprotocol/sdk/types.js';
import puppeteer from 'puppeteer';
const server = new Server(
{ name: 'keplaca-scraper', version: '1.0.0' },
{ capabilities: { tools: {} } }
);
server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [
{
name: 'scrape_plate_data',
description: 'Scrape vehicle data for a Brazilian plate from keplaca.com',
inputSchema: {
type: 'object',
properties: {
plate: {
type: 'string',
description: 'Plate number (e.g. NZQ4477)',
},
},
required: ['plate'],
},
},
],
}));
server.setRequestHandler(CallToolRequestSchema, async (req) => {
if (req.params.name !== 'scrape_plate_data') {
throw new Error('Unknown tool');
}
const plate = (req.params.arguments as { plate: string }).plate.toUpperCase();
const url = `https://www.keplaca.com/placa?placa-fipe=${plate}`;
const browser = await puppeteer.launch({
headless: true,
args: ['--no-sandbox', '--disable-setuid-sandbox'],
});
try {
const page = await browser.newPage();
await page.goto(url, { waitUntil: 'networkidle2', timeout: 30000 });
// Wait for the card that contains the data
await page.waitForSelector('.card-body', { timeout: 10000 });
const data = await page.evaluate(() => {
const rows = Array.from(document.querySelectorAll('.card-body .row'));
const map: Record<string, string> = {};
rows.forEach((row) => {
const label = row.querySelector('strong')?.textContent?.trim().replace(':', '');
const value = row.querySelector('span')?.textContent?.trim();
if (label && value) map[label] = value;
});
return {
marca: map['Marca'],
modelo: map['Modelo'],
ano: map['Ano'],
anoModelo: map['Ano Modelo'],
cor: map['Cor'],
cilindrada: map['Cilindrada'],
potencia: map['Potência'],
combustivel: map['Combustível'],
chassi: map['Chassi'],
motor: map['Motor'],
passageiros: map['Passageiros'],
uf: map['UF'],
municipio: map['Município'],
};
});
return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
} finally {
await browser.close();
}
});
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
}
main().catch(console.error);