@dotdo/postgres 0.1.1 → 0.1.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 +73 -1
- package/dist/client/index.d.ts +47 -0
- package/dist/client/index.d.ts.map +1 -0
- package/dist/client/index.js +47 -0
- package/dist/client/index.js.map +1 -0
- package/dist/client/postgres-client.d.ts +273 -0
- package/dist/client/postgres-client.d.ts.map +1 -0
- package/dist/client/postgres-client.js +389 -0
- package/dist/client/postgres-client.js.map +1 -0
- package/dist/client/types.d.ts +167 -0
- package/dist/client/types.d.ts.map +1 -0
- package/dist/client/types.js +7 -0
- package/dist/client/types.js.map +1 -0
- package/dist/do/index.d.ts +18 -0
- package/dist/do/index.d.ts.map +1 -0
- package/dist/do/index.js +18 -0
- package/dist/do/index.js.map +1 -0
- package/dist/do/postgres.d.ts +110 -0
- package/dist/do/postgres.d.ts.map +1 -0
- package/dist/do/postgres.js +266 -0
- package/dist/do/postgres.js.map +1 -0
- package/dist/do/sql.d.ts +92 -0
- package/dist/do/sql.d.ts.map +1 -0
- package/dist/do/sql.js +204 -0
- package/dist/do/sql.js.map +1 -0
- package/dist/index.d.ts +25 -30
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +29 -30
- package/dist/index.js.map +1 -1
- package/dist/mcp/binding.d.ts +47 -0
- package/dist/mcp/binding.d.ts.map +1 -0
- package/dist/mcp/binding.js +183 -0
- package/dist/mcp/binding.js.map +1 -0
- package/dist/mcp/index.d.ts +92 -0
- package/dist/mcp/index.d.ts.map +1 -0
- package/dist/mcp/index.js +91 -0
- package/dist/mcp/index.js.map +1 -0
- package/dist/mcp/server.d.ts +62 -0
- package/dist/mcp/server.d.ts.map +1 -0
- package/dist/mcp/server.js +278 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/mcp/tools.d.ts +58 -0
- package/dist/mcp/tools.d.ts.map +1 -0
- package/dist/mcp/tools.js +356 -0
- package/dist/mcp/tools.js.map +1 -0
- package/dist/mcp/types.d.ts +139 -0
- package/dist/mcp/types.d.ts.map +1 -0
- package/dist/mcp/types.js +7 -0
- package/dist/mcp/types.js.map +1 -0
- package/dist/pglite/workers-pglite.d.ts +13 -4
- package/dist/pglite/workers-pglite.d.ts.map +1 -1
- package/dist/pglite/workers-pglite.js +110 -5
- package/dist/pglite/workers-pglite.js.map +1 -1
- package/dist/pglite-assets/pglite.data +0 -0
- package/dist/pglite-assets/pglite.wasm +0 -0
- package/dist/worker/auth.d.ts.map +1 -1
- package/dist/worker/auth.js +16 -6
- package/dist/worker/auth.js.map +1 -1
- package/dist/worker/background-pglite-manager.d.ts +243 -0
- package/dist/worker/background-pglite-manager.d.ts.map +1 -0
- package/dist/worker/background-pglite-manager.js +528 -0
- package/dist/worker/background-pglite-manager.js.map +1 -0
- package/dist/worker/do-pglite-manager.d.ts +77 -0
- package/dist/worker/do-pglite-manager.d.ts.map +1 -1
- package/dist/worker/do-pglite-manager.js +189 -12
- package/dist/worker/do-pglite-manager.js.map +1 -1
- package/dist/worker/entry.d.ts.map +1 -1
- package/dist/worker/entry.js +108 -26
- package/dist/worker/entry.js.map +1 -1
- package/dist/worker/index.d.ts +7 -1
- package/dist/worker/index.d.ts.map +1 -1
- package/dist/worker/index.js +19 -1
- package/dist/worker/index.js.map +1 -1
- package/dist/worker/lazy-pglite-manager.d.ts +242 -0
- package/dist/worker/lazy-pglite-manager.d.ts.map +1 -0
- package/dist/worker/lazy-pglite-manager.js +463 -0
- package/dist/worker/lazy-pglite-manager.js.map +1 -0
- package/package.json +20 -6
- package/src/client/index.ts +61 -0
- package/src/client/postgres-client.ts +442 -0
- package/src/client/types.ts +211 -0
- package/src/do/index.ts +18 -0
- package/src/do/postgres.ts +367 -0
- package/src/do/sql.ts +280 -0
- package/src/index.ts +50 -30
- package/src/mcp/binding.ts +236 -0
- package/src/mcp/index.ts +122 -0
- package/src/mcp/server.ts +361 -0
- package/src/mcp/tools.ts +464 -0
- package/src/mcp/types.ts +148 -0
- package/src/pglite/workers-pglite.ts +141 -12
- package/src/pglite-assets/pglite.data +0 -0
- package/src/pglite-assets/pglite.wasm +0 -0
- package/src/worker/auth.ts +17 -6
- package/src/worker/background-pglite-manager.ts +680 -0
- package/src/worker/do-pglite-manager.ts +235 -19
- package/src/worker/entry.ts +112 -30
- package/src/worker/index.ts +71 -1
- package/src/worker/lazy-pglite-manager.ts +595 -0
- package/dist/iceberg/duckdb-wasm.d.ts +0 -447
- package/dist/iceberg/duckdb-wasm.d.ts.map +0 -1
- package/dist/iceberg/duckdb-wasm.js +0 -600
- package/dist/iceberg/duckdb-wasm.js.map +0 -1
- package/dist/iceberg/test-fixtures.d.ts +0 -151
- package/dist/iceberg/test-fixtures.d.ts.map +0 -1
- package/dist/iceberg/test-fixtures.js +0 -446
- package/dist/iceberg/test-fixtures.js.map +0 -1
- package/dist/worker/__mocks__/cloudflare-workers.d.ts +0 -31
- package/dist/worker/__mocks__/cloudflare-workers.d.ts.map +0 -1
- package/dist/worker/__mocks__/cloudflare-workers.js +0 -33
- package/dist/worker/__mocks__/cloudflare-workers.js.map +0 -1
package/src/mcp/tools.ts
ADDED
|
@@ -0,0 +1,464 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP Tools for PostgresDO
|
|
3
|
+
*
|
|
4
|
+
* Implements the three core MCP tools: search, fetch, and do.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type {
|
|
8
|
+
MCPSearchResult,
|
|
9
|
+
MCPFetchResult,
|
|
10
|
+
ToolResponse,
|
|
11
|
+
SearchInput,
|
|
12
|
+
FetchInput,
|
|
13
|
+
DoInput,
|
|
14
|
+
MCPAuthContext,
|
|
15
|
+
} from './types.js'
|
|
16
|
+
import type { QueryExecutor } from './binding.js'
|
|
17
|
+
import { createPGBinding, PG_BINDING_TYPES } from './binding.js'
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Tool definition for MCP protocol
|
|
21
|
+
*/
|
|
22
|
+
export interface Tool {
|
|
23
|
+
name: string
|
|
24
|
+
description: string
|
|
25
|
+
inputSchema: {
|
|
26
|
+
type: string
|
|
27
|
+
properties: Record<string, unknown>
|
|
28
|
+
required: string[]
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Search tool definition
|
|
34
|
+
*/
|
|
35
|
+
export const searchTool: Tool = {
|
|
36
|
+
name: 'search',
|
|
37
|
+
description:
|
|
38
|
+
'Search the PostgreSQL database. Supports SQL queries, table listing, and schema introspection.',
|
|
39
|
+
inputSchema: {
|
|
40
|
+
type: 'object',
|
|
41
|
+
properties: {
|
|
42
|
+
query: {
|
|
43
|
+
type: 'string',
|
|
44
|
+
description:
|
|
45
|
+
'Search query. Can be: SQL query (SELECT ...), "tables" to list tables, or "schema:<table>" to get table schema',
|
|
46
|
+
},
|
|
47
|
+
limit: {
|
|
48
|
+
type: 'number',
|
|
49
|
+
description: 'Maximum number of results to return',
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
required: ['query'],
|
|
53
|
+
},
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Fetch tool definition
|
|
58
|
+
*/
|
|
59
|
+
export const fetchTool: Tool = {
|
|
60
|
+
name: 'fetch',
|
|
61
|
+
description:
|
|
62
|
+
'Fetch a specific resource from the database. Format: "table/id" or "schema/table/column"',
|
|
63
|
+
inputSchema: {
|
|
64
|
+
type: 'object',
|
|
65
|
+
properties: {
|
|
66
|
+
resource: {
|
|
67
|
+
type: 'string',
|
|
68
|
+
description:
|
|
69
|
+
'Resource identifier. Format: "table/id" to get a row, or "schema/table" for table schema',
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
required: ['resource'],
|
|
73
|
+
},
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Do tool definition
|
|
78
|
+
*/
|
|
79
|
+
export const doTool: Tool = {
|
|
80
|
+
name: 'do',
|
|
81
|
+
description: `Execute TypeScript/JavaScript code with access to the PostgreSQL database via the 'pg' binding.
|
|
82
|
+
|
|
83
|
+
Available APIs:
|
|
84
|
+
${PG_BINDING_TYPES}`,
|
|
85
|
+
inputSchema: {
|
|
86
|
+
type: 'object',
|
|
87
|
+
properties: {
|
|
88
|
+
code: {
|
|
89
|
+
type: 'string',
|
|
90
|
+
description: 'TypeScript/JavaScript code to execute',
|
|
91
|
+
},
|
|
92
|
+
},
|
|
93
|
+
required: ['code'],
|
|
94
|
+
},
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Create the search tool handler
|
|
99
|
+
*/
|
|
100
|
+
export function createSearchHandler(
|
|
101
|
+
executor: QueryExecutor
|
|
102
|
+
): (input: SearchInput) => Promise<ToolResponse> {
|
|
103
|
+
return async (input: SearchInput): Promise<ToolResponse> => {
|
|
104
|
+
try {
|
|
105
|
+
const { query, limit = 100 } = input
|
|
106
|
+
|
|
107
|
+
// Handle special commands
|
|
108
|
+
if (query.toLowerCase() === 'tables') {
|
|
109
|
+
const result = await executor.rpcQuery(`
|
|
110
|
+
SELECT
|
|
111
|
+
table_name as name,
|
|
112
|
+
table_schema as schema,
|
|
113
|
+
table_type as type
|
|
114
|
+
FROM information_schema.tables
|
|
115
|
+
WHERE table_schema NOT IN ('pg_catalog', 'information_schema')
|
|
116
|
+
ORDER BY table_schema, table_name
|
|
117
|
+
LIMIT $1
|
|
118
|
+
`, [limit])
|
|
119
|
+
|
|
120
|
+
const results: MCPSearchResult[] = result.rows.map((row) => ({
|
|
121
|
+
id: `${row.schema}.${row.name}`,
|
|
122
|
+
title: row.name as string,
|
|
123
|
+
snippet: `${row.type} in schema ${row.schema}`,
|
|
124
|
+
metadata: { schema: row.schema, type: row.type },
|
|
125
|
+
}))
|
|
126
|
+
|
|
127
|
+
return {
|
|
128
|
+
content: [{ type: 'text', text: JSON.stringify(results, null, 2) }],
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Handle schema introspection: "schema:tablename"
|
|
133
|
+
if (query.toLowerCase().startsWith('schema:')) {
|
|
134
|
+
const tableName = query.slice(7).trim()
|
|
135
|
+
|
|
136
|
+
// Validate table name
|
|
137
|
+
if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(tableName)) {
|
|
138
|
+
return {
|
|
139
|
+
content: [{ type: 'text', text: JSON.stringify({ error: 'Invalid table name' }) }],
|
|
140
|
+
isError: true,
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const result = await executor.rpcQuery(
|
|
145
|
+
`
|
|
146
|
+
SELECT
|
|
147
|
+
column_name as name,
|
|
148
|
+
data_type as type,
|
|
149
|
+
is_nullable,
|
|
150
|
+
column_default
|
|
151
|
+
FROM information_schema.columns
|
|
152
|
+
WHERE table_name = $1
|
|
153
|
+
AND table_schema NOT IN ('pg_catalog', 'information_schema')
|
|
154
|
+
ORDER BY ordinal_position
|
|
155
|
+
`,
|
|
156
|
+
[tableName]
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
const results: MCPSearchResult[] = result.rows.map((row) => ({
|
|
160
|
+
id: `${tableName}.${row.name}`,
|
|
161
|
+
title: row.name as string,
|
|
162
|
+
snippet: `${row.type}${row.is_nullable === 'NO' ? ' NOT NULL' : ''}${row.column_default ? ` DEFAULT ${row.column_default}` : ''}`,
|
|
163
|
+
metadata: {
|
|
164
|
+
table: tableName,
|
|
165
|
+
type: row.type,
|
|
166
|
+
nullable: row.is_nullable === 'YES',
|
|
167
|
+
default: row.column_default,
|
|
168
|
+
},
|
|
169
|
+
}))
|
|
170
|
+
|
|
171
|
+
return {
|
|
172
|
+
content: [{ type: 'text', text: JSON.stringify(results, null, 2) }],
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Execute SQL query
|
|
177
|
+
// Add LIMIT if not present and query is a SELECT
|
|
178
|
+
let sql = query.trim()
|
|
179
|
+
const isSelect = sql.toLowerCase().startsWith('select')
|
|
180
|
+
|
|
181
|
+
if (isSelect && !sql.toLowerCase().includes('limit')) {
|
|
182
|
+
sql = `${sql} LIMIT ${limit}`
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const result = await executor.rpcQuery(sql)
|
|
186
|
+
|
|
187
|
+
const results: MCPSearchResult[] = result.rows.map((row, index) => {
|
|
188
|
+
// Try to find an id-like field
|
|
189
|
+
const idField = Object.keys(row).find(
|
|
190
|
+
(k) => k === 'id' || k.endsWith('_id') || k === 'uuid'
|
|
191
|
+
)
|
|
192
|
+
const id = idField ? String(row[idField]) : String(index)
|
|
193
|
+
|
|
194
|
+
// Create a title from the first non-id field
|
|
195
|
+
const titleField = Object.keys(row).find(
|
|
196
|
+
(k) => k !== idField && typeof row[k] === 'string'
|
|
197
|
+
)
|
|
198
|
+
const title = titleField ? String(row[titleField]) : `Row ${index + 1}`
|
|
199
|
+
|
|
200
|
+
return {
|
|
201
|
+
id,
|
|
202
|
+
title,
|
|
203
|
+
snippet: JSON.stringify(row),
|
|
204
|
+
metadata: row as Record<string, unknown>,
|
|
205
|
+
}
|
|
206
|
+
})
|
|
207
|
+
|
|
208
|
+
return {
|
|
209
|
+
content: [{ type: 'text', text: JSON.stringify(results, null, 2) }],
|
|
210
|
+
}
|
|
211
|
+
} catch (error) {
|
|
212
|
+
const message = error instanceof Error ? error.message : 'Unknown error'
|
|
213
|
+
return {
|
|
214
|
+
content: [{ type: 'text', text: JSON.stringify({ error: message }) }],
|
|
215
|
+
isError: true,
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Create the fetch tool handler
|
|
223
|
+
*/
|
|
224
|
+
export function createFetchHandler(
|
|
225
|
+
executor: QueryExecutor
|
|
226
|
+
): (input: FetchInput) => Promise<ToolResponse> {
|
|
227
|
+
return async (input: FetchInput): Promise<ToolResponse> => {
|
|
228
|
+
try {
|
|
229
|
+
const { resource } = input
|
|
230
|
+
const parts = resource.split('/')
|
|
231
|
+
|
|
232
|
+
// Handle schema fetch: "schema/tablename"
|
|
233
|
+
if (parts[0].toLowerCase() === 'schema' && parts.length === 2) {
|
|
234
|
+
const tableName = parts[1]
|
|
235
|
+
|
|
236
|
+
// Validate table name
|
|
237
|
+
if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(tableName)) {
|
|
238
|
+
return {
|
|
239
|
+
content: [{ type: 'text', text: JSON.stringify({ error: 'Invalid table name' }) }],
|
|
240
|
+
isError: true,
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
const result = await executor.rpcQuery(
|
|
245
|
+
`
|
|
246
|
+
SELECT
|
|
247
|
+
column_name as name,
|
|
248
|
+
data_type as type,
|
|
249
|
+
is_nullable = 'YES' as nullable,
|
|
250
|
+
column_default as "default"
|
|
251
|
+
FROM information_schema.columns
|
|
252
|
+
WHERE table_name = $1
|
|
253
|
+
AND table_schema NOT IN ('pg_catalog', 'information_schema')
|
|
254
|
+
ORDER BY ordinal_position
|
|
255
|
+
`,
|
|
256
|
+
[tableName]
|
|
257
|
+
)
|
|
258
|
+
|
|
259
|
+
const fetchResult: MCPFetchResult = {
|
|
260
|
+
content: JSON.stringify(result.rows, null, 2),
|
|
261
|
+
contentType: 'application/json',
|
|
262
|
+
metadata: {
|
|
263
|
+
table: tableName,
|
|
264
|
+
columnCount: result.rows.length,
|
|
265
|
+
},
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
return {
|
|
269
|
+
content: [{ type: 'text', text: JSON.stringify(fetchResult, null, 2) }],
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// Handle row fetch: "tablename/id"
|
|
274
|
+
if (parts.length === 2) {
|
|
275
|
+
const [tableName, id] = parts
|
|
276
|
+
|
|
277
|
+
// Validate table name
|
|
278
|
+
if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(tableName)) {
|
|
279
|
+
return {
|
|
280
|
+
content: [{ type: 'text', text: JSON.stringify({ error: 'Invalid table name' }) }],
|
|
281
|
+
isError: true,
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// Try to find the primary key column
|
|
286
|
+
const pkResult = await executor.rpcQuery(
|
|
287
|
+
`
|
|
288
|
+
SELECT kcu.column_name
|
|
289
|
+
FROM information_schema.key_column_usage kcu
|
|
290
|
+
JOIN information_schema.table_constraints tc
|
|
291
|
+
ON kcu.constraint_name = tc.constraint_name
|
|
292
|
+
WHERE tc.constraint_type = 'PRIMARY KEY'
|
|
293
|
+
AND kcu.table_name = $1
|
|
294
|
+
LIMIT 1
|
|
295
|
+
`,
|
|
296
|
+
[tableName]
|
|
297
|
+
)
|
|
298
|
+
|
|
299
|
+
const pkColumn =
|
|
300
|
+
pkResult.rows.length > 0 ? (pkResult.rows[0].column_name as string) : 'id'
|
|
301
|
+
|
|
302
|
+
// Fetch the row
|
|
303
|
+
const result = await executor.rpcQuery(
|
|
304
|
+
`SELECT * FROM "${tableName}" WHERE "${pkColumn}" = $1 LIMIT 1`,
|
|
305
|
+
[id]
|
|
306
|
+
)
|
|
307
|
+
|
|
308
|
+
if (result.rows.length === 0) {
|
|
309
|
+
return {
|
|
310
|
+
content: [
|
|
311
|
+
{
|
|
312
|
+
type: 'text',
|
|
313
|
+
text: JSON.stringify({ error: `Row not found: ${resource}` }),
|
|
314
|
+
},
|
|
315
|
+
],
|
|
316
|
+
isError: true,
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
const fetchResult: MCPFetchResult = {
|
|
321
|
+
content: JSON.stringify(result.rows[0], null, 2),
|
|
322
|
+
contentType: 'application/json',
|
|
323
|
+
metadata: {
|
|
324
|
+
table: tableName,
|
|
325
|
+
primaryKey: pkColumn,
|
|
326
|
+
id,
|
|
327
|
+
},
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
return {
|
|
331
|
+
content: [{ type: 'text', text: JSON.stringify(fetchResult, null, 2) }],
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
return {
|
|
336
|
+
content: [
|
|
337
|
+
{
|
|
338
|
+
type: 'text',
|
|
339
|
+
text: JSON.stringify({
|
|
340
|
+
error:
|
|
341
|
+
'Invalid resource format. Use "table/id" for rows or "schema/table" for schema',
|
|
342
|
+
}),
|
|
343
|
+
},
|
|
344
|
+
],
|
|
345
|
+
isError: true,
|
|
346
|
+
}
|
|
347
|
+
} catch (error) {
|
|
348
|
+
const message = error instanceof Error ? error.message : 'Unknown error'
|
|
349
|
+
return {
|
|
350
|
+
content: [{ type: 'text', text: JSON.stringify({ error: message }) }],
|
|
351
|
+
isError: true,
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
/**
|
|
358
|
+
* Create the do tool handler
|
|
359
|
+
*/
|
|
360
|
+
export function createDoHandler(
|
|
361
|
+
executor: QueryExecutor,
|
|
362
|
+
options: { timeout?: number; authContext?: MCPAuthContext } = {}
|
|
363
|
+
): (input: DoInput) => Promise<ToolResponse> {
|
|
364
|
+
const { timeout = 5000 } = options
|
|
365
|
+
|
|
366
|
+
return async (input: DoInput): Promise<ToolResponse> => {
|
|
367
|
+
const startTime = Date.now()
|
|
368
|
+
const logs: Array<[string, string]> = []
|
|
369
|
+
|
|
370
|
+
try {
|
|
371
|
+
const { code } = input
|
|
372
|
+
|
|
373
|
+
// Create the pg binding
|
|
374
|
+
const pg = createPGBinding(executor)
|
|
375
|
+
|
|
376
|
+
// Create sandbox console that captures logs
|
|
377
|
+
const sandboxConsole = {
|
|
378
|
+
log: (...args: unknown[]) => logs.push(['log', args.map(String).join(' ')]),
|
|
379
|
+
error: (...args: unknown[]) => logs.push(['error', args.map(String).join(' ')]),
|
|
380
|
+
warn: (...args: unknown[]) => logs.push(['warn', args.map(String).join(' ')]),
|
|
381
|
+
info: (...args: unknown[]) => logs.push(['info', args.map(String).join(' ')]),
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
// Create and execute function with sandbox
|
|
385
|
+
// Note: In production, use ai-evaluate for proper V8 isolation
|
|
386
|
+
const fn = new Function('pg', 'console', `
|
|
387
|
+
return (async () => {
|
|
388
|
+
${code}
|
|
389
|
+
})()
|
|
390
|
+
`)
|
|
391
|
+
|
|
392
|
+
// Execute with timeout
|
|
393
|
+
const result = await Promise.race([
|
|
394
|
+
fn(pg, sandboxConsole),
|
|
395
|
+
new Promise((_, reject) =>
|
|
396
|
+
setTimeout(() => reject(new Error('Execution timeout')), timeout)
|
|
397
|
+
),
|
|
398
|
+
])
|
|
399
|
+
|
|
400
|
+
const duration = Date.now() - startTime
|
|
401
|
+
|
|
402
|
+
return {
|
|
403
|
+
content: [
|
|
404
|
+
{
|
|
405
|
+
type: 'text',
|
|
406
|
+
text: JSON.stringify(
|
|
407
|
+
{
|
|
408
|
+
success: true,
|
|
409
|
+
value: result,
|
|
410
|
+
logs: logs.map(([level, msg]) => ({ level, message: msg })),
|
|
411
|
+
duration,
|
|
412
|
+
},
|
|
413
|
+
null,
|
|
414
|
+
2
|
|
415
|
+
),
|
|
416
|
+
},
|
|
417
|
+
],
|
|
418
|
+
}
|
|
419
|
+
} catch (error) {
|
|
420
|
+
const duration = Date.now() - startTime
|
|
421
|
+
const message = error instanceof Error ? error.message : 'Unknown error'
|
|
422
|
+
|
|
423
|
+
return {
|
|
424
|
+
content: [
|
|
425
|
+
{
|
|
426
|
+
type: 'text',
|
|
427
|
+
text: JSON.stringify(
|
|
428
|
+
{
|
|
429
|
+
success: false,
|
|
430
|
+
error: message,
|
|
431
|
+
logs: logs.map(([level, msg]) => ({ level, message: msg })),
|
|
432
|
+
duration,
|
|
433
|
+
},
|
|
434
|
+
null,
|
|
435
|
+
2
|
|
436
|
+
),
|
|
437
|
+
},
|
|
438
|
+
],
|
|
439
|
+
isError: true,
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
/**
|
|
446
|
+
* Get all tool definitions
|
|
447
|
+
*/
|
|
448
|
+
export function getToolDefinitions(): Tool[] {
|
|
449
|
+
return [searchTool, fetchTool, doTool]
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
/**
|
|
453
|
+
* Create all tool handlers
|
|
454
|
+
*/
|
|
455
|
+
export function createToolHandlers(
|
|
456
|
+
executor: QueryExecutor,
|
|
457
|
+
options: { timeout?: number; authContext?: MCPAuthContext } = {}
|
|
458
|
+
): Record<string, (input: unknown) => Promise<ToolResponse>> {
|
|
459
|
+
return {
|
|
460
|
+
search: createSearchHandler(executor) as (input: unknown) => Promise<ToolResponse>,
|
|
461
|
+
fetch: createFetchHandler(executor) as (input: unknown) => Promise<ToolResponse>,
|
|
462
|
+
do: createDoHandler(executor, options) as (input: unknown) => Promise<ToolResponse>,
|
|
463
|
+
}
|
|
464
|
+
}
|
package/src/mcp/types.ts
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP Server Types for PostgresDO
|
|
3
|
+
*
|
|
4
|
+
* Type definitions for the MCP (Model Context Protocol) server implementation.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Query result from pg.query()
|
|
9
|
+
*/
|
|
10
|
+
export interface QueryResult<T = Record<string, unknown>> {
|
|
11
|
+
rows: T[]
|
|
12
|
+
rowCount: number
|
|
13
|
+
fields: Array<{
|
|
14
|
+
name: string
|
|
15
|
+
dataTypeID: number
|
|
16
|
+
}>
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Table information from pg.tables()
|
|
21
|
+
*/
|
|
22
|
+
export interface TableInfo {
|
|
23
|
+
name: string
|
|
24
|
+
schema: string
|
|
25
|
+
type: 'table' | 'view' | 'materialized_view'
|
|
26
|
+
rowCount?: number
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Column schema from pg.schema()
|
|
31
|
+
*/
|
|
32
|
+
export interface ColumnInfo {
|
|
33
|
+
name: string
|
|
34
|
+
type: string
|
|
35
|
+
nullable: boolean
|
|
36
|
+
default?: string
|
|
37
|
+
primaryKey: boolean
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Search result for MCP search tool
|
|
42
|
+
*/
|
|
43
|
+
export interface MCPSearchResult {
|
|
44
|
+
id: string
|
|
45
|
+
title: string
|
|
46
|
+
snippet?: string
|
|
47
|
+
score?: number
|
|
48
|
+
metadata?: Record<string, unknown>
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Fetch result for MCP fetch tool
|
|
53
|
+
*/
|
|
54
|
+
export interface MCPFetchResult {
|
|
55
|
+
content: string
|
|
56
|
+
contentType?: string
|
|
57
|
+
metadata?: Record<string, unknown>
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* The pg binding exposed to the do tool
|
|
62
|
+
*/
|
|
63
|
+
export interface PGBinding {
|
|
64
|
+
/**
|
|
65
|
+
* Execute a SQL query with optional parameters
|
|
66
|
+
*/
|
|
67
|
+
query<T = Record<string, unknown>>(sql: string, params?: unknown[]): Promise<QueryResult<T>>
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Execute a SQL statement (INSERT, UPDATE, DELETE)
|
|
71
|
+
*/
|
|
72
|
+
execute(sql: string, params?: unknown[]): Promise<{ rowCount: number }>
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Execute multiple statements in a transaction
|
|
76
|
+
*/
|
|
77
|
+
transaction<T>(fn: (tx: PGTransaction) => Promise<T>): Promise<T>
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* List all tables in the database
|
|
81
|
+
*/
|
|
82
|
+
tables(): Promise<TableInfo[]>
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Get schema information for a table
|
|
86
|
+
*/
|
|
87
|
+
schema(tableName: string): Promise<ColumnInfo[]>
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Transaction context for pg.transaction()
|
|
92
|
+
*/
|
|
93
|
+
export interface PGTransaction {
|
|
94
|
+
query<T = Record<string, unknown>>(sql: string, params?: unknown[]): Promise<QueryResult<T>>
|
|
95
|
+
execute(sql: string, params?: unknown[]): Promise<{ rowCount: number }>
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* MCP tool response format
|
|
100
|
+
*/
|
|
101
|
+
export interface ToolResponse {
|
|
102
|
+
content: Array<{ type: string; text: string }>
|
|
103
|
+
isError?: boolean
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Search tool input
|
|
108
|
+
*/
|
|
109
|
+
export interface SearchInput {
|
|
110
|
+
query: string
|
|
111
|
+
limit?: number
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Fetch tool input
|
|
116
|
+
*/
|
|
117
|
+
export interface FetchInput {
|
|
118
|
+
resource: string
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Do tool input
|
|
123
|
+
*/
|
|
124
|
+
export interface DoInput {
|
|
125
|
+
code: string
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Auth context from oauth.do JWT
|
|
130
|
+
*/
|
|
131
|
+
export interface MCPAuthContext {
|
|
132
|
+
userId: string
|
|
133
|
+
email?: string
|
|
134
|
+
scopes?: string[]
|
|
135
|
+
metadata?: Record<string, unknown>
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* MCP Server configuration
|
|
140
|
+
*/
|
|
141
|
+
export interface MCPServerConfig {
|
|
142
|
+
/** Database ID for the PostgresDO instance */
|
|
143
|
+
databaseId: string
|
|
144
|
+
/** Optional auth context from JWT */
|
|
145
|
+
authContext?: MCPAuthContext
|
|
146
|
+
/** Timeout for code execution in ms (default: 5000) */
|
|
147
|
+
timeout?: number
|
|
148
|
+
}
|