react-native-sqlite-mcp 1.0.1 → 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.
- package/README.md +143 -46
- package/dist/db.js +35 -67
- package/dist/index.js +14 -1
- package/dist/locator.js +3 -10
- package/dist/logger.js +0 -4
- package/dist/shell.js +0 -9
- package/package.json +6 -3
- package/CONTRIBUTING.md +0 -35
- package/src/db.ts +0 -131
- package/src/index.ts +0 -387
- package/src/locator.ts +0 -272
- package/src/logger.ts +0 -37
- package/src/shell.ts +0 -81
- package/tsconfig.json +0 -16
package/src/db.ts
DELETED
|
@@ -1,131 +0,0 @@
|
|
|
1
|
-
import sqlite3 from "sqlite3";
|
|
2
|
-
import { logger } from "./logger.js";
|
|
3
|
-
|
|
4
|
-
export class Database {
|
|
5
|
-
private db: sqlite3.Database;
|
|
6
|
-
|
|
7
|
-
constructor(filename: string) {
|
|
8
|
-
this.db = new sqlite3.Database(filename);
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
public all<T>(sql: string, params: any[] = []): Promise<T[]> {
|
|
12
|
-
return new Promise((resolve, reject) => {
|
|
13
|
-
this.db.all(sql, params, (err, rows) => {
|
|
14
|
-
if (err) reject(err);
|
|
15
|
-
else resolve(rows as Array<T>);
|
|
16
|
-
});
|
|
17
|
-
});
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
public get<T>(sql: string, params: any[] = []): Promise<T | undefined> {
|
|
21
|
-
return new Promise((resolve, reject) => {
|
|
22
|
-
this.db.get(sql, params, (err, row) => {
|
|
23
|
-
if (err) reject(err);
|
|
24
|
-
else resolve(row as T | undefined);
|
|
25
|
-
});
|
|
26
|
-
});
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
public close(): Promise<void> {
|
|
30
|
-
return new Promise((resolve, reject) => {
|
|
31
|
-
this.db.close((err) => {
|
|
32
|
-
if (err) reject(err);
|
|
33
|
-
else resolve();
|
|
34
|
-
});
|
|
35
|
-
});
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
interface CachedConnection {
|
|
40
|
-
db: Database;
|
|
41
|
-
lastUsed: number;
|
|
42
|
-
timer: ReturnType<typeof setTimeout>;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
const CACHE_TTL_MS = 60_000; // close idle connections after 60s
|
|
46
|
-
const connectionCache = new Map<string, CachedConnection>();
|
|
47
|
-
|
|
48
|
-
function getCachedDb(dbPath: string): Database {
|
|
49
|
-
const existing = connectionCache.get(dbPath);
|
|
50
|
-
|
|
51
|
-
if (existing) {
|
|
52
|
-
clearTimeout(existing.timer);
|
|
53
|
-
existing.lastUsed = Date.now();
|
|
54
|
-
existing.timer = setTimeout(() => evictConnection(dbPath), CACHE_TTL_MS);
|
|
55
|
-
return existing.db;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
const db = new Database(dbPath);
|
|
59
|
-
const timer = setTimeout(() => evictConnection(dbPath), CACHE_TTL_MS);
|
|
60
|
-
|
|
61
|
-
connectionCache.set(dbPath, { db, lastUsed: Date.now(), timer });
|
|
62
|
-
logger.debug(`Opened DB connection: ${dbPath}`);
|
|
63
|
-
return db;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
async function evictConnection(dbPath: string): Promise<void> {
|
|
67
|
-
const entry = connectionCache.get(dbPath);
|
|
68
|
-
if (!entry) return;
|
|
69
|
-
|
|
70
|
-
connectionCache.delete(dbPath);
|
|
71
|
-
try {
|
|
72
|
-
await entry.db.close();
|
|
73
|
-
logger.debug(`Closed idle DB connection: ${dbPath}`);
|
|
74
|
-
} catch (e) {
|
|
75
|
-
logger.warn(`Error closing DB: ${dbPath}`, { error: String(e) });
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
export async function closeAllConnections(): Promise<void> {
|
|
80
|
-
const paths = [...connectionCache.keys()];
|
|
81
|
-
for (const p of paths) {
|
|
82
|
-
await evictConnection(p);
|
|
83
|
-
}
|
|
84
|
-
logger.info(`Closed ${paths.length} cached DB connection(s)`);
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
const QUERY_TIMEOUT_MS = 30_000;
|
|
88
|
-
|
|
89
|
-
function withTimeout<T>(promise: Promise<T>, ms: number, label: string): Promise<T> {
|
|
90
|
-
return new Promise<T>((resolve, reject) => {
|
|
91
|
-
const timer = setTimeout(() => {
|
|
92
|
-
reject(new Error(`Query timed out after ${ms}ms: ${label}`));
|
|
93
|
-
}, ms);
|
|
94
|
-
|
|
95
|
-
promise
|
|
96
|
-
.then((result) => { clearTimeout(timer); resolve(result); })
|
|
97
|
-
.catch((err) => { clearTimeout(timer); reject(err); });
|
|
98
|
-
});
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
export async function queryDb(dbPath: string, sql: string, params: any[] = []): Promise<any[]> {
|
|
102
|
-
const db = getCachedDb(dbPath);
|
|
103
|
-
return withTimeout(db.all(sql, params), QUERY_TIMEOUT_MS, sql.slice(0, 80));
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
export async function inspectSchema(dbPath: string): Promise<any> {
|
|
107
|
-
const db = getCachedDb(dbPath);
|
|
108
|
-
|
|
109
|
-
const tables = await withTimeout(
|
|
110
|
-
db.all<{ name: string }>("SELECT name FROM sqlite_master WHERE type='table' ORDER BY name;"),
|
|
111
|
-
QUERY_TIMEOUT_MS,
|
|
112
|
-
"inspect_schema:tables"
|
|
113
|
-
);
|
|
114
|
-
|
|
115
|
-
const schemaInfo: Record<string, any> = {};
|
|
116
|
-
|
|
117
|
-
for (const table of tables) {
|
|
118
|
-
const columns = await db.all(`PRAGMA table_info("${table.name}");`);
|
|
119
|
-
const createSql = await db.get<{ sql: string }>(
|
|
120
|
-
`SELECT sql FROM sqlite_master WHERE type='table' AND name=?`,
|
|
121
|
-
[table.name]
|
|
122
|
-
);
|
|
123
|
-
|
|
124
|
-
schemaInfo[table.name] = {
|
|
125
|
-
columns,
|
|
126
|
-
createSql: createSql?.sql
|
|
127
|
-
};
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
return schemaInfo;
|
|
131
|
-
}
|
package/src/index.ts
DELETED
|
@@ -1,387 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
4
|
-
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
5
|
-
import {
|
|
6
|
-
CallToolRequestSchema,
|
|
7
|
-
ListToolsRequestSchema,
|
|
8
|
-
} from "@modelcontextprotocol/sdk/types.js";
|
|
9
|
-
import { listDatabases, syncDatabase } from "./locator.js";
|
|
10
|
-
import { inspectSchema, queryDb, closeAllConnections } from "./db.js";
|
|
11
|
-
import { logger } from "./logger.js";
|
|
12
|
-
|
|
13
|
-
// ---------------------------------------------------------------------------
|
|
14
|
-
// Process-level guards — prevent silent crashes that cause EOF errors
|
|
15
|
-
// ---------------------------------------------------------------------------
|
|
16
|
-
|
|
17
|
-
process.on("uncaughtException", (error) => {
|
|
18
|
-
logger.error("Uncaught exception (process kept alive)", {
|
|
19
|
-
message: error.message,
|
|
20
|
-
stack: error.stack?.slice(0, 500),
|
|
21
|
-
});
|
|
22
|
-
// Do NOT call process.exit() — keep the MCP server alive
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
process.on("unhandledRejection", (reason) => {
|
|
26
|
-
logger.error("Unhandled promise rejection (process kept alive)", {
|
|
27
|
-
reason: String(reason),
|
|
28
|
-
});
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
// ---------------------------------------------------------------------------
|
|
32
|
-
// State
|
|
33
|
-
// ---------------------------------------------------------------------------
|
|
34
|
-
|
|
35
|
-
interface SyncedDB {
|
|
36
|
-
localPath: string;
|
|
37
|
-
dbName: string;
|
|
38
|
-
platform: "ios" | "android";
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
let activeDatabases: SyncedDB[] = [];
|
|
42
|
-
|
|
43
|
-
// ---------------------------------------------------------------------------
|
|
44
|
-
// Server setup
|
|
45
|
-
// ---------------------------------------------------------------------------
|
|
46
|
-
|
|
47
|
-
const server = new Server(
|
|
48
|
-
{
|
|
49
|
-
name: "react-native-sqlite-bridge",
|
|
50
|
-
version: "1.0.0",
|
|
51
|
-
},
|
|
52
|
-
{
|
|
53
|
-
capabilities: {
|
|
54
|
-
tools: {},
|
|
55
|
-
},
|
|
56
|
-
}
|
|
57
|
-
);
|
|
58
|
-
|
|
59
|
-
// MCP-level transport error handler
|
|
60
|
-
server.onerror = (error) => {
|
|
61
|
-
logger.error("MCP transport error", { message: String(error) });
|
|
62
|
-
};
|
|
63
|
-
|
|
64
|
-
// ---------------------------------------------------------------------------
|
|
65
|
-
// Tool definitions
|
|
66
|
-
// ---------------------------------------------------------------------------
|
|
67
|
-
|
|
68
|
-
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
69
|
-
return {
|
|
70
|
-
tools: [
|
|
71
|
-
{
|
|
72
|
-
name: "sync_database",
|
|
73
|
-
description:
|
|
74
|
-
"Re-runs the adb pull or file-find logic to ensure the AI is looking at the latest data from the emulator/simulator.",
|
|
75
|
-
inputSchema: {
|
|
76
|
-
type: "object",
|
|
77
|
-
properties: {
|
|
78
|
-
dbName: {
|
|
79
|
-
type: "string",
|
|
80
|
-
description:
|
|
81
|
-
"The name of the database file or a glob pattern (e.g., 'my_app.db' or '*.db'). Optional. If omitted, it will select the first discovered database.",
|
|
82
|
-
},
|
|
83
|
-
bundleId: {
|
|
84
|
-
type: "string",
|
|
85
|
-
description:
|
|
86
|
-
"(Android only) The application bundle ID (e.g., 'com.example.app'). Not required for iOS.",
|
|
87
|
-
},
|
|
88
|
-
platform: {
|
|
89
|
-
type: "string",
|
|
90
|
-
description:
|
|
91
|
-
"Optional. Explicitly target 'ios' or 'android'.",
|
|
92
|
-
},
|
|
93
|
-
},
|
|
94
|
-
},
|
|
95
|
-
},
|
|
96
|
-
{
|
|
97
|
-
name: "list_databases",
|
|
98
|
-
description:
|
|
99
|
-
"Lists all available SQLite databases found on the iOS Simulator or Android Emulator.",
|
|
100
|
-
inputSchema: {
|
|
101
|
-
type: "object",
|
|
102
|
-
properties: {
|
|
103
|
-
bundleId: {
|
|
104
|
-
type: "string",
|
|
105
|
-
description:
|
|
106
|
-
"(Android only) The application bundle ID (e.g., 'com.example.app'). Not required for iOS.",
|
|
107
|
-
},
|
|
108
|
-
platform: {
|
|
109
|
-
type: "string",
|
|
110
|
-
description:
|
|
111
|
-
"Optional. Explicitly target 'ios' or 'android'.",
|
|
112
|
-
},
|
|
113
|
-
},
|
|
114
|
-
},
|
|
115
|
-
},
|
|
116
|
-
{
|
|
117
|
-
name: "inspect_schema",
|
|
118
|
-
description:
|
|
119
|
-
"Returns a list of all tables and their column definitions. This gives the AI the 'map' of the database.",
|
|
120
|
-
inputSchema: {
|
|
121
|
-
type: "object",
|
|
122
|
-
properties: {
|
|
123
|
-
dbName: {
|
|
124
|
-
type: "string",
|
|
125
|
-
description:
|
|
126
|
-
"Optional. Target a specific database name. If omitted, uses the active DB or auto-selects.",
|
|
127
|
-
},
|
|
128
|
-
platform: {
|
|
129
|
-
type: "string",
|
|
130
|
-
description:
|
|
131
|
-
"Optional. Explicitly target 'ios' or 'android'. If omitted, uses the active DB or auto-selects.",
|
|
132
|
-
},
|
|
133
|
-
},
|
|
134
|
-
required: [],
|
|
135
|
-
},
|
|
136
|
-
},
|
|
137
|
-
{
|
|
138
|
-
name: "read_table_contents",
|
|
139
|
-
description:
|
|
140
|
-
"Returns rows from a specific table. Equivalent to SELECT * FROM table_name.",
|
|
141
|
-
inputSchema: {
|
|
142
|
-
type: "object",
|
|
143
|
-
properties: {
|
|
144
|
-
tableName: {
|
|
145
|
-
type: "string",
|
|
146
|
-
description: "The name of the table to read.",
|
|
147
|
-
},
|
|
148
|
-
limit: {
|
|
149
|
-
type: "number",
|
|
150
|
-
description:
|
|
151
|
-
"Optional limit to the number of rows returned. Defaults to 100.",
|
|
152
|
-
},
|
|
153
|
-
dbName: {
|
|
154
|
-
type: "string",
|
|
155
|
-
description:
|
|
156
|
-
"Optional. Target a specific database name. If omitted, uses the active DB or auto-selects.",
|
|
157
|
-
},
|
|
158
|
-
platform: {
|
|
159
|
-
type: "string",
|
|
160
|
-
description:
|
|
161
|
-
"Optional. Explicitly target 'ios' or 'android'. If omitted, uses the active DB or auto-selects.",
|
|
162
|
-
},
|
|
163
|
-
},
|
|
164
|
-
required: ["tableName"],
|
|
165
|
-
},
|
|
166
|
-
},
|
|
167
|
-
{
|
|
168
|
-
name: "query_db",
|
|
169
|
-
description:
|
|
170
|
-
"Accepts a raw SQL SELECT string and returns the JSON result set.",
|
|
171
|
-
inputSchema: {
|
|
172
|
-
type: "object",
|
|
173
|
-
properties: {
|
|
174
|
-
sql: {
|
|
175
|
-
type: "string",
|
|
176
|
-
description: "The raw SQL SELECT string to execute.",
|
|
177
|
-
},
|
|
178
|
-
params: {
|
|
179
|
-
type: "array",
|
|
180
|
-
description:
|
|
181
|
-
"Optional arguments to bind to the SQL query. Use this to safely substitute ? placeholders in your SQL string (e.g. ['value', 42]).",
|
|
182
|
-
items: {
|
|
183
|
-
description: "A single bound parameter value.",
|
|
184
|
-
},
|
|
185
|
-
},
|
|
186
|
-
dbName: {
|
|
187
|
-
type: "string",
|
|
188
|
-
description:
|
|
189
|
-
"Optional. Target a specific database name. If omitted, uses the active DB or auto-selects.",
|
|
190
|
-
},
|
|
191
|
-
platform: {
|
|
192
|
-
type: "string",
|
|
193
|
-
description:
|
|
194
|
-
"Optional. Explicitly target 'ios' or 'android'. If omitted, uses the active DB or auto-selects.",
|
|
195
|
-
},
|
|
196
|
-
},
|
|
197
|
-
required: ["sql"],
|
|
198
|
-
},
|
|
199
|
-
},
|
|
200
|
-
],
|
|
201
|
-
};
|
|
202
|
-
});
|
|
203
|
-
|
|
204
|
-
// ---------------------------------------------------------------------------
|
|
205
|
-
// Helpers
|
|
206
|
-
// ---------------------------------------------------------------------------
|
|
207
|
-
|
|
208
|
-
function cleanPlatform(raw?: string): "ios" | "android" | undefined {
|
|
209
|
-
if (!raw) return undefined;
|
|
210
|
-
const cleaned = raw.replace(/['"]/g, "").trim().toLowerCase();
|
|
211
|
-
if (cleaned === "ios" || cleaned === "android") return cleaned;
|
|
212
|
-
return undefined;
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
async function ensureDbState(args: any): Promise<SyncedDB> {
|
|
216
|
-
const reqDbName = args?.dbName as string | undefined;
|
|
217
|
-
const reqPlatform = cleanPlatform(args?.platform as string | undefined);
|
|
218
|
-
|
|
219
|
-
// If nothing is synced, sync defaults
|
|
220
|
-
if (activeDatabases.length === 0) {
|
|
221
|
-
const envDb = reqDbName || process.env.DB_NAME;
|
|
222
|
-
const envBundle = process.env.ANDROID_BUNDLE_ID;
|
|
223
|
-
activeDatabases = await syncDatabase(envDb, envBundle, reqPlatform);
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
let candidates = activeDatabases;
|
|
227
|
-
if (reqPlatform)
|
|
228
|
-
candidates = candidates.filter((db) => db.platform === reqPlatform);
|
|
229
|
-
if (reqDbName)
|
|
230
|
-
candidates = candidates.filter((db) => db.dbName === reqDbName);
|
|
231
|
-
|
|
232
|
-
if (candidates.length === 1) return candidates[0];
|
|
233
|
-
|
|
234
|
-
if (candidates.length === 0) {
|
|
235
|
-
throw new Error(
|
|
236
|
-
`No synced databases match the criteria (platform: ${reqPlatform || "any"}, dbName: ${reqDbName || "any"}). Try calling sync_database first.`
|
|
237
|
-
);
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
const matches = candidates
|
|
241
|
-
.map((c) => `[${c.platform}] ${c.dbName}`)
|
|
242
|
-
.join(", ");
|
|
243
|
-
throw new Error(
|
|
244
|
-
`Multiple databases match the criteria. Please specify 'platform' or 'dbName'. Matches: ${matches}`
|
|
245
|
-
);
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
// ---------------------------------------------------------------------------
|
|
249
|
-
// Tool handlers
|
|
250
|
-
// ---------------------------------------------------------------------------
|
|
251
|
-
|
|
252
|
-
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
253
|
-
const { name, arguments: args } = request.params;
|
|
254
|
-
|
|
255
|
-
try {
|
|
256
|
-
if (name === "list_databases") {
|
|
257
|
-
const bundleId = args?.bundleId as string | undefined;
|
|
258
|
-
const platform = cleanPlatform(args?.platform as string | undefined);
|
|
259
|
-
const results = await listDatabases(bundleId, platform);
|
|
260
|
-
return {
|
|
261
|
-
content: [
|
|
262
|
-
{ type: "text", text: JSON.stringify(results, null, 2) },
|
|
263
|
-
],
|
|
264
|
-
};
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
if (name === "sync_database") {
|
|
268
|
-
const dbName = args?.dbName as string | undefined;
|
|
269
|
-
const bundleId = args?.bundleId as string | undefined;
|
|
270
|
-
const platform = cleanPlatform(args?.platform as string | undefined);
|
|
271
|
-
|
|
272
|
-
const results = await syncDatabase(dbName, bundleId, platform);
|
|
273
|
-
activeDatabases = results;
|
|
274
|
-
|
|
275
|
-
let msg = "Successfully synced databases:\n";
|
|
276
|
-
for (const res of results) {
|
|
277
|
-
msg += `- Platform: ${res.platform} | DB: ${res.dbName}\n Path: ${res.localPath}\n`;
|
|
278
|
-
}
|
|
279
|
-
return { content: [{ type: "text", text: msg }] };
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
if (name === "inspect_schema") {
|
|
283
|
-
const activeDb = await ensureDbState(args);
|
|
284
|
-
const schema = await inspectSchema(activeDb.localPath);
|
|
285
|
-
return {
|
|
286
|
-
content: [
|
|
287
|
-
{
|
|
288
|
-
type: "text",
|
|
289
|
-
text:
|
|
290
|
-
`[Active Platform: ${activeDb.platform} | DB: ${activeDb.dbName}]\n` +
|
|
291
|
-
JSON.stringify(schema, null, 2),
|
|
292
|
-
},
|
|
293
|
-
],
|
|
294
|
-
};
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
if (name === "read_table_contents") {
|
|
298
|
-
const activeDb = await ensureDbState(args);
|
|
299
|
-
const tableName = args?.tableName as string;
|
|
300
|
-
const limit = (args?.limit as number) || 100;
|
|
301
|
-
|
|
302
|
-
if (!tableName) {
|
|
303
|
-
throw new Error("Missing required argument: tableName");
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
const sql = `SELECT * FROM "${tableName}" LIMIT ?`;
|
|
307
|
-
const results = await queryDb(activeDb.localPath, sql, [limit]);
|
|
308
|
-
return {
|
|
309
|
-
content: [
|
|
310
|
-
{
|
|
311
|
-
type: "text",
|
|
312
|
-
text:
|
|
313
|
-
`[Active Platform: ${activeDb.platform} | DB: ${activeDb.dbName} | Table: ${tableName} | Limit: ${limit}]\n` +
|
|
314
|
-
JSON.stringify(results, null, 2),
|
|
315
|
-
},
|
|
316
|
-
],
|
|
317
|
-
};
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
if (name === "query_db") {
|
|
321
|
-
const activeDb = await ensureDbState(args);
|
|
322
|
-
const sql = args?.sql as string;
|
|
323
|
-
const params = (args?.params as any[]) || [];
|
|
324
|
-
|
|
325
|
-
if (!sql) {
|
|
326
|
-
throw new Error("Missing required argument: sql");
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
const results = await queryDb(activeDb.localPath, sql, params);
|
|
330
|
-
return {
|
|
331
|
-
content: [
|
|
332
|
-
{
|
|
333
|
-
type: "text",
|
|
334
|
-
text:
|
|
335
|
-
`[Active Platform: ${activeDb.platform} | DB: ${activeDb.dbName}]\n` +
|
|
336
|
-
JSON.stringify(results, null, 2),
|
|
337
|
-
},
|
|
338
|
-
],
|
|
339
|
-
};
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
throw new Error(`Unknown tool: ${name}`);
|
|
343
|
-
} catch (error: any) {
|
|
344
|
-
logger.error(`Tool "${name}" failed`, { message: error.message });
|
|
345
|
-
return {
|
|
346
|
-
content: [
|
|
347
|
-
{
|
|
348
|
-
type: "text",
|
|
349
|
-
text: `Error: ${error.message}`,
|
|
350
|
-
},
|
|
351
|
-
],
|
|
352
|
-
isError: true,
|
|
353
|
-
};
|
|
354
|
-
}
|
|
355
|
-
});
|
|
356
|
-
|
|
357
|
-
// ---------------------------------------------------------------------------
|
|
358
|
-
// Graceful shutdown
|
|
359
|
-
// ---------------------------------------------------------------------------
|
|
360
|
-
|
|
361
|
-
async function shutdown(signal: string) {
|
|
362
|
-
logger.info(`Received ${signal}, shutting down gracefully...`);
|
|
363
|
-
try {
|
|
364
|
-
await closeAllConnections();
|
|
365
|
-
} catch (e) {
|
|
366
|
-
logger.error("Error during shutdown", { error: String(e) });
|
|
367
|
-
}
|
|
368
|
-
process.exit(0);
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
process.on("SIGINT", () => shutdown("SIGINT"));
|
|
372
|
-
process.on("SIGTERM", () => shutdown("SIGTERM"));
|
|
373
|
-
|
|
374
|
-
// ---------------------------------------------------------------------------
|
|
375
|
-
// Start
|
|
376
|
-
// ---------------------------------------------------------------------------
|
|
377
|
-
|
|
378
|
-
async function run() {
|
|
379
|
-
const transport = new StdioServerTransport();
|
|
380
|
-
await server.connect(transport);
|
|
381
|
-
logger.info("Universal React Native SQLite MCP Server running on stdio");
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
run().catch((error) => {
|
|
385
|
-
logger.error("Server startup error", { message: error.message, stack: error.stack });
|
|
386
|
-
process.exit(1);
|
|
387
|
-
});
|