@iflow-mcp/faaak2-db-mcp 1.0.2 → 1.0.3

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.
@@ -1,4 +1,3 @@
1
1
  import { createClient } from "db-vendo-client";
2
2
  import { profile as dbProfile } from "db-vendo-client/p/db/index.js";
3
-
4
3
  export const client = createClient(dbProfile, "db-mcp-server");
package/build/index.js ADDED
@@ -0,0 +1,108 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { instructions } from "./instructions.js";
3
+ import { registerFindStation } from "./tools/find-station.js";
4
+ import { registerGetDepartures } from "./tools/get-departures.js";
5
+ import { registerFindTrip } from "./tools/find-trip.js";
6
+ import { registerFindJourneys } from "./tools/find-journeys.js";
7
+ function createServer() {
8
+ const server = new McpServer({ name: "db-mcp", version: "1.0.0" }, { instructions });
9
+ registerFindStation(server);
10
+ registerGetDepartures(server);
11
+ registerFindTrip(server);
12
+ registerFindJourneys(server);
13
+ return server;
14
+ }
15
+ if (process.env.PORT) {
16
+ // Remote: Streamable HTTP transport
17
+ const { createServer: createHttpServer } = await import("node:http");
18
+ const { StreamableHTTPServerTransport } = await import("@modelcontextprotocol/sdk/server/streamableHttp.js");
19
+ const PORT = parseInt(process.env.PORT, 10);
20
+ const sessions = new Map();
21
+ // Session timeout: clean up sessions after 30 min
22
+ const SESSION_TIMEOUT_MS = 30 * 60 * 1000;
23
+ setInterval(() => {
24
+ const now = Date.now();
25
+ for (const [id, session] of sessions) {
26
+ if (now - session.createdAt > SESSION_TIMEOUT_MS) {
27
+ session.transport.close?.();
28
+ sessions.delete(id);
29
+ console.log(`Session expired: ${id} (${sessions.size} active)`);
30
+ }
31
+ }
32
+ }, 60_000);
33
+ const httpServer = createHttpServer(async (req, res) => {
34
+ const url = new URL(req.url || "/", `http://localhost:${PORT}`);
35
+ // Health check for Railway
36
+ if (url.pathname === "/" && req.method === "GET") {
37
+ res.writeHead(200, { "Content-Type": "application/json" });
38
+ res.end(JSON.stringify({ status: "ok", name: "db-mcp" }));
39
+ return;
40
+ }
41
+ if (url.pathname !== "/mcp") {
42
+ res.writeHead(404, { "Content-Type": "application/json" });
43
+ res.end(JSON.stringify({ error: "Not found. Use POST /mcp" }));
44
+ return;
45
+ }
46
+ // CORS
47
+ res.setHeader("Access-Control-Allow-Origin", "https://mcp-builder.de");
48
+ res.setHeader("Access-Control-Allow-Methods", "GET, POST, DELETE, OPTIONS");
49
+ res.setHeader("Access-Control-Allow-Headers", "Content-Type, mcp-session-id");
50
+ res.setHeader("Access-Control-Expose-Headers", "mcp-session-id");
51
+ if (req.method === "OPTIONS") {
52
+ res.writeHead(204);
53
+ res.end();
54
+ return;
55
+ }
56
+ // Parse body for POST
57
+ let parsedBody = undefined;
58
+ if (req.method === "POST") {
59
+ const chunks = [];
60
+ for await (const chunk of req) {
61
+ chunks.push(typeof chunk === "string" ? Buffer.from(chunk) : chunk);
62
+ }
63
+ parsedBody = JSON.parse(Buffer.concat(chunks).toString());
64
+ }
65
+ // Route by session
66
+ const sessionId = req.headers["mcp-session-id"];
67
+ if (sessionId && sessions.has(sessionId)) {
68
+ const session = sessions.get(sessionId);
69
+ await session.transport.handleRequest(req, res, parsedBody);
70
+ return;
71
+ }
72
+ if (sessionId && !sessions.has(sessionId)) {
73
+ // Session expired or lost (e.g. after redeploy) — tell client to re-initialize
74
+ res.writeHead(404, { "Content-Type": "application/json" });
75
+ res.end(JSON.stringify({ error: "Session not found. Please start a new session." }));
76
+ return;
77
+ }
78
+ // New session
79
+ const transport = new StreamableHTTPServerTransport({
80
+ sessionIdGenerator: () => crypto.randomUUID(),
81
+ onsessioninitialized: (id) => {
82
+ sessions.set(id, { server, transport, createdAt: Date.now() });
83
+ console.log(`Session created: ${id} (${sessions.size} active)`);
84
+ },
85
+ });
86
+ transport.onclose = () => {
87
+ const id = transport.sessionId;
88
+ if (id) {
89
+ sessions.delete(id);
90
+ console.log(`Session closed: ${id} (${sessions.size} active)`);
91
+ }
92
+ };
93
+ const server = createServer();
94
+ await server.connect(transport);
95
+ await transport.handleRequest(req, res, parsedBody);
96
+ });
97
+ httpServer.listen(PORT, "127.0.0.1", () => {
98
+ console.log(`DB-MCP Streamable HTTP server listening on port ${PORT}`);
99
+ console.log(`Endpoint: http://localhost:${PORT}/mcp`);
100
+ });
101
+ }
102
+ else {
103
+ // Local: stdio transport
104
+ const { StdioServerTransport } = await import("@modelcontextprotocol/sdk/server/stdio.js");
105
+ const server = createServer();
106
+ const transport = new StdioServerTransport();
107
+ await server.connect(transport);
108
+ }
package/build/slim.js ADDED
@@ -0,0 +1,139 @@
1
+ /**
2
+ * Strips DB API responses down to the fields the LLM actually needs.
3
+ * Removes coordinates, operator details, ril100 IDs, excessive product info, etc.
4
+ * Also removes undefined/null values to minimize JSON size.
5
+ */
6
+ /** JSON.stringify replacer that drops undefined/null values */
7
+ function dropNulls(_key, value) {
8
+ return value === null || value === undefined ? undefined : value;
9
+ }
10
+ /** Compact JSON without pretty-printing, dropping nulls */
11
+ export function compact(obj) {
12
+ return JSON.stringify(obj, dropNulls);
13
+ }
14
+ function pick(obj, keys) {
15
+ if (!obj)
16
+ return {};
17
+ const out = {};
18
+ for (const k of keys) {
19
+ if (obj[k] !== undefined && obj[k] !== null)
20
+ out[k] = obj[k];
21
+ }
22
+ return out;
23
+ }
24
+ function slimStop(stop) {
25
+ if (!stop)
26
+ return null;
27
+ return { id: stop.id, name: stop.name };
28
+ }
29
+ function slimRemarks(remarks) {
30
+ if (!Array.isArray(remarks))
31
+ return undefined;
32
+ const kept = remarks
33
+ .filter((r) => r.type === "warning" || r.type === "status")
34
+ .map((r) => pick(r, ["type", "summary", "text", "validFrom", "validUntil"]));
35
+ return kept.length > 0 ? kept : undefined;
36
+ }
37
+ function slimStopover(s) {
38
+ const out = { stop: s.stop?.name ?? null };
39
+ // Only include times that exist
40
+ const arr = s.arrival ?? s.plannedArrival;
41
+ if (arr)
42
+ out.arrival = arr;
43
+ if (s.arrivalDelay)
44
+ out.arrivalDelay = s.arrivalDelay;
45
+ const dep = s.departure ?? s.plannedDeparture;
46
+ if (dep)
47
+ out.departure = dep;
48
+ if (s.departureDelay)
49
+ out.departureDelay = s.departureDelay;
50
+ // Platform only if present
51
+ const plat = s.departurePlatform ?? s.plannedDeparturePlatform;
52
+ if (plat)
53
+ out.platform = plat;
54
+ if (s.cancelled)
55
+ out.cancelled = true;
56
+ return out;
57
+ }
58
+ // ── public API ───────────────────────────────────────────────────────
59
+ function slimLeg(leg) {
60
+ const out = {
61
+ origin: slimStop(leg.origin),
62
+ destination: slimStop(leg.destination),
63
+ departure: leg.departure ?? leg.plannedDeparture,
64
+ plannedDeparture: leg.plannedDeparture,
65
+ departureDelay: leg.departureDelay ?? undefined,
66
+ arrival: leg.arrival ?? leg.plannedArrival,
67
+ plannedArrival: leg.plannedArrival,
68
+ arrivalDelay: leg.arrivalDelay ?? undefined,
69
+ };
70
+ if (leg.walking) {
71
+ out.walking = true;
72
+ if (leg.distance)
73
+ out.distance = leg.distance;
74
+ }
75
+ else {
76
+ out.line = leg.line ? pick(leg.line, ["name", "productName", "mode"]) : undefined;
77
+ out.direction = leg.direction;
78
+ out.departurePlatform = leg.departurePlatform ?? leg.plannedDeparturePlatform;
79
+ out.plannedDeparturePlatform = leg.plannedDeparturePlatform;
80
+ out.arrivalPlatform = leg.arrivalPlatform ?? leg.plannedArrivalPlatform;
81
+ out.plannedArrivalPlatform = leg.plannedArrivalPlatform;
82
+ if (leg.cancelled)
83
+ out.cancelled = true;
84
+ const remarks = slimRemarks(leg.remarks);
85
+ if (remarks)
86
+ out.remarks = remarks;
87
+ if (Array.isArray(leg.stopovers)) {
88
+ out.stopovers = leg.stopovers.map(slimStopover);
89
+ }
90
+ }
91
+ return out;
92
+ }
93
+ export function slimJourneys(data) {
94
+ const journeys = data.journeys ?? [];
95
+ return {
96
+ journeys: journeys.map((j) => ({
97
+ legs: (j.legs ?? []).map(slimLeg),
98
+ ...(j.price ? { price: j.price } : {}),
99
+ })),
100
+ };
101
+ }
102
+ export function slimTrip(data) {
103
+ const trip = data.trip ?? data;
104
+ return {
105
+ trip: {
106
+ ...pick(trip, ["id", "direction", "cancelled"]),
107
+ line: trip.line ? pick(trip.line, ["name", "productName", "mode"]) : undefined,
108
+ origin: slimStop(trip.origin),
109
+ destination: slimStop(trip.destination),
110
+ departure: trip.departure ?? trip.plannedDeparture,
111
+ plannedDeparture: trip.plannedDeparture,
112
+ departureDelay: trip.departureDelay ?? undefined,
113
+ arrival: trip.arrival ?? trip.plannedArrival,
114
+ plannedArrival: trip.plannedArrival,
115
+ arrivalDelay: trip.arrivalDelay ?? undefined,
116
+ remarks: slimRemarks(trip.remarks),
117
+ stopovers: Array.isArray(trip.stopovers)
118
+ ? trip.stopovers.map(slimStopover)
119
+ : undefined,
120
+ },
121
+ };
122
+ }
123
+ export function slimDeparture(d) {
124
+ return {
125
+ tripId: d.tripId,
126
+ line: d.line ? pick(d.line, ["name", "productName", "mode"]) : undefined,
127
+ direction: d.direction,
128
+ when: d.when ?? d.plannedWhen,
129
+ plannedWhen: d.plannedWhen,
130
+ delay: d.delay ?? undefined,
131
+ platform: d.platform ?? d.plannedPlatform,
132
+ plannedPlatform: d.plannedPlatform,
133
+ ...(d.cancelled ? { cancelled: true } : {}),
134
+ remarks: slimRemarks(d.remarks),
135
+ };
136
+ }
137
+ export function slimDepartures(departures) {
138
+ return departures.map((d) => slimDeparture(d));
139
+ }
@@ -0,0 +1,38 @@
1
+ import { z } from "zod";
2
+ import { client } from "../api-client.js";
3
+ import { slimJourneys, compact } from "../slim.js";
4
+ export function registerFindJourneys(server) {
5
+ server.tool("find_journeys", "Find journey connections between two stations. Returns journeys with legs, lines, platforms, stopovers, and remarks.", {
6
+ from_id: z.string().describe("Departure station ID (e.g. '8000261' for München Hbf)"),
7
+ to_id: z.string().describe("Arrival station ID (e.g. '8000105' for Frankfurt Hbf)"),
8
+ departure: z.string().describe("ISO departure date/time (e.g. '2026-03-08T14:00')"),
9
+ results: z.number().default(4).describe("Number of journeys to return (default 4)"),
10
+ }, async ({ from_id, to_id, departure, results }) => {
11
+ try {
12
+ for (const [label, id] of [["from_id", from_id], ["to_id", to_id]]) {
13
+ if (!/^\d{6,9}$/.test(id)) {
14
+ return {
15
+ isError: true,
16
+ content: [{ type: "text", text: `find_journeys failed: Invalid ${label} '${id}'. Expected a numeric HAFAS ID (e.g. '8000261' for München Hbf). Use find_station to look up the correct ID.` }],
17
+ };
18
+ }
19
+ }
20
+ const data = await client.journeys(from_id, to_id, {
21
+ departure: new Date(departure),
22
+ results,
23
+ stopovers: true,
24
+ remarks: true,
25
+ });
26
+ const slim = slimJourneys(data);
27
+ return {
28
+ content: [{ type: "text", text: compact(slim) }],
29
+ };
30
+ }
31
+ catch (error) {
32
+ return {
33
+ isError: true,
34
+ content: [{ type: "text", text: `find_journeys failed: ${error instanceof Error ? error.message : String(error)}` }],
35
+ };
36
+ }
37
+ });
38
+ }
@@ -0,0 +1,24 @@
1
+ import { z } from "zod";
2
+ import { client } from "../api-client.js";
3
+ export function registerFindStation(server) {
4
+ server.tool("find_station", "Search for a Deutsche Bahn station by name. Returns raw JSON array of matching locations.", {
5
+ query: z.string().describe("Station name to search for"),
6
+ results: z
7
+ .number()
8
+ .default(1)
9
+ .describe("Number of results to return (default 1)"),
10
+ }, async ({ query, results }) => {
11
+ try {
12
+ const data = await client.locations(query, { results });
13
+ return {
14
+ content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
15
+ };
16
+ }
17
+ catch (error) {
18
+ return {
19
+ isError: true,
20
+ content: [{ type: "text", text: `find_station failed: ${error instanceof Error ? error.message : String(error)}` }],
21
+ };
22
+ }
23
+ });
24
+ }
@@ -0,0 +1,105 @@
1
+ import { z } from "zod";
2
+ import { client } from "../api-client.js";
3
+ import { slimTrip, compact } from "../slim.js";
4
+ const PRODUCT_PREFIXES = {
5
+ nationalExpress: ["ICE", "TGV", "RJ", "RJX", "ECE"],
6
+ national: ["IC", "EC", "EN", "NJ", "FLX"],
7
+ regionalExpress: ["RE", "IRE", "MEX", "FEX"],
8
+ regional: ["RB", "RS"],
9
+ suburban: ["S"],
10
+ };
11
+ function getProductFilters(trainName) {
12
+ const prefix = trainName.replace(/\s+/g, "").replace(/\d+$/, "").toUpperCase();
13
+ for (const [product, prefixes] of Object.entries(PRODUCT_PREFIXES)) {
14
+ if (prefixes.includes(prefix)) {
15
+ const products = {
16
+ nationalExpress: false,
17
+ national: false,
18
+ regionalExpress: false,
19
+ regional: false,
20
+ suburban: false,
21
+ bus: false,
22
+ ferry: false,
23
+ subway: false,
24
+ tram: false,
25
+ };
26
+ products[product] = true;
27
+ return products;
28
+ }
29
+ }
30
+ // Unknown prefix — don't filter
31
+ return undefined;
32
+ }
33
+ export function registerFindTrip(server) {
34
+ server.tool("find_trip", "Find a specific train trip by name and station. Fetches all departures for the day, matches the train, then retrieves full trip details with stopovers and remarks.", {
35
+ train_name: z.string().describe("Train name to search for (e.g. 'ICE 599')"),
36
+ station_id: z.string().describe("Station ID (e.g. '8000261' for München Hbf)"),
37
+ date: z.string().describe("ISO date string (e.g. '2026-03-08')"),
38
+ }, async ({ train_name, station_id, date }) => {
39
+ try {
40
+ if (!/^\d{6,9}$/.test(station_id)) {
41
+ return {
42
+ isError: true,
43
+ content: [{ type: "text", text: `find_trip failed: Invalid station_id '${station_id}'. Expected a numeric HAFAS ID (e.g. '8000261' for München Hbf). Use find_station to look up the correct ID.` }],
44
+ };
45
+ }
46
+ // Step 1: Get departures for the day (db profile max 720 min, so split into two 12h windows)
47
+ const products = getProductFilters(train_name);
48
+ const baseOpt = { duration: 720 };
49
+ if (products)
50
+ baseOpt.products = products;
51
+ const res1 = await client.departures(station_id, {
52
+ ...baseOpt,
53
+ when: new Date(`${date}T00:00`),
54
+ });
55
+ let departures = (res1.departures ?? []);
56
+ // Search second half of day if needed
57
+ const normalize = (s) => s.toLowerCase().replace(/\s+/g, "");
58
+ let match = departures.find((d) => d.line?.name != null &&
59
+ normalize(d.line.name) === normalize(train_name));
60
+ if (!match) {
61
+ const res2 = await client.departures(station_id, {
62
+ ...baseOpt,
63
+ when: new Date(`${date}T12:00`),
64
+ });
65
+ const moreDepartures = (res2.departures ?? []);
66
+ departures = [...departures, ...moreDepartures];
67
+ match = moreDepartures.find((d) => d.line?.name != null &&
68
+ normalize(d.line.name) === normalize(train_name));
69
+ }
70
+ if (!match || !match.tripId) {
71
+ const availableNames = [
72
+ ...new Set(departures
73
+ .map((d) => d.line?.name)
74
+ .filter((n) => !!n)),
75
+ ];
76
+ return {
77
+ content: [
78
+ {
79
+ type: "text",
80
+ text: JSON.stringify({
81
+ error: `No departure found matching '${train_name}' at station ${station_id} on ${date}`,
82
+ available_trains: availableNames,
83
+ }, null, 2),
84
+ },
85
+ ],
86
+ };
87
+ }
88
+ // Step 2: Fetch full trip details
89
+ const trip = await client.trip(match.tripId, match.line?.name ?? "", {
90
+ stopovers: true,
91
+ remarks: true,
92
+ });
93
+ const slim = slimTrip(trip);
94
+ return {
95
+ content: [{ type: "text", text: compact(slim) }],
96
+ };
97
+ }
98
+ catch (error) {
99
+ return {
100
+ isError: true,
101
+ content: [{ type: "text", text: `find_trip failed: ${error instanceof Error ? error.message : String(error)}` }],
102
+ };
103
+ }
104
+ });
105
+ }
@@ -0,0 +1,39 @@
1
+ import { z } from "zod";
2
+ import { client } from "../api-client.js";
3
+ import { slimDepartures, compact } from "../slim.js";
4
+ export function registerGetDepartures(server) {
5
+ server.tool("get_departures", "Get upcoming departures from a Deutsche Bahn station. Returns raw JSON array of departures with line.name, direction, when, delay, platform, remarks.", {
6
+ station_id: z.string().describe("Station ID (e.g. '8000261' for München Hbf)"),
7
+ when: z
8
+ .string()
9
+ .optional()
10
+ .describe("ISO 8601 datetime string (optional, defaults to now)"),
11
+ duration: z
12
+ .number()
13
+ .default(60)
14
+ .describe("Duration in minutes to query (default 60)"),
15
+ }, async ({ station_id, when, duration }) => {
16
+ try {
17
+ if (!/^\d{6,9}$/.test(station_id)) {
18
+ return {
19
+ isError: true,
20
+ content: [{ type: "text", text: `get_departures failed: Invalid station_id '${station_id}'. Expected a numeric HAFAS ID (e.g. '8000261' for München Hbf). Use find_station to look up the correct ID.` }],
21
+ };
22
+ }
23
+ const opt = { duration };
24
+ if (when)
25
+ opt.when = new Date(when);
26
+ const res = await client.departures(station_id, opt);
27
+ const slim = slimDepartures((res.departures ?? []));
28
+ return {
29
+ content: [{ type: "text", text: compact(slim) }],
30
+ };
31
+ }
32
+ catch (error) {
33
+ return {
34
+ isError: true,
35
+ content: [{ type: "text", text: `get_departures failed: ${error instanceof Error ? error.message : String(error)}` }],
36
+ };
37
+ }
38
+ });
39
+ }
package/package.json CHANGED
@@ -1 +1 @@
1
- {"name": "@iflow-mcp/faaak2-db-mcp", "version": "1.0.2", "type": "module", "bin": {"iflow-mcp_faaak2-db-mcp": "cli.js"}, "scripts": {"build": "tsc", "start": "node build/index.js", "serve": "PORT=3000 node build/index.js"}, "dependencies": {"@modelcontextprotocol/sdk": "^1.11.0", "db-vendo-client": "^6.10.8", "zod": "^3.24.0"}, "devDependencies": {"@types/node": "^22.0.0", "typescript": "^5.7.0"}}
1
+ {"name": "@iflow-mcp/faaak2-db-mcp", "version": "1.0.3", "type": "module", "bin": {"iflow-mcp_faaak2-db-mcp": "cli.js"}, "files": ["build", "cli.js", "package.json"], "scripts": {"build": "tsc", "start": "node build/index.js", "serve": "PORT=3000 node build/index.js"}, "dependencies": {"@modelcontextprotocol/sdk": "^1.11.0", "db-vendo-client": "^6.10.8", "zod": "^3.24.0"}, "devDependencies": {"@types/node": "^22.0.0", "typescript": "^5.7.0"}}
package/.mcp.json DELETED
@@ -1,8 +0,0 @@
1
- {
2
- "mcpServers": {
3
- "db-mcp": {
4
- "type": "http",
5
- "url": "https://mcp-builder.de/db/mcp"
6
- }
7
- }
8
- }
package/24918_process.log DELETED
@@ -1,14 +0,0 @@
1
- [2026-03-17] 开始处理项目 mcp-db
2
- [2026-03-17] 步骤1完成:成功fork并克隆项目
3
- [2026-03-17] 步骤2完成:代码阅读和类型判断
4
- - 项目类型:Node.js(TypeScript)
5
- - 支持协议:stdio, Streamable HTTP
6
- - 工具数量:4个
7
- - 符合npm合规政策
8
- [2026-03-17] 步骤3完成:本地测试
9
- - 修改package.json:name改为@iflow-mcp/faaak2-db-mcp,添加bin字段
10
- - 构建成功
11
- - 本地测试通过,检测到4个工具:find_station, get_departures, find_trip, find_journeys
12
- [2026-03-17] 步骤4完成:成功创建并推送iflow分支
13
- [2026-03-17] 步骤5完成:成功上传到npm官方仓库
14
- [2026-03-17] 步骤6开始:生成ServerConfig并测试
package/CLAUDE.md DELETED
@@ -1,51 +0,0 @@
1
- ## Scope Rule
2
- - Thorough inside the task boundary, hands-off outside it
3
- - Within scope: find root causes, don't patch symptoms
4
- - Outside scope: don't touch it, even if it's ugly
5
-
6
- ## Plan Mode
7
- - Use plan mode when there are multiple valid approaches or architectural decisions
8
- - If something goes sideways, STOP and re-plan — don't keep pushing
9
- - Skip plan mode when the path is obvious, even if many steps
10
-
11
- ## Self-Improvement Loop
12
- - After ANY correction, update auto-memory (MEMORY.md or topic files)
13
- - Write rules that prevent the same mistake recurring
14
- - Keep MEMORY.md as concise index, use topic files for details
15
-
16
- ## Verification
17
- - Never mark a task complete without proving it works
18
- - Run tests, check logs, demonstrate correctness
19
- - "Would a staff engineer approve this?"
20
-
21
- ## Bug Fixing
22
- - When given a bug: fix it autonomously
23
- - Follow logs and errors to root cause, then resolve
24
-
25
- ## Deployment
26
-
27
- ### Production (mcp-builder.de)
28
- - **URL:** `https://mcp-builder.de/db/mcp`
29
- - **Health:** `GET /` → `{"status":"ok","name":"db-mcp"}`
30
- - **Transport:** Streamable HTTP (MCP Spec 2025-03-26)
31
- - **Hosting:** Systemd service behind Caddy reverse proxy
32
-
33
- ### Transport-Logik (`src/index.ts`)
34
- - `PORT` env var gesetzt → Streamable HTTP
35
- - Kein `PORT` → stdio (lokale Nutzung via Claude Desktop/Claude Code)
36
-
37
- ### MCP-Client Konfiguration (Remote)
38
- ```json
39
- {
40
- "mcpServers": {
41
- "db-mcp": {
42
- "url": "https://mcp-builder.de/db/mcp"
43
- }
44
- }
45
- }
46
- ```
47
-
48
- ### Lokaler Test HTTP-Modus
49
- ```bash
50
- npm run serve # startet auf PORT=3000
51
- ```
package/language.json DELETED
@@ -1 +0,0 @@
1
- nodejs
package/package_name DELETED
@@ -1 +0,0 @@
1
- @iflow-mcp/faaak2-db-mcp
package/push_info.json DELETED
@@ -1,5 +0,0 @@
1
- {
2
- "push_platform": "github",
3
- "fork_url": "https://github.com/iflow-mcp/faaak2-mcp-db",
4
- "fork_branch": "iflow"
5
- }
@@ -1 +0,0 @@
1
- {"serverConfig": "{\"mcpServers\":{\"db-mcp\":{\"command\":\"npx\",\"args\":[\"-y\",\"-p\",\"node\",\"node\",\"-e\",\"import process from 'process';const {exec}=await import('child_process');const p=exec('npx -y @iflow-mcp/faaak2-db-mcp@latest');p.stdout.pipe(process.stdout);p.stderr.pipe(process.stderr);p.on('close',c=>process.exit(c))\"],\"values\":{}}}}"}
@@ -1,34 +0,0 @@
1
- declare module "db-vendo-client" {
2
- interface ClientOptions {
3
- when?: Date;
4
- duration?: number;
5
- products?: Record<string, boolean>;
6
- results?: number;
7
- stopovers?: boolean;
8
- remarks?: boolean;
9
- departure?: Date;
10
- arrival?: Date;
11
- transfers?: number;
12
- transferTime?: number;
13
- language?: string;
14
- polyline?: boolean;
15
- [key: string]: unknown;
16
- }
17
-
18
- interface Client {
19
- locations(query: string, opt?: ClientOptions): Promise<unknown[]>;
20
- departures(station: string, opt?: ClientOptions): Promise<{ departures: unknown[]; realtimeDataUpdatedAt?: number }>;
21
- arrivals(station: string, opt?: ClientOptions): Promise<{ arrivals: unknown[]; realtimeDataUpdatedAt?: number }>;
22
- journeys(from: string, to: string, opt?: ClientOptions): Promise<{ journeys: unknown[]; earlierRef?: string; laterRef?: string; realtimeDataUpdatedAt?: number }>;
23
- trip(id: string, lineName: string, opt?: ClientOptions): Promise<unknown>;
24
- stop(id: string, opt?: ClientOptions): Promise<unknown>;
25
- nearby(location: { latitude: number; longitude: number }, opt?: ClientOptions): Promise<unknown[]>;
26
- refreshJourney(refreshToken: string, opt?: ClientOptions): Promise<unknown>;
27
- }
28
-
29
- export function createClient(profile: unknown, userAgent: string): Client;
30
- }
31
-
32
- declare module "db-vendo-client/p/db/index.js" {
33
- export const profile: unknown;
34
- }
package/src/index.ts DELETED
@@ -1,135 +0,0 @@
1
- import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
- import { instructions } from "./instructions.js";
3
- import { registerFindStation } from "./tools/find-station.js";
4
- import { registerGetDepartures } from "./tools/get-departures.js";
5
- import { registerFindTrip } from "./tools/find-trip.js";
6
- import { registerFindJourneys } from "./tools/find-journeys.js";
7
-
8
- function createServer(): McpServer {
9
- const server = new McpServer(
10
- { name: "db-mcp", version: "1.0.0" },
11
- { instructions },
12
- );
13
- registerFindStation(server);
14
- registerGetDepartures(server);
15
- registerFindTrip(server);
16
- registerFindJourneys(server);
17
- return server;
18
- }
19
-
20
- if (process.env.PORT) {
21
- // Remote: Streamable HTTP transport
22
- const { createServer: createHttpServer } = await import("node:http");
23
- const { StreamableHTTPServerTransport } = await import(
24
- "@modelcontextprotocol/sdk/server/streamableHttp.js"
25
- );
26
-
27
- const PORT = parseInt(process.env.PORT, 10);
28
- const sessions = new Map<
29
- string,
30
- { server: McpServer; transport: InstanceType<typeof StreamableHTTPServerTransport>; createdAt: number }
31
- >();
32
-
33
- // Session timeout: clean up sessions after 30 min
34
- const SESSION_TIMEOUT_MS = 30 * 60 * 1000;
35
-
36
- setInterval(() => {
37
- const now = Date.now();
38
- for (const [id, session] of sessions) {
39
- if (now - session.createdAt > SESSION_TIMEOUT_MS) {
40
- session.transport.close?.();
41
- sessions.delete(id);
42
- console.log(`Session expired: ${id} (${sessions.size} active)`);
43
- }
44
- }
45
- }, 60_000);
46
-
47
- const httpServer = createHttpServer(async (req, res) => {
48
- const url = new URL(req.url || "/", `http://localhost:${PORT}`);
49
-
50
- // Health check for Railway
51
- if (url.pathname === "/" && req.method === "GET") {
52
- res.writeHead(200, { "Content-Type": "application/json" });
53
- res.end(JSON.stringify({ status: "ok", name: "db-mcp" }));
54
- return;
55
- }
56
-
57
- if (url.pathname !== "/mcp") {
58
- res.writeHead(404, { "Content-Type": "application/json" });
59
- res.end(JSON.stringify({ error: "Not found. Use POST /mcp" }));
60
- return;
61
- }
62
-
63
- // CORS
64
- res.setHeader("Access-Control-Allow-Origin", "https://mcp-builder.de");
65
- res.setHeader("Access-Control-Allow-Methods", "GET, POST, DELETE, OPTIONS");
66
- res.setHeader("Access-Control-Allow-Headers", "Content-Type, mcp-session-id");
67
- res.setHeader("Access-Control-Expose-Headers", "mcp-session-id");
68
-
69
- if (req.method === "OPTIONS") {
70
- res.writeHead(204);
71
- res.end();
72
- return;
73
- }
74
-
75
- // Parse body for POST
76
- let parsedBody: unknown = undefined;
77
- if (req.method === "POST") {
78
- const chunks: Buffer[] = [];
79
- for await (const chunk of req) {
80
- chunks.push(typeof chunk === "string" ? Buffer.from(chunk) : chunk);
81
- }
82
- parsedBody = JSON.parse(Buffer.concat(chunks).toString());
83
- }
84
-
85
- // Route by session
86
- const sessionId = req.headers["mcp-session-id"] as string | undefined;
87
-
88
- if (sessionId && sessions.has(sessionId)) {
89
- const session = sessions.get(sessionId)!;
90
- await session.transport.handleRequest(req, res, parsedBody);
91
- return;
92
- }
93
-
94
- if (sessionId && !sessions.has(sessionId)) {
95
- // Session expired or lost (e.g. after redeploy) — tell client to re-initialize
96
- res.writeHead(404, { "Content-Type": "application/json" });
97
- res.end(JSON.stringify({ error: "Session not found. Please start a new session." }));
98
- return;
99
- }
100
-
101
- // New session
102
- const transport = new StreamableHTTPServerTransport({
103
- sessionIdGenerator: () => crypto.randomUUID(),
104
- onsessioninitialized: (id) => {
105
- sessions.set(id, { server, transport, createdAt: Date.now() });
106
- console.log(`Session created: ${id} (${sessions.size} active)`);
107
- },
108
- });
109
-
110
- transport.onclose = () => {
111
- const id = transport.sessionId;
112
- if (id) {
113
- sessions.delete(id);
114
- console.log(`Session closed: ${id} (${sessions.size} active)`);
115
- }
116
- };
117
-
118
- const server = createServer();
119
- await server.connect(transport);
120
- await transport.handleRequest(req, res, parsedBody);
121
- });
122
-
123
- httpServer.listen(PORT, "127.0.0.1", () => {
124
- console.log(`DB-MCP Streamable HTTP server listening on port ${PORT}`);
125
- console.log(`Endpoint: http://localhost:${PORT}/mcp`);
126
- });
127
- } else {
128
- // Local: stdio transport
129
- const { StdioServerTransport } = await import(
130
- "@modelcontextprotocol/sdk/server/stdio.js"
131
- );
132
- const server = createServer();
133
- const transport = new StdioServerTransport();
134
- await server.connect(transport);
135
- }
package/src/slim.ts DELETED
@@ -1,148 +0,0 @@
1
- /**
2
- * Strips DB API responses down to the fields the LLM actually needs.
3
- * Removes coordinates, operator details, ril100 IDs, excessive product info, etc.
4
- * Also removes undefined/null values to minimize JSON size.
5
- */
6
-
7
- // ── helpers ──────────────────────────────────────────────────────────
8
-
9
- interface AnyObj {
10
- [key: string]: unknown;
11
- }
12
-
13
- /** JSON.stringify replacer that drops undefined/null values */
14
- function dropNulls(_key: string, value: unknown): unknown {
15
- return value === null || value === undefined ? undefined : value;
16
- }
17
-
18
- /** Compact JSON without pretty-printing, dropping nulls */
19
- export function compact(obj: unknown): string {
20
- return JSON.stringify(obj, dropNulls);
21
- }
22
-
23
- function pick<T extends AnyObj>(obj: T | undefined | null, keys: string[]): AnyObj {
24
- if (!obj) return {};
25
- const out: AnyObj = {};
26
- for (const k of keys) {
27
- if (obj[k] !== undefined && obj[k] !== null) out[k] = obj[k];
28
- }
29
- return out;
30
- }
31
-
32
- function slimStop(stop: AnyObj | undefined | null): AnyObj | null {
33
- if (!stop) return null;
34
- return { id: stop.id, name: stop.name };
35
- }
36
-
37
- function slimRemarks(remarks: unknown): AnyObj[] | undefined {
38
- if (!Array.isArray(remarks)) return undefined;
39
- const kept = remarks
40
- .filter((r: AnyObj) => r.type === "warning" || r.type === "status")
41
- .map((r: AnyObj) => pick(r, ["type", "summary", "text", "validFrom", "validUntil"]));
42
- return kept.length > 0 ? kept : undefined;
43
- }
44
-
45
- function slimStopover(s: AnyObj): AnyObj {
46
- const out: AnyObj = { stop: (s.stop as AnyObj)?.name ?? null };
47
- // Only include times that exist
48
- const arr = s.arrival ?? s.plannedArrival;
49
- if (arr) out.arrival = arr;
50
- if (s.arrivalDelay) out.arrivalDelay = s.arrivalDelay;
51
- const dep = s.departure ?? s.plannedDeparture;
52
- if (dep) out.departure = dep;
53
- if (s.departureDelay) out.departureDelay = s.departureDelay;
54
- // Platform only if present
55
- const plat = s.departurePlatform ?? s.plannedDeparturePlatform;
56
- if (plat) out.platform = plat;
57
- if (s.cancelled) out.cancelled = true;
58
- return out;
59
- }
60
-
61
- // ── public API ───────────────────────────────────────────────────────
62
-
63
- function slimLeg(leg: AnyObj): AnyObj {
64
- const out: AnyObj = {
65
- origin: slimStop(leg.origin as AnyObj),
66
- destination: slimStop(leg.destination as AnyObj),
67
- departure: leg.departure ?? leg.plannedDeparture,
68
- plannedDeparture: leg.plannedDeparture,
69
- departureDelay: leg.departureDelay ?? undefined,
70
- arrival: leg.arrival ?? leg.plannedArrival,
71
- plannedArrival: leg.plannedArrival,
72
- arrivalDelay: leg.arrivalDelay ?? undefined,
73
- };
74
-
75
- if (leg.walking) {
76
- out.walking = true;
77
- if (leg.distance) out.distance = leg.distance;
78
- } else {
79
- out.line = leg.line ? pick(leg.line as AnyObj, ["name", "productName", "mode"]) : undefined;
80
- out.direction = leg.direction;
81
- out.departurePlatform = leg.departurePlatform ?? leg.plannedDeparturePlatform;
82
- out.plannedDeparturePlatform = leg.plannedDeparturePlatform;
83
- out.arrivalPlatform = leg.arrivalPlatform ?? leg.plannedArrivalPlatform;
84
- out.plannedArrivalPlatform = leg.plannedArrivalPlatform;
85
-
86
- if (leg.cancelled) out.cancelled = true;
87
-
88
- const remarks = slimRemarks(leg.remarks);
89
- if (remarks) out.remarks = remarks;
90
-
91
- if (Array.isArray(leg.stopovers)) {
92
- out.stopovers = (leg.stopovers as AnyObj[]).map(slimStopover);
93
- }
94
- }
95
-
96
- return out;
97
- }
98
-
99
- export function slimJourneys(data: AnyObj): AnyObj {
100
- const journeys = (data.journeys as AnyObj[]) ?? [];
101
- return {
102
- journeys: journeys.map((j) => ({
103
- legs: ((j.legs as AnyObj[]) ?? []).map(slimLeg),
104
- ...(j.price ? { price: j.price } : {}),
105
- })),
106
- };
107
- }
108
-
109
- export function slimTrip(data: AnyObj): AnyObj {
110
- const trip = (data.trip as AnyObj) ?? data;
111
- return {
112
- trip: {
113
- ...pick(trip, ["id", "direction", "cancelled"]),
114
- line: trip.line ? pick(trip.line as AnyObj, ["name", "productName", "mode"]) : undefined,
115
- origin: slimStop(trip.origin as AnyObj),
116
- destination: slimStop(trip.destination as AnyObj),
117
- departure: trip.departure ?? trip.plannedDeparture,
118
- plannedDeparture: trip.plannedDeparture,
119
- departureDelay: trip.departureDelay ?? undefined,
120
- arrival: trip.arrival ?? trip.plannedArrival,
121
- plannedArrival: trip.plannedArrival,
122
- arrivalDelay: trip.arrivalDelay ?? undefined,
123
- remarks: slimRemarks(trip.remarks),
124
- stopovers: Array.isArray(trip.stopovers)
125
- ? (trip.stopovers as AnyObj[]).map(slimStopover)
126
- : undefined,
127
- },
128
- };
129
- }
130
-
131
- export function slimDeparture(d: AnyObj): AnyObj {
132
- return {
133
- tripId: d.tripId,
134
- line: d.line ? pick(d.line as AnyObj, ["name", "productName", "mode"]) : undefined,
135
- direction: d.direction,
136
- when: d.when ?? d.plannedWhen,
137
- plannedWhen: d.plannedWhen,
138
- delay: d.delay ?? undefined,
139
- platform: d.platform ?? d.plannedPlatform,
140
- plannedPlatform: d.plannedPlatform,
141
- ...(d.cancelled ? { cancelled: true } : {}),
142
- remarks: slimRemarks(d.remarks),
143
- };
144
- }
145
-
146
- export function slimDepartures(departures: unknown[]): AnyObj[] {
147
- return departures.map((d) => slimDeparture(d as AnyObj));
148
- }
@@ -1,46 +0,0 @@
1
- import { z } from "zod";
2
- import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
- import { client } from "../api-client.js";
4
- import { slimJourneys, compact } from "../slim.js";
5
-
6
- export function registerFindJourneys(server: McpServer) {
7
- server.tool(
8
- "find_journeys",
9
- "Find journey connections between two stations. Returns journeys with legs, lines, platforms, stopovers, and remarks.",
10
- {
11
- from_id: z.string().describe("Departure station ID (e.g. '8000261' for München Hbf)"),
12
- to_id: z.string().describe("Arrival station ID (e.g. '8000105' for Frankfurt Hbf)"),
13
- departure: z.string().describe("ISO departure date/time (e.g. '2026-03-08T14:00')"),
14
- results: z.number().default(4).describe("Number of journeys to return (default 4)"),
15
- },
16
- async ({ from_id, to_id, departure, results }) => {
17
- try {
18
- for (const [label, id] of [["from_id", from_id], ["to_id", to_id]] as const) {
19
- if (!/^\d{6,9}$/.test(id)) {
20
- return {
21
- isError: true,
22
- content: [{ type: "text" as const, text: `find_journeys failed: Invalid ${label} '${id}'. Expected a numeric HAFAS ID (e.g. '8000261' for München Hbf). Use find_station to look up the correct ID.` }],
23
- };
24
- }
25
- }
26
-
27
- const data = await client.journeys(from_id, to_id, {
28
- departure: new Date(departure),
29
- results,
30
- stopovers: true,
31
- remarks: true,
32
- });
33
-
34
- const slim = slimJourneys(data as Record<string, unknown>);
35
- return {
36
- content: [{ type: "text" as const, text: compact(slim) }],
37
- };
38
- } catch (error) {
39
- return {
40
- isError: true,
41
- content: [{ type: "text" as const, text: `find_journeys failed: ${error instanceof Error ? error.message : String(error)}` }],
42
- };
43
- }
44
- },
45
- );
46
- }
@@ -1,31 +0,0 @@
1
- import { z } from "zod";
2
- import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
- import { client } from "../api-client.js";
4
-
5
- export function registerFindStation(server: McpServer) {
6
- server.tool(
7
- "find_station",
8
- "Search for a Deutsche Bahn station by name. Returns raw JSON array of matching locations.",
9
- {
10
- query: z.string().describe("Station name to search for"),
11
- results: z
12
- .number()
13
- .default(1)
14
- .describe("Number of results to return (default 1)"),
15
- },
16
- async ({ query, results }) => {
17
- try {
18
- const data = await client.locations(query, { results });
19
-
20
- return {
21
- content: [{ type: "text" as const, text: JSON.stringify(data, null, 2) }],
22
- };
23
- } catch (error) {
24
- return {
25
- isError: true,
26
- content: [{ type: "text" as const, text: `find_station failed: ${error instanceof Error ? error.message : String(error)}` }],
27
- };
28
- }
29
- },
30
- );
31
- }
@@ -1,140 +0,0 @@
1
- import { z } from "zod";
2
- import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
- import { client } from "../api-client.js";
4
- import { slimTrip, compact } from "../slim.js";
5
-
6
- interface Departure {
7
- tripId?: string;
8
- line?: { name?: string };
9
- [key: string]: unknown;
10
- }
11
-
12
- const PRODUCT_PREFIXES: Record<string, string[]> = {
13
- nationalExpress: ["ICE", "TGV", "RJ", "RJX", "ECE"],
14
- national: ["IC", "EC", "EN", "NJ", "FLX"],
15
- regionalExpress: ["RE", "IRE", "MEX", "FEX"],
16
- regional: ["RB", "RS"],
17
- suburban: ["S"],
18
- };
19
-
20
- function getProductFilters(trainName: string): Record<string, boolean> | undefined {
21
- const prefix = trainName.replace(/\s+/g, "").replace(/\d+$/, "").toUpperCase();
22
-
23
- for (const [product, prefixes] of Object.entries(PRODUCT_PREFIXES)) {
24
- if (prefixes.includes(prefix)) {
25
- const products: Record<string, boolean> = {
26
- nationalExpress: false,
27
- national: false,
28
- regionalExpress: false,
29
- regional: false,
30
- suburban: false,
31
- bus: false,
32
- ferry: false,
33
- subway: false,
34
- tram: false,
35
- };
36
- products[product] = true;
37
- return products;
38
- }
39
- }
40
-
41
- // Unknown prefix — don't filter
42
- return undefined;
43
- }
44
-
45
- export function registerFindTrip(server: McpServer) {
46
- server.tool(
47
- "find_trip",
48
- "Find a specific train trip by name and station. Fetches all departures for the day, matches the train, then retrieves full trip details with stopovers and remarks.",
49
- {
50
- train_name: z.string().describe("Train name to search for (e.g. 'ICE 599')"),
51
- station_id: z.string().describe("Station ID (e.g. '8000261' for München Hbf)"),
52
- date: z.string().describe("ISO date string (e.g. '2026-03-08')"),
53
- },
54
- async ({ train_name, station_id, date }) => {
55
- try {
56
- if (!/^\d{6,9}$/.test(station_id)) {
57
- return {
58
- isError: true,
59
- content: [{ type: "text" as const, text: `find_trip failed: Invalid station_id '${station_id}'. Expected a numeric HAFAS ID (e.g. '8000261' for München Hbf). Use find_station to look up the correct ID.` }],
60
- };
61
- }
62
-
63
- // Step 1: Get departures for the day (db profile max 720 min, so split into two 12h windows)
64
- const products = getProductFilters(train_name);
65
- const baseOpt: Record<string, unknown> = { duration: 720 };
66
- if (products) baseOpt.products = products;
67
-
68
- const res1 = await client.departures(station_id, {
69
- ...baseOpt,
70
- when: new Date(`${date}T00:00`),
71
- });
72
- let departures: Departure[] = (res1.departures ?? []) as Departure[];
73
-
74
- // Search second half of day if needed
75
- const normalize = (s: string) => s.toLowerCase().replace(/\s+/g, "");
76
- let match = departures.find(
77
- (d) =>
78
- d.line?.name != null &&
79
- normalize(d.line.name) === normalize(train_name),
80
- );
81
-
82
- if (!match) {
83
- const res2 = await client.departures(station_id, {
84
- ...baseOpt,
85
- when: new Date(`${date}T12:00`),
86
- });
87
- const moreDepartures = (res2.departures ?? []) as Departure[];
88
- departures = [...departures, ...moreDepartures];
89
-
90
- match = moreDepartures.find(
91
- (d) =>
92
- d.line?.name != null &&
93
- normalize(d.line.name) === normalize(train_name),
94
- );
95
- }
96
-
97
- if (!match || !match.tripId) {
98
- const availableNames = [
99
- ...new Set(
100
- departures
101
- .map((d) => d.line?.name)
102
- .filter((n): n is string => !!n),
103
- ),
104
- ];
105
- return {
106
- content: [
107
- {
108
- type: "text" as const,
109
- text: JSON.stringify(
110
- {
111
- error: `No departure found matching '${train_name}' at station ${station_id} on ${date}`,
112
- available_trains: availableNames,
113
- },
114
- null,
115
- 2,
116
- ),
117
- },
118
- ],
119
- };
120
- }
121
-
122
- // Step 2: Fetch full trip details
123
- const trip = await client.trip(match.tripId, match.line?.name ?? "", {
124
- stopovers: true,
125
- remarks: true,
126
- });
127
-
128
- const slim = slimTrip(trip as Record<string, unknown>);
129
- return {
130
- content: [{ type: "text" as const, text: compact(slim) }],
131
- };
132
- } catch (error) {
133
- return {
134
- isError: true,
135
- content: [{ type: "text" as const, text: `find_trip failed: ${error instanceof Error ? error.message : String(error)}` }],
136
- };
137
- }
138
- },
139
- );
140
- }
@@ -1,47 +0,0 @@
1
- import { z } from "zod";
2
- import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
- import { client } from "../api-client.js";
4
- import { slimDepartures, compact } from "../slim.js";
5
-
6
- export function registerGetDepartures(server: McpServer) {
7
- server.tool(
8
- "get_departures",
9
- "Get upcoming departures from a Deutsche Bahn station. Returns raw JSON array of departures with line.name, direction, when, delay, platform, remarks.",
10
- {
11
- station_id: z.string().describe("Station ID (e.g. '8000261' for München Hbf)"),
12
- when: z
13
- .string()
14
- .optional()
15
- .describe("ISO 8601 datetime string (optional, defaults to now)"),
16
- duration: z
17
- .number()
18
- .default(60)
19
- .describe("Duration in minutes to query (default 60)"),
20
- },
21
- async ({ station_id, when, duration }) => {
22
- try {
23
- if (!/^\d{6,9}$/.test(station_id)) {
24
- return {
25
- isError: true,
26
- content: [{ type: "text" as const, text: `get_departures failed: Invalid station_id '${station_id}'. Expected a numeric HAFAS ID (e.g. '8000261' for München Hbf). Use find_station to look up the correct ID.` }],
27
- };
28
- }
29
-
30
- const opt: Record<string, unknown> = { duration };
31
- if (when) opt.when = new Date(when);
32
-
33
- const res = await client.departures(station_id, opt);
34
-
35
- const slim = slimDepartures((res.departures ?? []) as Record<string, unknown>[]);
36
- return {
37
- content: [{ type: "text" as const, text: compact(slim) }],
38
- };
39
- } catch (error) {
40
- return {
41
- isError: true,
42
- content: [{ type: "text" as const, text: `get_departures failed: ${error instanceof Error ? error.message : String(error)}` }],
43
- };
44
- }
45
- },
46
- );
47
- }
package/tsconfig.json DELETED
@@ -1,13 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "target": "ES2022",
4
- "module": "Node16",
5
- "moduleResolution": "Node16",
6
- "strict": true,
7
- "outDir": "./build",
8
- "rootDir": "./src",
9
- "esModuleInterop": true,
10
- "skipLibCheck": true
11
- },
12
- "include": ["src"]
13
- }
File without changes