mcp-emtrafesa 1.1.1 → 1.1.2
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.es.md +2 -2
- package/README.md +2 -2
- package/dist/src/index.js +122 -184
- package/package.json +2 -2
package/README.es.md
CHANGED
|
@@ -68,7 +68,7 @@ bun run index.ts
|
|
|
68
68
|
| `get-arrival-terminal` | Obtiene terminales de destino para un origen | `departureTerminalId` |
|
|
69
69
|
| `get-departure-schedules` | Obtiene horarios entre dos terminales | `departureTerminalId`, `arrivalTerminalId`, `date?` |
|
|
70
70
|
| `get-latest-purchased-tickets` | Busca tus boletos comprados | `DNI`, `email` |
|
|
71
|
-
| `
|
|
71
|
+
| `get-ticket-pdf` | Descarga tu boleto como archivo PDF | `ticketCode` |
|
|
72
72
|
| `get-frequently-asked-questions` | Obtiene preguntas frecuentes del servicio | Ninguno |
|
|
73
73
|
|
|
74
74
|
---
|
|
@@ -103,7 +103,7 @@ const tickets = await client.callTool("get-latest-purchased-tickets", {
|
|
|
103
103
|
### Descargar tu boleto en PDF
|
|
104
104
|
|
|
105
105
|
```typescript
|
|
106
|
-
const pdf = await client.callTool("
|
|
106
|
+
const pdf = await client.callTool("get-ticket-pdf", {
|
|
107
107
|
ticketCode: "BP01-123456",
|
|
108
108
|
});
|
|
109
109
|
// Devuelve un PDF codificado en base64 que puedes guardar o mostrar
|
package/README.md
CHANGED
|
@@ -68,7 +68,7 @@ bun run index.ts
|
|
|
68
68
|
| `get-arrival-terminal` | Get destination terminals for a given origin | `departureTerminalId` |
|
|
69
69
|
| `get-departure-schedules` | Get schedules between two terminals | `departureTerminalId`, `arrivalTerminalId`, `date?` |
|
|
70
70
|
| `get-latest-purchased-tickets` | Search your purchased tickets | `DNI`, `email` |
|
|
71
|
-
| `
|
|
71
|
+
| `get-ticket-pdf` | Download your ticket as a PDF file | `ticketCode` |
|
|
72
72
|
| `get-frequently-asked-questions` | Get FAQs about the service | None |
|
|
73
73
|
|
|
74
74
|
---
|
|
@@ -103,7 +103,7 @@ const tickets = await client.callTool("get-latest-purchased-tickets", {
|
|
|
103
103
|
### Download your ticket as PDF
|
|
104
104
|
|
|
105
105
|
```typescript
|
|
106
|
-
const pdf = await client.callTool("
|
|
106
|
+
const pdf = await client.callTool("get-ticket-pdf", {
|
|
107
107
|
ticketCode: "BP01-123456",
|
|
108
108
|
});
|
|
109
109
|
// Returns a base64-encoded PDF that can be saved or displayed
|
package/dist/src/index.js
CHANGED
|
@@ -31289,6 +31289,11 @@ var require_mime_type = __commonJS((exports, module) => {
|
|
|
31289
31289
|
};
|
|
31290
31290
|
});
|
|
31291
31291
|
|
|
31292
|
+
// src/index.ts
|
|
31293
|
+
import { readFileSync } from "node:fs";
|
|
31294
|
+
import { join } from "node:path";
|
|
31295
|
+
import { fileURLToPath } from "node:url";
|
|
31296
|
+
|
|
31292
31297
|
// node_modules/zod/v3/external.js
|
|
31293
31298
|
var exports_external = {};
|
|
31294
31299
|
__export(exports_external, {
|
|
@@ -57486,170 +57491,124 @@ var api3 = {
|
|
|
57486
57491
|
}
|
|
57487
57492
|
};
|
|
57488
57493
|
|
|
57489
|
-
// src/
|
|
57490
|
-
|
|
57491
|
-
|
|
57492
|
-
|
|
57493
|
-
|
|
57494
|
-
|
|
57495
|
-
|
|
57496
|
-
}
|
|
57497
|
-
async
|
|
57498
|
-
|
|
57499
|
-
|
|
57500
|
-
|
|
57501
|
-
|
|
57502
|
-
|
|
57503
|
-
|
|
57504
|
-
|
|
57505
|
-
|
|
57506
|
-
}
|
|
57507
|
-
|
|
57508
|
-
|
|
57509
|
-
|
|
57510
|
-
|
|
57511
|
-
|
|
57512
|
-
|
|
57513
|
-
|
|
57514
|
-
|
|
57515
|
-
|
|
57516
|
-
|
|
57517
|
-
|
|
57518
|
-
|
|
57519
|
-
|
|
57520
|
-
|
|
57521
|
-
|
|
57522
|
-
|
|
57523
|
-
|
|
57524
|
-
|
|
57525
|
-
|
|
57526
|
-
|
|
57527
|
-
|
|
57528
|
-
|
|
57529
|
-
|
|
57530
|
-
|
|
57531
|
-
|
|
57532
|
-
|
|
57533
|
-
|
|
57534
|
-
|
|
57535
|
-
|
|
57536
|
-
|
|
57537
|
-
|
|
57538
|
-
|
|
57539
|
-
|
|
57540
|
-
|
|
57541
|
-
|
|
57542
|
-
|
|
57543
|
-
|
|
57544
|
-
|
|
57545
|
-
|
|
57546
|
-
|
|
57547
|
-
|
|
57548
|
-
|
|
57549
|
-
|
|
57550
|
-
|
|
57551
|
-
|
|
57552
|
-
|
|
57553
|
-
|
|
57554
|
-
|
|
57555
|
-
|
|
57556
|
-
|
|
57557
|
-
const
|
|
57558
|
-
|
|
57559
|
-
const seats = $card.find(".text-muted.small span").toArray().map((el) => $2(el).text().trim()).filter((txt) => /^\d+$/.test(txt));
|
|
57560
|
-
const ticketsCodes = $card.find("p.text-truncate").text().trim().split("|").map((code) => code.trim());
|
|
57561
|
-
const price = $card.find("h4").first().text().trim();
|
|
57562
|
-
const operation = $card.find("h6.text-success").text().replace(/[^0-9]/g, "").trim();
|
|
57563
|
-
const [origin, destination] = $card.find("button.btn-sm").toArray().map((btn) => $2(btn).text().trim());
|
|
57564
|
-
return {
|
|
57565
|
-
dateTime: dateTimeRaw,
|
|
57566
|
-
seats,
|
|
57567
|
-
ticketsCodes,
|
|
57568
|
-
price,
|
|
57569
|
-
operationNumber: operation,
|
|
57570
|
-
origin,
|
|
57571
|
-
destination
|
|
57572
|
-
};
|
|
57573
|
-
}).get();
|
|
57574
|
-
return tickets;
|
|
57575
|
-
}
|
|
57576
|
-
async function downloadTicketPDF({
|
|
57577
|
-
tiketCode
|
|
57578
|
-
}) {
|
|
57579
|
-
const req = await fetch(`https://www.emtrafesa.pe/Home/ComprobanteDescarga?Boletos=3,BP01,${tiketCode}`, {
|
|
57580
|
-
headers: api3.headers
|
|
57581
|
-
});
|
|
57582
|
-
if (!req.ok) {
|
|
57583
|
-
throw new Error(`Failed to download ticket PDF: ${req.statusText}`);
|
|
57494
|
+
// src/infrastructure/http/emtrafesa-http.repository.ts
|
|
57495
|
+
class EmtrafesaHttpRepository {
|
|
57496
|
+
async getTerminals() {
|
|
57497
|
+
const req = await fetch("https://emtrafesa.pe/Home/GetSucursales", {
|
|
57498
|
+
headers: api3.headers
|
|
57499
|
+
});
|
|
57500
|
+
return await req.json();
|
|
57501
|
+
}
|
|
57502
|
+
async getFrequentlyAskedQuestions() {
|
|
57503
|
+
const req = await fetch("https://emtrafesa.pe/Home/GetPreguntasFrecuentes", {
|
|
57504
|
+
headers: api3.headers
|
|
57505
|
+
});
|
|
57506
|
+
return await req.json();
|
|
57507
|
+
}
|
|
57508
|
+
async getArrivalTerminalsByDepartureTerminal(params) {
|
|
57509
|
+
const req = await fetch(`https://emtrafesa.pe/Home/GetSucursalesDestino?origen=${params.departureTerminalId}`, { headers: api3.headers });
|
|
57510
|
+
return await req.json();
|
|
57511
|
+
}
|
|
57512
|
+
async getDepartureSchedules(params) {
|
|
57513
|
+
const formattedDate = params.date || new Intl.DateTimeFormat("es-PE", {
|
|
57514
|
+
timeZone: "America/Lima",
|
|
57515
|
+
day: "2-digit",
|
|
57516
|
+
month: "2-digit",
|
|
57517
|
+
year: "numeric"
|
|
57518
|
+
}).format(new Date);
|
|
57519
|
+
const req = await fetch("https://emtrafesa.pe/Home/GetItinerario", {
|
|
57520
|
+
method: "POST",
|
|
57521
|
+
headers: { ...api3.headers, "Content-Type": "application/json" },
|
|
57522
|
+
body: JSON.stringify({
|
|
57523
|
+
embarque_sucursal_id: params.departureTerminalId,
|
|
57524
|
+
desembarque_sucursal_id: params.arrivalTerminalId,
|
|
57525
|
+
embarque_fecha: formattedDate
|
|
57526
|
+
})
|
|
57527
|
+
});
|
|
57528
|
+
return await req.json();
|
|
57529
|
+
}
|
|
57530
|
+
async getLatestPurchaseTickets(params) {
|
|
57531
|
+
const body = new URLSearchParams;
|
|
57532
|
+
body.append("Dni", params.DNI);
|
|
57533
|
+
body.append("Correo", params.email);
|
|
57534
|
+
const req = await fetch("https://emtrafesa.pe/Consulta/PostConsulta", {
|
|
57535
|
+
method: "POST",
|
|
57536
|
+
headers: {
|
|
57537
|
+
...api3.headers,
|
|
57538
|
+
"Content-Type": "application/x-www-form-urlencoded"
|
|
57539
|
+
},
|
|
57540
|
+
body: body.toString()
|
|
57541
|
+
});
|
|
57542
|
+
const html3 = await req.text();
|
|
57543
|
+
const $2 = load(html3);
|
|
57544
|
+
return $2(".card-body").map((_, card) => {
|
|
57545
|
+
const $card = $2(card);
|
|
57546
|
+
return {
|
|
57547
|
+
dateTime: $card.find("h5").first().text().trim(),
|
|
57548
|
+
seats: $card.find(".text-muted.small span").toArray().map((el) => $2(el).text().trim()).filter((txt) => /^\d+$/.test(txt)),
|
|
57549
|
+
ticketsCodes: $card.find("p.text-truncate").text().trim().split("|").map((code) => code.trim()),
|
|
57550
|
+
price: $card.find("h4").first().text().trim(),
|
|
57551
|
+
operationNumber: $card.find("h6.text-success").text().replace(/[^0-9]/g, "").trim(),
|
|
57552
|
+
origin: $card.find("button.btn-sm").first().text().trim(),
|
|
57553
|
+
destination: $card.find("button.btn-sm").last().text().trim()
|
|
57554
|
+
};
|
|
57555
|
+
}).get();
|
|
57556
|
+
}
|
|
57557
|
+
async downloadTicketPDF(params) {
|
|
57558
|
+
const req = await fetch(`https://www.emtrafesa.pe/Home/ComprobanteDescarga?Boletos=3,BP01,${params.ticketCode}`, { headers: api3.headers });
|
|
57559
|
+
if (!req.ok) {
|
|
57560
|
+
throw new Error(`Failed to download ticket PDF: ${req.statusText}`);
|
|
57561
|
+
}
|
|
57562
|
+
const arrayBuffer = await req.arrayBuffer();
|
|
57563
|
+
return Buffer.from(arrayBuffer);
|
|
57584
57564
|
}
|
|
57585
|
-
const arrayBuffer = await req.arrayBuffer();
|
|
57586
|
-
return Buffer.from(arrayBuffer);
|
|
57587
57565
|
}
|
|
57588
57566
|
|
|
57589
|
-
// src/
|
|
57567
|
+
// src/shared/utils.ts
|
|
57590
57568
|
function bufferToBase64(buffer) {
|
|
57591
57569
|
return buffer.toString("base64");
|
|
57592
57570
|
}
|
|
57593
|
-
function isPdfBuffer(buffer) {
|
|
57594
|
-
if (buffer.length < 4)
|
|
57595
|
-
return false;
|
|
57596
|
-
const pdfHeader = buffer.subarray(0, 4).toString("ascii");
|
|
57597
|
-
return pdfHeader === "%PDF";
|
|
57598
|
-
}
|
|
57599
57571
|
|
|
57600
|
-
// src/
|
|
57601
|
-
|
|
57572
|
+
// src/infrastructure/mcp/tools.ts
|
|
57573
|
+
function errorResponse(error2) {
|
|
57574
|
+
return {
|
|
57575
|
+
content: [
|
|
57576
|
+
{
|
|
57577
|
+
type: "text",
|
|
57578
|
+
text: error2 instanceof Error ? error2.message : "unknown error"
|
|
57579
|
+
}
|
|
57580
|
+
]
|
|
57581
|
+
};
|
|
57582
|
+
}
|
|
57583
|
+
function registerTools(server, repository) {
|
|
57602
57584
|
server.tool("get-terminals", "Get terminals throughout the country", async () => {
|
|
57603
57585
|
try {
|
|
57604
|
-
const terminals = await getTerminals();
|
|
57605
|
-
return {
|
|
57606
|
-
content: [{ type: "text", text: JSON.stringify(terminals) }]
|
|
57607
|
-
};
|
|
57586
|
+
const terminals = await repository.getTerminals();
|
|
57587
|
+
return { content: [{ type: "text", text: JSON.stringify(terminals) }] };
|
|
57608
57588
|
} catch (error2) {
|
|
57609
|
-
return
|
|
57610
|
-
content: [
|
|
57611
|
-
{
|
|
57612
|
-
type: "text",
|
|
57613
|
-
text: JSON.stringify(error2 instanceof Error ? error2.message : "unknow error")
|
|
57614
|
-
}
|
|
57615
|
-
]
|
|
57616
|
-
};
|
|
57589
|
+
return errorResponse(error2);
|
|
57617
57590
|
}
|
|
57618
57591
|
});
|
|
57619
57592
|
server.tool("get-frequently-asked-questions", "Provides frequently asked questions about terminals, tickets, types of people, etc.", async () => {
|
|
57620
57593
|
try {
|
|
57621
|
-
const faq = await getFrequentlyAskedQuestions();
|
|
57622
|
-
return {
|
|
57623
|
-
content: [{ type: "text", text: JSON.stringify(faq) }]
|
|
57624
|
-
};
|
|
57594
|
+
const faq = await repository.getFrequentlyAskedQuestions();
|
|
57595
|
+
return { content: [{ type: "text", text: JSON.stringify(faq) }] };
|
|
57625
57596
|
} catch (error2) {
|
|
57626
|
-
return
|
|
57627
|
-
content: [
|
|
57628
|
-
{
|
|
57629
|
-
type: "text",
|
|
57630
|
-
text: JSON.stringify(error2 instanceof Error ? error2.message : "unknow error")
|
|
57631
|
-
}
|
|
57632
|
-
]
|
|
57633
|
-
};
|
|
57597
|
+
return errorResponse(error2);
|
|
57634
57598
|
}
|
|
57635
57599
|
});
|
|
57636
57600
|
server.tool("get-arrival-terminal", "Get arrival terminal for a departure terminal.", {
|
|
57637
57601
|
departureTerminalId: exports_external.string().describe("Departure terminal id (origin)")
|
|
57638
57602
|
}, async ({ departureTerminalId }) => {
|
|
57639
57603
|
try {
|
|
57640
|
-
const arrivalTerminals = await getArrivalTerminalsByDepartureTerminal({
|
|
57604
|
+
const arrivalTerminals = await repository.getArrivalTerminalsByDepartureTerminal({
|
|
57605
|
+
departureTerminalId
|
|
57606
|
+
});
|
|
57641
57607
|
return {
|
|
57642
57608
|
content: [{ type: "text", text: JSON.stringify(arrivalTerminals) }]
|
|
57643
57609
|
};
|
|
57644
57610
|
} catch (error2) {
|
|
57645
|
-
return
|
|
57646
|
-
content: [
|
|
57647
|
-
{
|
|
57648
|
-
type: "text",
|
|
57649
|
-
text: JSON.stringify(error2 instanceof Error ? error2.message : "unknown error")
|
|
57650
|
-
}
|
|
57651
|
-
]
|
|
57652
|
-
};
|
|
57611
|
+
return errorResponse(error2);
|
|
57653
57612
|
}
|
|
57654
57613
|
});
|
|
57655
57614
|
server.tool("get-departure-schedules", "Get departure schedules for a specific departure terminal.", {
|
|
@@ -57658,23 +57617,14 @@ async function registerTools(server) {
|
|
|
57658
57617
|
date: exports_external.string().optional().describe("Date in the format DD/MM/YYYY")
|
|
57659
57618
|
}, async ({ departureTerminalId, arrivalTerminalId, date: date4 }) => {
|
|
57660
57619
|
try {
|
|
57661
|
-
const schedules = await getDepartureSchedules({
|
|
57620
|
+
const schedules = await repository.getDepartureSchedules({
|
|
57662
57621
|
departureTerminalId,
|
|
57663
57622
|
arrivalTerminalId,
|
|
57664
57623
|
date: date4
|
|
57665
57624
|
});
|
|
57666
|
-
return {
|
|
57667
|
-
content: [{ type: "text", text: JSON.stringify(schedules) }]
|
|
57668
|
-
};
|
|
57625
|
+
return { content: [{ type: "text", text: JSON.stringify(schedules) }] };
|
|
57669
57626
|
} catch (error2) {
|
|
57670
|
-
return
|
|
57671
|
-
content: [
|
|
57672
|
-
{
|
|
57673
|
-
type: "text",
|
|
57674
|
-
text: JSON.stringify(error2 instanceof Error ? error2.message : "unknown error")
|
|
57675
|
-
}
|
|
57676
|
-
]
|
|
57677
|
-
};
|
|
57627
|
+
return errorResponse(error2);
|
|
57678
57628
|
}
|
|
57679
57629
|
});
|
|
57680
57630
|
server.tool("get-latest-purchased-tickets", "Get the latest purchased tickets for a specific departure terminal.", {
|
|
@@ -57682,61 +57632,49 @@ async function registerTools(server) {
|
|
|
57682
57632
|
email: exports_external.string().email().describe("Email of the user")
|
|
57683
57633
|
}, async ({ DNI, email: email2 }) => {
|
|
57684
57634
|
try {
|
|
57685
|
-
const tickets = await getLatestPurchaseTickets({
|
|
57686
|
-
|
|
57687
|
-
|
|
57688
|
-
};
|
|
57635
|
+
const tickets = await repository.getLatestPurchaseTickets({
|
|
57636
|
+
DNI,
|
|
57637
|
+
email: email2
|
|
57638
|
+
});
|
|
57639
|
+
return { content: [{ type: "text", text: JSON.stringify(tickets) }] };
|
|
57689
57640
|
} catch (error2) {
|
|
57690
|
-
return
|
|
57691
|
-
content: [
|
|
57692
|
-
{
|
|
57693
|
-
type: "text",
|
|
57694
|
-
text: JSON.stringify(error2 instanceof Error ? error2.message : "unknown error")
|
|
57695
|
-
}
|
|
57696
|
-
]
|
|
57697
|
-
};
|
|
57641
|
+
return errorResponse(error2);
|
|
57698
57642
|
}
|
|
57699
57643
|
});
|
|
57700
|
-
server.tool("
|
|
57701
|
-
|
|
57702
|
-
}, async ({
|
|
57644
|
+
server.tool("get-ticket-pdf", "Download and view a ticket PDF by its code. Returns a data URL that can be opened in browser.", {
|
|
57645
|
+
ticketCode: exports_external.string().describe("Ticket code")
|
|
57646
|
+
}, async ({ ticketCode }) => {
|
|
57703
57647
|
try {
|
|
57704
|
-
const pdfBuffer = await downloadTicketPDF({
|
|
57705
|
-
if (!isPdfBuffer(pdfBuffer)) {
|
|
57706
|
-
throw new Error("Received invalid PDF data from server");
|
|
57707
|
-
}
|
|
57648
|
+
const pdfBuffer = await repository.downloadTicketPDF({ ticketCode });
|
|
57708
57649
|
const base64Data = bufferToBase64(pdfBuffer);
|
|
57709
57650
|
return {
|
|
57710
57651
|
content: [
|
|
57711
57652
|
{
|
|
57712
57653
|
type: "resource",
|
|
57713
57654
|
resource: {
|
|
57714
|
-
uri: `
|
|
57715
|
-
|
|
57716
|
-
mimeType: "application/pdf"
|
|
57655
|
+
uri: `ticket://${ticketCode}/document.pdf`,
|
|
57656
|
+
name: `Ticket ${ticketCode}`,
|
|
57657
|
+
mimeType: "application/pdf",
|
|
57658
|
+
blob: base64Data
|
|
57717
57659
|
}
|
|
57718
57660
|
}
|
|
57719
57661
|
]
|
|
57720
57662
|
};
|
|
57721
57663
|
} catch (error2) {
|
|
57722
|
-
return
|
|
57723
|
-
content: [
|
|
57724
|
-
{
|
|
57725
|
-
type: "text",
|
|
57726
|
-
text: JSON.stringify(error2 instanceof Error ? error2.message : "unknown error")
|
|
57727
|
-
}
|
|
57728
|
-
]
|
|
57729
|
-
};
|
|
57664
|
+
return errorResponse(error2);
|
|
57730
57665
|
}
|
|
57731
57666
|
});
|
|
57732
57667
|
}
|
|
57733
57668
|
|
|
57734
57669
|
// src/index.ts
|
|
57670
|
+
var __dirname2 = fileURLToPath(new URL(".", import.meta.url));
|
|
57671
|
+
var packageJson = JSON.parse(readFileSync(join(__dirname2, "../package.json"), "utf-8"));
|
|
57735
57672
|
var server = new McpServer({
|
|
57736
|
-
name:
|
|
57737
|
-
version:
|
|
57673
|
+
name: packageJson.name,
|
|
57674
|
+
version: packageJson.version
|
|
57738
57675
|
});
|
|
57739
|
-
|
|
57676
|
+
var repository = new EmtrafesaHttpRepository;
|
|
57677
|
+
registerTools(server, repository);
|
|
57740
57678
|
async function main() {
|
|
57741
57679
|
const transport = new StdioServerTransport;
|
|
57742
57680
|
await server.connect(transport);
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mcp-emtrafesa",
|
|
3
3
|
"description": "A Model Context Protocol (MCP) server for accessing Emtrafesa bus transportation services in Peru",
|
|
4
|
-
"version": "1.1.
|
|
4
|
+
"version": "1.1.2",
|
|
5
5
|
"module": "src/index.ts",
|
|
6
6
|
"main": "dist/src/index.js",
|
|
7
7
|
"types": "dist/src/index.d.ts",
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
"dist/src/*.d.ts"
|
|
39
39
|
],
|
|
40
40
|
"scripts": {
|
|
41
|
-
"build": "bun build --target=node ./src/index.ts --
|
|
41
|
+
"build": "bun build --target=node ./src/index.ts --outdir=dist/src && bun run build:declaration",
|
|
42
42
|
"build:declaration": "tsc --emitDeclarationOnly --project tsconfig.types.json",
|
|
43
43
|
"format": "bunx biome check --write",
|
|
44
44
|
"prepare": "husky",
|