ainative-postgres-mcp 1.0.0
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/.claude/CLAUDE.md +53 -0
- package/.cody/CODY.md +53 -0
- package/LICENSE +21 -0
- package/README.md +177 -0
- package/index.js +696 -0
- package/package.json +94 -0
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# AINative Postgres MCP — Usage Guide
|
|
2
|
+
|
|
3
|
+
This MCP server is an enhanced fork of @modelcontextprotocol/server-postgres that auto-provisions a managed PostgreSQL instance with pgvector. Zero-config database for AI agents.
|
|
4
|
+
|
|
5
|
+
## Available Tools (5)
|
|
6
|
+
|
|
7
|
+
| Tool | Description |
|
|
8
|
+
|------|-------------|
|
|
9
|
+
| `query` | Execute read-only SQL queries (SELECT, EXPLAIN, etc.) |
|
|
10
|
+
| `list_tables` | List all tables in the database |
|
|
11
|
+
| `describe_table` | Get table schema: columns, types, constraints, indexes |
|
|
12
|
+
| `create_table` | Create a new table with SQL DDL |
|
|
13
|
+
| `insert` | Insert rows and return the inserted data |
|
|
14
|
+
|
|
15
|
+
## Behavior Rules
|
|
16
|
+
|
|
17
|
+
1. **Use `query` for all read operations** — it runs in a READ ONLY transaction for safety.
|
|
18
|
+
2. **Use `list_tables` first** — before querying, check what tables exist.
|
|
19
|
+
3. **Use `describe_table` before inserting** — verify column names and types.
|
|
20
|
+
4. **Use `create_table` for DDL** — only CREATE TABLE statements are allowed (no DROP, ALTER).
|
|
21
|
+
5. **Use `insert` for writes** — pass structured row objects, not raw SQL INSERT.
|
|
22
|
+
6. **pgvector is available** — create `vector(N)` columns for embeddings and use `<=>` for similarity search.
|
|
23
|
+
|
|
24
|
+
## Auto-Provisioning
|
|
25
|
+
|
|
26
|
+
No DATABASE_URL? The server auto-provisions a free managed PostgreSQL instance and prints a **claim URL**. Surface this URL to the user so they can take ownership of their database.
|
|
27
|
+
|
|
28
|
+
## MCP Config
|
|
29
|
+
|
|
30
|
+
```json
|
|
31
|
+
{
|
|
32
|
+
"mcpServers": {
|
|
33
|
+
"postgres": {
|
|
34
|
+
"command": "npx",
|
|
35
|
+
"args": ["-y", "ainative-postgres-mcp"]
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
With existing database:
|
|
42
|
+
|
|
43
|
+
```json
|
|
44
|
+
{
|
|
45
|
+
"mcpServers": {
|
|
46
|
+
"postgres": {
|
|
47
|
+
"command": "npx",
|
|
48
|
+
"args": ["-y", "ainative-postgres-mcp"],
|
|
49
|
+
"env": { "DATABASE_URL": "postgresql://user:pass@host:5432/db" }
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
```
|
package/.cody/CODY.md
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# AINative Postgres MCP — Cody Usage Guide
|
|
2
|
+
|
|
3
|
+
This MCP server is an enhanced fork of @modelcontextprotocol/server-postgres that auto-provisions a managed PostgreSQL instance with pgvector. Zero-config database for AI agents.
|
|
4
|
+
|
|
5
|
+
## Available Tools (5)
|
|
6
|
+
|
|
7
|
+
| Tool | Description |
|
|
8
|
+
|------|-------------|
|
|
9
|
+
| `query` | Execute read-only SQL queries (SELECT, EXPLAIN, etc.) |
|
|
10
|
+
| `list_tables` | List all tables in the database |
|
|
11
|
+
| `describe_table` | Get table schema: columns, types, constraints, indexes |
|
|
12
|
+
| `create_table` | Create a new table with SQL DDL |
|
|
13
|
+
| `insert` | Insert rows and return the inserted data |
|
|
14
|
+
|
|
15
|
+
## Behavior Rules
|
|
16
|
+
|
|
17
|
+
1. **Use `query` for all read operations** — it runs in a READ ONLY transaction for safety.
|
|
18
|
+
2. **Use `list_tables` first** — before querying, check what tables exist.
|
|
19
|
+
3. **Use `describe_table` before inserting** — verify column names and types.
|
|
20
|
+
4. **Use `create_table` for DDL** — only CREATE TABLE statements are allowed (no DROP, ALTER).
|
|
21
|
+
5. **Use `insert` for writes** — pass structured row objects, not raw SQL INSERT.
|
|
22
|
+
6. **pgvector is available** — create `vector(N)` columns for embeddings and use `<=>` for similarity search.
|
|
23
|
+
|
|
24
|
+
## Auto-Provisioning
|
|
25
|
+
|
|
26
|
+
No DATABASE_URL? The server auto-provisions a free managed PostgreSQL instance and prints a **claim URL**. Surface this URL to the user so they can take ownership of their database.
|
|
27
|
+
|
|
28
|
+
## MCP Config
|
|
29
|
+
|
|
30
|
+
```json
|
|
31
|
+
{
|
|
32
|
+
"mcpServers": {
|
|
33
|
+
"postgres": {
|
|
34
|
+
"command": "npx",
|
|
35
|
+
"args": ["-y", "ainative-postgres-mcp"]
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
With existing database:
|
|
42
|
+
|
|
43
|
+
```json
|
|
44
|
+
{
|
|
45
|
+
"mcpServers": {
|
|
46
|
+
"postgres": {
|
|
47
|
+
"command": "npx",
|
|
48
|
+
"args": ["-y", "ainative-postgres-mcp"],
|
|
49
|
+
"env": { "DATABASE_URL": "postgresql://user:pass@host:5432/db" }
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
```
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 AINative Studio
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
# ainative-postgres-mcp
|
|
2
|
+
|
|
3
|
+
Zero-config PostgreSQL MCP server with auto-provisioning. Drop-in replacement for `@modelcontextprotocol/server-postgres` — no `DATABASE_URL` needed.
|
|
4
|
+
|
|
5
|
+
The official `@modelcontextprotocol/server-postgres` requires you to bring your own Postgres database and pass a connection string. This fork **auto-provisions a managed PostgreSQL instance** with pgvector on first run. Agents get a database instantly with zero configuration.
|
|
6
|
+
|
|
7
|
+
## Quick Start
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npx ainative-postgres-mcp
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
That's it. On first run:
|
|
14
|
+
1. A free managed PostgreSQL instance is provisioned
|
|
15
|
+
2. pgvector extension is enabled automatically
|
|
16
|
+
3. Connection details are saved to `~/.ainative/postgres-config.json`, `.env`, and `.mcp.json`
|
|
17
|
+
4. A claim URL is printed so you can take ownership of the database
|
|
18
|
+
|
|
19
|
+
## MCP Configuration
|
|
20
|
+
|
|
21
|
+
### Claude Code / Claude Desktop
|
|
22
|
+
|
|
23
|
+
Add to your `claude_desktop_config.json` or `.mcp.json`:
|
|
24
|
+
|
|
25
|
+
```json
|
|
26
|
+
{
|
|
27
|
+
"mcpServers": {
|
|
28
|
+
"postgres": {
|
|
29
|
+
"command": "npx",
|
|
30
|
+
"args": ["-y", "ainative-postgres-mcp"]
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### With existing database
|
|
37
|
+
|
|
38
|
+
```json
|
|
39
|
+
{
|
|
40
|
+
"mcpServers": {
|
|
41
|
+
"postgres": {
|
|
42
|
+
"command": "npx",
|
|
43
|
+
"args": ["-y", "ainative-postgres-mcp"],
|
|
44
|
+
"env": {
|
|
45
|
+
"DATABASE_URL": "postgresql://user:pass@host:5432/mydb"
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### Cursor
|
|
53
|
+
|
|
54
|
+
Add to `.cursor/mcp.json`:
|
|
55
|
+
|
|
56
|
+
```json
|
|
57
|
+
{
|
|
58
|
+
"mcpServers": {
|
|
59
|
+
"postgres": {
|
|
60
|
+
"command": "npx",
|
|
61
|
+
"args": ["-y", "ainative-postgres-mcp"]
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Windsurf
|
|
68
|
+
|
|
69
|
+
Add to `~/.windsurf/mcp.json`:
|
|
70
|
+
|
|
71
|
+
```json
|
|
72
|
+
{
|
|
73
|
+
"mcpServers": {
|
|
74
|
+
"postgres": {
|
|
75
|
+
"command": "npx",
|
|
76
|
+
"args": ["-y", "ainative-postgres-mcp"]
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Tools
|
|
83
|
+
|
|
84
|
+
| Tool | Description |
|
|
85
|
+
|------|-------------|
|
|
86
|
+
| `query` | Execute read-only SQL queries (runs in READ ONLY transaction) |
|
|
87
|
+
| `list_tables` | List all tables with schema, name, and type |
|
|
88
|
+
| `describe_table` | Get full table schema: columns, types, constraints, indexes |
|
|
89
|
+
| `create_table` | Create a new table with SQL DDL |
|
|
90
|
+
| `insert` | Insert rows into a table (returns inserted rows) |
|
|
91
|
+
|
|
92
|
+
### Comparison with official server-postgres
|
|
93
|
+
|
|
94
|
+
| Feature | `@modelcontextprotocol/server-postgres` | `ainative-postgres-mcp` |
|
|
95
|
+
|---------|----------------------------------------|------------------------|
|
|
96
|
+
| Read queries | Yes | Yes |
|
|
97
|
+
| Write queries | No | Yes (`create_table`, `insert`) |
|
|
98
|
+
| Schema inspection | Via resources | Via `describe_table` tool |
|
|
99
|
+
| Table listing | No | Yes (`list_tables`) |
|
|
100
|
+
| Auto-provisioning | No | Yes |
|
|
101
|
+
| pgvector | Manual setup | Auto-enabled |
|
|
102
|
+
| Zero config | No (requires DATABASE_URL) | Yes |
|
|
103
|
+
| Free tier | N/A | Yes |
|
|
104
|
+
|
|
105
|
+
## How Auto-Provisioning Works
|
|
106
|
+
|
|
107
|
+
```
|
|
108
|
+
Start
|
|
109
|
+
|
|
|
110
|
+
v
|
|
111
|
+
DATABASE_URL set? ----yes----> Connect directly
|
|
112
|
+
|
|
|
113
|
+
no
|
|
114
|
+
v
|
|
115
|
+
~/.ainative/postgres-config.json exists? ----yes----> Load and connect
|
|
116
|
+
|
|
|
117
|
+
no
|
|
118
|
+
v
|
|
119
|
+
.mcp.json has DATABASE_URL? ----yes----> Load and connect
|
|
120
|
+
|
|
|
121
|
+
no
|
|
122
|
+
v
|
|
123
|
+
POST /api/v1/public/instant-db (get API key)
|
|
124
|
+
|
|
|
125
|
+
v
|
|
126
|
+
POST /api/v1/zerodb/postgres/provision (get Postgres)
|
|
127
|
+
|
|
|
128
|
+
v
|
|
129
|
+
Save to ~/.ainative/postgres-config.json + .env + .mcp.json
|
|
130
|
+
|
|
|
131
|
+
v
|
|
132
|
+
Connect and enable pgvector
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
## pgvector Support
|
|
136
|
+
|
|
137
|
+
pgvector is automatically enabled on provisioned instances. You can use vector columns in your tables:
|
|
138
|
+
|
|
139
|
+
```sql
|
|
140
|
+
CREATE TABLE documents (
|
|
141
|
+
id SERIAL PRIMARY KEY,
|
|
142
|
+
content TEXT,
|
|
143
|
+
embedding vector(1536)
|
|
144
|
+
);
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
And run similarity searches:
|
|
148
|
+
|
|
149
|
+
```sql
|
|
150
|
+
SELECT content, embedding <=> '[0.1, 0.2, ...]'::vector AS distance
|
|
151
|
+
FROM documents
|
|
152
|
+
ORDER BY distance
|
|
153
|
+
LIMIT 10;
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
## Environment Variables
|
|
157
|
+
|
|
158
|
+
| Variable | Description | Required |
|
|
159
|
+
|----------|-------------|----------|
|
|
160
|
+
| `DATABASE_URL` | PostgreSQL connection string | No (auto-provisioned if missing) |
|
|
161
|
+
| `ZERODB_API_KEY` | ZeroDB API key (for provisioning) | No (auto-created) |
|
|
162
|
+
| `ZERODB_PROJECT_ID` | ZeroDB project ID | No (auto-created) |
|
|
163
|
+
| `ZERODB_API_URL` | ZeroDB API base URL | No (defaults to `https://api.ainative.studio`) |
|
|
164
|
+
|
|
165
|
+
## Free Tier
|
|
166
|
+
|
|
167
|
+
Auto-provisioned instances include:
|
|
168
|
+
- Managed PostgreSQL with pgvector
|
|
169
|
+
- Shared compute (suitable for development and light production)
|
|
170
|
+
- Automatic backups
|
|
171
|
+
- SSL encryption
|
|
172
|
+
|
|
173
|
+
Sign up at [ainative.studio](https://ainative.studio) to claim your instance and unlock higher limits.
|
|
174
|
+
|
|
175
|
+
## License
|
|
176
|
+
|
|
177
|
+
MIT
|
package/index.js
ADDED
|
@@ -0,0 +1,696 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* AINative Postgres MCP Server
|
|
5
|
+
*
|
|
6
|
+
* Zero-config PostgreSQL MCP server with auto-provisioning.
|
|
7
|
+
* Drop-in replacement for @modelcontextprotocol/server-postgres that
|
|
8
|
+
* auto-provisions a managed Postgres instance via ZeroDB on first run.
|
|
9
|
+
*
|
|
10
|
+
* Tools:
|
|
11
|
+
* query — Execute read-only SQL queries
|
|
12
|
+
* describe_table — Get table schema (columns, types, constraints)
|
|
13
|
+
* list_tables — List all tables in the database
|
|
14
|
+
* create_table — Create a new table with SQL DDL
|
|
15
|
+
* insert — Insert rows into a table
|
|
16
|
+
*
|
|
17
|
+
* On startup:
|
|
18
|
+
* 1. Check for DATABASE_URL env var
|
|
19
|
+
* 2. If missing, auto-provision via ZeroDB Postgres API
|
|
20
|
+
* 3. Connect using pg (node-postgres)
|
|
21
|
+
* 4. Enable pgvector extension automatically
|
|
22
|
+
*
|
|
23
|
+
* Usage:
|
|
24
|
+
* npx ainative-postgres-mcp # Auto-provisions on first run
|
|
25
|
+
* DATABASE_URL=postgres://... npx ainative-postgres-mcp # Use existing database
|
|
26
|
+
*
|
|
27
|
+
* Refs #3947
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
31
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
32
|
+
import {
|
|
33
|
+
ListToolsRequestSchema,
|
|
34
|
+
CallToolRequestSchema
|
|
35
|
+
} from '@modelcontextprotocol/sdk/types.js';
|
|
36
|
+
import dotenv from 'dotenv';
|
|
37
|
+
import pg from 'pg';
|
|
38
|
+
import { createRequire } from 'module';
|
|
39
|
+
|
|
40
|
+
const require = createRequire(import.meta.url);
|
|
41
|
+
const { version: PKG_VERSION } = require('./package.json');
|
|
42
|
+
|
|
43
|
+
dotenv.config();
|
|
44
|
+
|
|
45
|
+
const SERVER_NAME = 'ainative-postgres-mcp';
|
|
46
|
+
const ZERODB_API_URL = process.env.ZERODB_API_URL || 'https://api.ainative.studio';
|
|
47
|
+
|
|
48
|
+
// ─────────────────────────────────────────────────────────────────
|
|
49
|
+
// Auto-provisioning: get or create a managed Postgres instance
|
|
50
|
+
// ─────────────────────────────────────────────────────────────────
|
|
51
|
+
|
|
52
|
+
async function httpPost(url, body, headers = {}) {
|
|
53
|
+
const https = await import('https');
|
|
54
|
+
const urlObj = new URL(url);
|
|
55
|
+
return new Promise((resolve, reject) => {
|
|
56
|
+
const data = JSON.stringify(body);
|
|
57
|
+
const req = https.default.request({
|
|
58
|
+
hostname: urlObj.hostname,
|
|
59
|
+
port: urlObj.port || 443,
|
|
60
|
+
path: urlObj.pathname,
|
|
61
|
+
method: 'POST',
|
|
62
|
+
headers: {
|
|
63
|
+
'Content-Type': 'application/json',
|
|
64
|
+
'Content-Length': Buffer.byteLength(data),
|
|
65
|
+
...headers
|
|
66
|
+
}
|
|
67
|
+
}, (res) => {
|
|
68
|
+
let responseData = '';
|
|
69
|
+
res.on('data', chunk => responseData += chunk);
|
|
70
|
+
res.on('end', () => {
|
|
71
|
+
if (res.statusCode >= 200 && res.statusCode < 300) {
|
|
72
|
+
try { resolve(JSON.parse(responseData)); }
|
|
73
|
+
catch { resolve(responseData); }
|
|
74
|
+
} else {
|
|
75
|
+
reject(new Error(`HTTP ${res.statusCode}: ${responseData}`));
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
req.on('error', reject);
|
|
80
|
+
req.write(data);
|
|
81
|
+
req.end();
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
async function httpGet(url, headers = {}) {
|
|
86
|
+
const https = await import('https');
|
|
87
|
+
const urlObj = new URL(url);
|
|
88
|
+
return new Promise((resolve, reject) => {
|
|
89
|
+
const req = https.default.request({
|
|
90
|
+
hostname: urlObj.hostname,
|
|
91
|
+
port: urlObj.port || 443,
|
|
92
|
+
path: urlObj.pathname,
|
|
93
|
+
method: 'GET',
|
|
94
|
+
headers: { 'Content-Type': 'application/json', ...headers }
|
|
95
|
+
}, (res) => {
|
|
96
|
+
let data = '';
|
|
97
|
+
res.on('data', chunk => data += chunk);
|
|
98
|
+
res.on('end', () => {
|
|
99
|
+
if (res.statusCode >= 200 && res.statusCode < 300) {
|
|
100
|
+
try { resolve(JSON.parse(data)); }
|
|
101
|
+
catch { resolve(data); }
|
|
102
|
+
} else {
|
|
103
|
+
reject(new Error(`HTTP ${res.statusCode}: ${data}`));
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
req.on('error', reject);
|
|
108
|
+
req.end();
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
async function loadSavedConfig() {
|
|
113
|
+
const { existsSync, readFileSync } = await import('fs');
|
|
114
|
+
const { join } = await import('path');
|
|
115
|
+
const os = await import('os');
|
|
116
|
+
|
|
117
|
+
const configPath = join(os.default.homedir(), '.ainative', 'postgres-config.json');
|
|
118
|
+
if (existsSync(configPath)) {
|
|
119
|
+
try {
|
|
120
|
+
const config = JSON.parse(readFileSync(configPath, 'utf-8'));
|
|
121
|
+
if (config.database_url) return config;
|
|
122
|
+
} catch (_) {}
|
|
123
|
+
}
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
async function saveConfig(config) {
|
|
128
|
+
const { existsSync, mkdirSync, writeFileSync, readFileSync, appendFileSync } = await import('fs');
|
|
129
|
+
const { join } = await import('path');
|
|
130
|
+
const os = await import('os');
|
|
131
|
+
|
|
132
|
+
// Save to ~/.ainative/postgres-config.json
|
|
133
|
+
const ainativeDir = join(os.default.homedir(), '.ainative');
|
|
134
|
+
if (!existsSync(ainativeDir)) mkdirSync(ainativeDir, { recursive: true });
|
|
135
|
+
const configPath = join(ainativeDir, 'postgres-config.json');
|
|
136
|
+
writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n');
|
|
137
|
+
|
|
138
|
+
// Append to .env in cwd
|
|
139
|
+
const envPath = join(process.cwd(), '.env');
|
|
140
|
+
const envBlock = `\n# PostgreSQL (auto-provisioned by ainative-postgres-mcp)\nDATABASE_URL=${config.database_url}\nZERODB_API_KEY=${config.api_key}\nZERODB_PROJECT_ID=${config.project_id}\n`;
|
|
141
|
+
if (existsSync(envPath)) {
|
|
142
|
+
if (!readFileSync(envPath, 'utf-8').includes('DATABASE_URL')) {
|
|
143
|
+
appendFileSync(envPath, envBlock);
|
|
144
|
+
}
|
|
145
|
+
} else {
|
|
146
|
+
writeFileSync(envPath, envBlock.trimStart());
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Write .mcp.json
|
|
150
|
+
const mcpPath = join(process.cwd(), '.mcp.json');
|
|
151
|
+
const mcpEntry = {
|
|
152
|
+
'ainative-postgres': {
|
|
153
|
+
command: 'npx',
|
|
154
|
+
args: ['-y', 'ainative-postgres-mcp'],
|
|
155
|
+
env: {
|
|
156
|
+
DATABASE_URL: config.database_url,
|
|
157
|
+
ZERODB_API_KEY: config.api_key,
|
|
158
|
+
ZERODB_PROJECT_ID: config.project_id
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
};
|
|
162
|
+
let existing = {};
|
|
163
|
+
if (existsSync(mcpPath)) { try { existing = JSON.parse(readFileSync(mcpPath, 'utf-8')); } catch (_) {} }
|
|
164
|
+
writeFileSync(mcpPath, JSON.stringify({
|
|
165
|
+
...existing,
|
|
166
|
+
mcpServers: { ...(existing.mcpServers || {}), ...mcpEntry }
|
|
167
|
+
}, null, 2) + '\n');
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
async function autoProvision() {
|
|
171
|
+
console.error('\n No DATABASE_URL found — auto-provisioning a managed PostgreSQL instance...\n');
|
|
172
|
+
|
|
173
|
+
// Step 1: Get API key via instant-db
|
|
174
|
+
let apiKey, projectId;
|
|
175
|
+
const existingKey = process.env.ZERODB_API_KEY;
|
|
176
|
+
const existingProject = process.env.ZERODB_PROJECT_ID;
|
|
177
|
+
|
|
178
|
+
if (existingKey && existingProject) {
|
|
179
|
+
apiKey = existingKey;
|
|
180
|
+
projectId = existingProject;
|
|
181
|
+
console.error(' Using existing ZeroDB credentials');
|
|
182
|
+
} else {
|
|
183
|
+
console.error(' Step 1/2: Creating free ZeroDB account...');
|
|
184
|
+
const creds = await httpPost(`${ZERODB_API_URL}/api/v1/public/instant-db`, {
|
|
185
|
+
agree_terms: true
|
|
186
|
+
});
|
|
187
|
+
apiKey = creds.api_key;
|
|
188
|
+
projectId = creds.project_id;
|
|
189
|
+
console.error(` Account created: ${projectId}`);
|
|
190
|
+
if (creds.claim_url) {
|
|
191
|
+
console.error(`\n *** Claim your account: ${creds.claim_url} ***\n`);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Step 2: Provision Postgres instance
|
|
196
|
+
console.error(' Step 2/2: Provisioning PostgreSQL instance...');
|
|
197
|
+
const authHeaders = {
|
|
198
|
+
'X-API-Key': apiKey,
|
|
199
|
+
'X-Project-ID': projectId
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
const pgResult = await httpPost(
|
|
203
|
+
`${ZERODB_API_URL}/api/v1/zerodb/postgres/provision`,
|
|
204
|
+
{ enable_pgvector: true },
|
|
205
|
+
authHeaders
|
|
206
|
+
);
|
|
207
|
+
|
|
208
|
+
const databaseUrl = pgResult.connection_string || pgResult.database_url || pgResult.dsn;
|
|
209
|
+
if (!databaseUrl) {
|
|
210
|
+
throw new Error('Provisioning succeeded but no connection string returned');
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const config = {
|
|
214
|
+
database_url: databaseUrl,
|
|
215
|
+
api_key: apiKey,
|
|
216
|
+
project_id: projectId,
|
|
217
|
+
provisioned_at: new Date().toISOString()
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
await saveConfig(config);
|
|
221
|
+
|
|
222
|
+
console.error(' PostgreSQL provisioned successfully!');
|
|
223
|
+
console.error(` Connection saved to ~/.ainative/postgres-config.json`);
|
|
224
|
+
console.error(` Also saved to .env and .mcp.json\n`);
|
|
225
|
+
|
|
226
|
+
return databaseUrl;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
async function resolveDatabaseUrl() {
|
|
230
|
+
// 1. Check env var
|
|
231
|
+
if (process.env.DATABASE_URL) {
|
|
232
|
+
console.error(' Using DATABASE_URL from environment');
|
|
233
|
+
return process.env.DATABASE_URL;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// 2. Check saved config
|
|
237
|
+
const saved = await loadSavedConfig();
|
|
238
|
+
if (saved?.database_url) {
|
|
239
|
+
console.error(' Using saved config from ~/.ainative/postgres-config.json');
|
|
240
|
+
return saved.database_url;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// 3. Scan .mcp.json for credentials
|
|
244
|
+
const { existsSync, readFileSync } = await import('fs');
|
|
245
|
+
const { join, dirname } = await import('path');
|
|
246
|
+
|
|
247
|
+
let dir = process.cwd();
|
|
248
|
+
for (let i = 0; i < 6; i++) {
|
|
249
|
+
const mcpPath = join(dir, '.mcp.json');
|
|
250
|
+
if (existsSync(mcpPath)) {
|
|
251
|
+
try {
|
|
252
|
+
const mcp = JSON.parse(readFileSync(mcpPath, 'utf-8'));
|
|
253
|
+
const servers = mcp.mcpServers || {};
|
|
254
|
+
const pgServer = servers['ainative-postgres']
|
|
255
|
+
|| servers['postgres']
|
|
256
|
+
|| servers['postgres-mcp']
|
|
257
|
+
|| Object.values(servers).find(s => (s.args || []).join(' ').includes('postgres'));
|
|
258
|
+
const env = pgServer?.env;
|
|
259
|
+
if (env?.DATABASE_URL) {
|
|
260
|
+
console.error(` Loaded DATABASE_URL from ${mcpPath}`);
|
|
261
|
+
return env.DATABASE_URL;
|
|
262
|
+
}
|
|
263
|
+
} catch (_) {}
|
|
264
|
+
}
|
|
265
|
+
const parent = dirname(dir);
|
|
266
|
+
if (parent === dir) break;
|
|
267
|
+
dir = parent;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// 4. Auto-provision
|
|
271
|
+
return await autoProvision();
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// ─────────────────────────────────────────────────────────────────
|
|
275
|
+
// Postgres Client Manager
|
|
276
|
+
// ─────────────────────────────────────────────────────────────────
|
|
277
|
+
|
|
278
|
+
class PostgresManager {
|
|
279
|
+
constructor(databaseUrl) {
|
|
280
|
+
this.databaseUrl = databaseUrl;
|
|
281
|
+
this.pool = null;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
async connect() {
|
|
285
|
+
this.pool = new pg.default.Pool({
|
|
286
|
+
connectionString: this.databaseUrl,
|
|
287
|
+
max: 5,
|
|
288
|
+
idleTimeoutMillis: 30000,
|
|
289
|
+
connectionTimeoutMillis: 10000,
|
|
290
|
+
ssl: this.databaseUrl.includes('sslmode=require') || this.databaseUrl.includes('ssl=true')
|
|
291
|
+
? { rejectUnauthorized: false }
|
|
292
|
+
: undefined
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
// Test connection
|
|
296
|
+
const client = await this.pool.connect();
|
|
297
|
+
try {
|
|
298
|
+
await client.query('SELECT 1');
|
|
299
|
+
} finally {
|
|
300
|
+
client.release();
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
async enablePgvector() {
|
|
305
|
+
try {
|
|
306
|
+
await this.pool.query('CREATE EXTENSION IF NOT EXISTS vector');
|
|
307
|
+
return true;
|
|
308
|
+
} catch (err) {
|
|
309
|
+
// pgvector may not be available — non-fatal
|
|
310
|
+
console.error(` pgvector extension: ${err.message.includes('could not') ? 'not available' : err.message}`);
|
|
311
|
+
return false;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
async query(sql) {
|
|
316
|
+
const client = await this.pool.connect();
|
|
317
|
+
try {
|
|
318
|
+
// Execute in a read-only transaction for safety
|
|
319
|
+
await client.query('BEGIN TRANSACTION READ ONLY');
|
|
320
|
+
const result = await client.query(sql);
|
|
321
|
+
await client.query('COMMIT');
|
|
322
|
+
return {
|
|
323
|
+
rows: result.rows,
|
|
324
|
+
rowCount: result.rowCount,
|
|
325
|
+
fields: result.fields?.map(f => ({ name: f.name, dataTypeID: f.dataTypeID }))
|
|
326
|
+
};
|
|
327
|
+
} catch (err) {
|
|
328
|
+
await client.query('ROLLBACK').catch(() => {});
|
|
329
|
+
throw err;
|
|
330
|
+
} finally {
|
|
331
|
+
client.release();
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
async execute(sql, params = []) {
|
|
336
|
+
const client = await this.pool.connect();
|
|
337
|
+
try {
|
|
338
|
+
const result = await client.query(sql, params);
|
|
339
|
+
return {
|
|
340
|
+
rows: result.rows,
|
|
341
|
+
rowCount: result.rowCount,
|
|
342
|
+
command: result.command
|
|
343
|
+
};
|
|
344
|
+
} catch (err) {
|
|
345
|
+
throw err;
|
|
346
|
+
} finally {
|
|
347
|
+
client.release();
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
async listTables() {
|
|
352
|
+
const result = await this.pool.query(`
|
|
353
|
+
SELECT table_schema, table_name, table_type
|
|
354
|
+
FROM information_schema.tables
|
|
355
|
+
WHERE table_schema NOT IN ('pg_catalog', 'information_schema')
|
|
356
|
+
ORDER BY table_schema, table_name
|
|
357
|
+
`);
|
|
358
|
+
return result.rows;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
async describeTable(tableName) {
|
|
362
|
+
// Parse schema.table if provided
|
|
363
|
+
let schema = 'public';
|
|
364
|
+
let table = tableName;
|
|
365
|
+
if (tableName.includes('.')) {
|
|
366
|
+
[schema, table] = tableName.split('.');
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
const columnsResult = await this.pool.query(`
|
|
370
|
+
SELECT
|
|
371
|
+
c.column_name,
|
|
372
|
+
c.data_type,
|
|
373
|
+
c.character_maximum_length,
|
|
374
|
+
c.is_nullable,
|
|
375
|
+
c.column_default,
|
|
376
|
+
c.udt_name
|
|
377
|
+
FROM information_schema.columns c
|
|
378
|
+
WHERE c.table_schema = $1 AND c.table_name = $2
|
|
379
|
+
ORDER BY c.ordinal_position
|
|
380
|
+
`, [schema, table]);
|
|
381
|
+
|
|
382
|
+
const constraintsResult = await this.pool.query(`
|
|
383
|
+
SELECT
|
|
384
|
+
tc.constraint_name,
|
|
385
|
+
tc.constraint_type,
|
|
386
|
+
kcu.column_name
|
|
387
|
+
FROM information_schema.table_constraints tc
|
|
388
|
+
JOIN information_schema.key_column_usage kcu
|
|
389
|
+
ON tc.constraint_name = kcu.constraint_name
|
|
390
|
+
AND tc.table_schema = kcu.table_schema
|
|
391
|
+
WHERE tc.table_schema = $1 AND tc.table_name = $2
|
|
392
|
+
ORDER BY tc.constraint_type, kcu.column_name
|
|
393
|
+
`, [schema, table]);
|
|
394
|
+
|
|
395
|
+
const indexesResult = await this.pool.query(`
|
|
396
|
+
SELECT
|
|
397
|
+
indexname,
|
|
398
|
+
indexdef
|
|
399
|
+
FROM pg_indexes
|
|
400
|
+
WHERE schemaname = $1 AND tablename = $2
|
|
401
|
+
ORDER BY indexname
|
|
402
|
+
`, [schema, table]);
|
|
403
|
+
|
|
404
|
+
return {
|
|
405
|
+
table: tableName,
|
|
406
|
+
schema: schema,
|
|
407
|
+
columns: columnsResult.rows,
|
|
408
|
+
constraints: constraintsResult.rows,
|
|
409
|
+
indexes: indexesResult.rows
|
|
410
|
+
};
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
async createTable(sql) {
|
|
414
|
+
// Validate it's a CREATE TABLE statement
|
|
415
|
+
const normalized = sql.trim().toUpperCase();
|
|
416
|
+
if (!normalized.startsWith('CREATE TABLE') && !normalized.startsWith('CREATE UNLOGGED TABLE')) {
|
|
417
|
+
throw new Error('SQL must be a CREATE TABLE statement');
|
|
418
|
+
}
|
|
419
|
+
return await this.execute(sql);
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
async insert(table, rows) {
|
|
423
|
+
if (!rows || rows.length === 0) {
|
|
424
|
+
throw new Error('No rows provided');
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
const columns = Object.keys(rows[0]);
|
|
428
|
+
const values = [];
|
|
429
|
+
const placeholders = [];
|
|
430
|
+
let paramIndex = 1;
|
|
431
|
+
|
|
432
|
+
for (const row of rows) {
|
|
433
|
+
const rowPlaceholders = [];
|
|
434
|
+
for (const col of columns) {
|
|
435
|
+
values.push(row[col] !== undefined ? row[col] : null);
|
|
436
|
+
rowPlaceholders.push(`$${paramIndex++}`);
|
|
437
|
+
}
|
|
438
|
+
placeholders.push(`(${rowPlaceholders.join(', ')})`);
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
// Sanitize table name — allow schema.table format
|
|
442
|
+
const safeTable = table.split('.').map(part => `"${part.replace(/"/g, '""')}"`).join('.');
|
|
443
|
+
const safeColumns = columns.map(c => `"${c.replace(/"/g, '""')}"`).join(', ');
|
|
444
|
+
|
|
445
|
+
const sql = `INSERT INTO ${safeTable} (${safeColumns}) VALUES ${placeholders.join(', ')} RETURNING *`;
|
|
446
|
+
return await this.execute(sql, values);
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
async close() {
|
|
450
|
+
if (this.pool) {
|
|
451
|
+
await this.pool.end();
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
// ─────────────────────────────────────────────────────────────────
|
|
457
|
+
// Tool definitions
|
|
458
|
+
// ─────────────────────────────────────────────────────────────────
|
|
459
|
+
|
|
460
|
+
const TOOLS = [
|
|
461
|
+
{
|
|
462
|
+
name: 'query',
|
|
463
|
+
description: 'Execute a read-only SQL query against the connected PostgreSQL database. All queries run inside a READ ONLY transaction for safety. Use this for SELECT, EXPLAIN, and other read operations.',
|
|
464
|
+
inputSchema: {
|
|
465
|
+
type: 'object',
|
|
466
|
+
properties: {
|
|
467
|
+
sql: {
|
|
468
|
+
type: 'string',
|
|
469
|
+
description: 'The SQL query to execute (read-only)'
|
|
470
|
+
}
|
|
471
|
+
},
|
|
472
|
+
required: ['sql']
|
|
473
|
+
}
|
|
474
|
+
},
|
|
475
|
+
{
|
|
476
|
+
name: 'list_tables',
|
|
477
|
+
description: 'List all tables in the connected PostgreSQL database, including schema, table name, and type (BASE TABLE or VIEW).',
|
|
478
|
+
inputSchema: {
|
|
479
|
+
type: 'object',
|
|
480
|
+
properties: {},
|
|
481
|
+
required: []
|
|
482
|
+
}
|
|
483
|
+
},
|
|
484
|
+
{
|
|
485
|
+
name: 'describe_table',
|
|
486
|
+
description: 'Get the full schema of a table including columns (name, type, nullable, default), constraints (primary key, foreign key, unique), and indexes. Supports schema.table format.',
|
|
487
|
+
inputSchema: {
|
|
488
|
+
type: 'object',
|
|
489
|
+
properties: {
|
|
490
|
+
table_name: {
|
|
491
|
+
type: 'string',
|
|
492
|
+
description: 'The table name to describe. Use "schema.table" for non-public schemas.'
|
|
493
|
+
}
|
|
494
|
+
},
|
|
495
|
+
required: ['table_name']
|
|
496
|
+
}
|
|
497
|
+
},
|
|
498
|
+
{
|
|
499
|
+
name: 'create_table',
|
|
500
|
+
description: 'Create a new table in the database using a SQL CREATE TABLE statement. Supports all PostgreSQL column types including pgvector vector columns.',
|
|
501
|
+
inputSchema: {
|
|
502
|
+
type: 'object',
|
|
503
|
+
properties: {
|
|
504
|
+
sql: {
|
|
505
|
+
type: 'string',
|
|
506
|
+
description: 'The CREATE TABLE SQL statement'
|
|
507
|
+
}
|
|
508
|
+
},
|
|
509
|
+
required: ['sql']
|
|
510
|
+
}
|
|
511
|
+
},
|
|
512
|
+
{
|
|
513
|
+
name: 'insert',
|
|
514
|
+
description: 'Insert one or more rows into a table. Rows are provided as an array of objects where keys are column names. Returns the inserted rows.',
|
|
515
|
+
inputSchema: {
|
|
516
|
+
type: 'object',
|
|
517
|
+
properties: {
|
|
518
|
+
table: {
|
|
519
|
+
type: 'string',
|
|
520
|
+
description: 'The table name to insert into. Use "schema.table" for non-public schemas.'
|
|
521
|
+
},
|
|
522
|
+
rows: {
|
|
523
|
+
type: 'array',
|
|
524
|
+
items: {
|
|
525
|
+
type: 'object',
|
|
526
|
+
description: 'A row object where keys are column names and values are the data to insert'
|
|
527
|
+
},
|
|
528
|
+
description: 'Array of row objects to insert'
|
|
529
|
+
}
|
|
530
|
+
},
|
|
531
|
+
required: ['table', 'rows']
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
];
|
|
535
|
+
|
|
536
|
+
// ─────────────────────────────────────────────────────────────────
|
|
537
|
+
// Tool execution router
|
|
538
|
+
// ─────────────────────────────────────────────────────────────────
|
|
539
|
+
|
|
540
|
+
async function executeTool(name, args, manager) {
|
|
541
|
+
switch (name) {
|
|
542
|
+
case 'query':
|
|
543
|
+
return await manager.query(args.sql);
|
|
544
|
+
case 'list_tables':
|
|
545
|
+
return await manager.listTables();
|
|
546
|
+
case 'describe_table':
|
|
547
|
+
return await manager.describeTable(args.table_name);
|
|
548
|
+
case 'create_table':
|
|
549
|
+
return await manager.createTable(args.sql);
|
|
550
|
+
case 'insert':
|
|
551
|
+
return await manager.insert(args.table, args.rows);
|
|
552
|
+
default:
|
|
553
|
+
return null;
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
// ─────────────────────────────────────────────────────────────────
|
|
558
|
+
// Main server
|
|
559
|
+
// ─────────────────────────────────────────────────────────────────
|
|
560
|
+
|
|
561
|
+
async function main() {
|
|
562
|
+
console.error('\n');
|
|
563
|
+
console.error(' ██████╗ ██████╗ ███████╗████████╗ ██████╗ ██████╗ ███████╗███████╗');
|
|
564
|
+
console.error(' ██╔══██╗██╔═══██╗██╔════╝╚══██╔══╝██╔════╝ ██╔══██╗██╔════╝██╔════╝');
|
|
565
|
+
console.error(' ██████╔╝██║ ██║███████╗ ██║ ██║ ███╗██████╔╝█████╗ ███████╗');
|
|
566
|
+
console.error(' ██╔═══╝ ██║ ██║╚════██║ ██║ ██║ ██║██╔══██╗██╔══╝ ╚════██║');
|
|
567
|
+
console.error(' ██║ ╚██████╔╝███████║ ██║ ╚██████╔╝██║ ██║███████╗███████║');
|
|
568
|
+
console.error(' ╚═╝ ╚═════╝ ╚══════╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝╚══════╝╚══════╝');
|
|
569
|
+
console.error('\n AINative Postgres — Zero-Config Database for AI Agents');
|
|
570
|
+
console.error('\n==========================================================');
|
|
571
|
+
console.error(` Postgres MCP Server v${PKG_VERSION}`);
|
|
572
|
+
console.error(' Drop-in replacement for @modelcontextprotocol/server-postgres');
|
|
573
|
+
console.error(' Managed PostgreSQL with pgvector + auto-provisioning');
|
|
574
|
+
console.error('==========================================================\n');
|
|
575
|
+
|
|
576
|
+
// Resolve database URL (env > saved config > .mcp.json > auto-provision)
|
|
577
|
+
let databaseUrl;
|
|
578
|
+
try {
|
|
579
|
+
databaseUrl = await resolveDatabaseUrl();
|
|
580
|
+
} catch (err) {
|
|
581
|
+
console.error(` Failed to resolve database: ${err.message}`);
|
|
582
|
+
console.error(' Set DATABASE_URL or run: npx zerodb-cli init');
|
|
583
|
+
console.error(' Or sign up: https://ainative.studio\n');
|
|
584
|
+
process.exit(1);
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
// Connect to Postgres
|
|
588
|
+
const manager = new PostgresManager(databaseUrl);
|
|
589
|
+
try {
|
|
590
|
+
await manager.connect();
|
|
591
|
+
console.error(' Connected to PostgreSQL');
|
|
592
|
+
} catch (err) {
|
|
593
|
+
console.error(` Connection failed: ${err.message}`);
|
|
594
|
+
console.error(' Check your DATABASE_URL and try again.\n');
|
|
595
|
+
process.exit(1);
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
// Enable pgvector
|
|
599
|
+
const hasVector = await manager.enablePgvector();
|
|
600
|
+
if (hasVector) {
|
|
601
|
+
console.error(' pgvector extension: enabled');
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
// Show table count
|
|
605
|
+
try {
|
|
606
|
+
const tables = await manager.listTables();
|
|
607
|
+
console.error(` Tables: ${tables.length} found`);
|
|
608
|
+
} catch (_) {}
|
|
609
|
+
|
|
610
|
+
console.error(` Tools: ${TOOLS.length} available (query, list_tables, describe_table, create_table, insert)\n`);
|
|
611
|
+
|
|
612
|
+
// Create MCP server
|
|
613
|
+
const server = new Server(
|
|
614
|
+
{ name: SERVER_NAME, version: PKG_VERSION },
|
|
615
|
+
{ capabilities: { tools: {} } }
|
|
616
|
+
);
|
|
617
|
+
|
|
618
|
+
// List tools handler
|
|
619
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
620
|
+
tools: TOOLS.map(tool => ({
|
|
621
|
+
name: tool.name,
|
|
622
|
+
description: tool.description,
|
|
623
|
+
inputSchema: tool.inputSchema
|
|
624
|
+
}))
|
|
625
|
+
}));
|
|
626
|
+
|
|
627
|
+
// Call tool handler
|
|
628
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
629
|
+
const { name, arguments: args } = request.params;
|
|
630
|
+
const tool = TOOLS.find(t => t.name === name);
|
|
631
|
+
|
|
632
|
+
if (!tool) {
|
|
633
|
+
return {
|
|
634
|
+
content: [{ type: 'text', text: JSON.stringify({ error: `Unknown tool: ${name}` }) }],
|
|
635
|
+
isError: true
|
|
636
|
+
};
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
try {
|
|
640
|
+
const result = await executeTool(name, args || {}, manager);
|
|
641
|
+
|
|
642
|
+
if (result === null) {
|
|
643
|
+
return {
|
|
644
|
+
content: [{ type: 'text', text: JSON.stringify({ error: `Tool ${name} not implemented` }) }],
|
|
645
|
+
isError: true
|
|
646
|
+
};
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
return {
|
|
650
|
+
content: [{ type: 'text', text: JSON.stringify(result, null, 2) }]
|
|
651
|
+
};
|
|
652
|
+
} catch (err) {
|
|
653
|
+
console.error(` [${SERVER_NAME}] Tool ${name} error:`, err.message);
|
|
654
|
+
return {
|
|
655
|
+
content: [{
|
|
656
|
+
type: 'text',
|
|
657
|
+
text: JSON.stringify({
|
|
658
|
+
error: err.message,
|
|
659
|
+
tool: name,
|
|
660
|
+
hint: err.message.includes('ECONNREFUSED') || err.message.includes('timeout')
|
|
661
|
+
? 'Database connection failed. Check DATABASE_URL or re-provision with: npx ainative-postgres-mcp'
|
|
662
|
+
: undefined
|
|
663
|
+
})
|
|
664
|
+
}],
|
|
665
|
+
isError: true
|
|
666
|
+
};
|
|
667
|
+
}
|
|
668
|
+
});
|
|
669
|
+
|
|
670
|
+
// Connect via stdio transport
|
|
671
|
+
const transport = new StdioServerTransport();
|
|
672
|
+
await server.connect(transport);
|
|
673
|
+
console.error(` MCP Server connected and ready (${TOOLS.length} tools)\n`);
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
// Only start server when run directly (not imported for testing)
|
|
677
|
+
const isMainModule = process.argv[1] && (
|
|
678
|
+
process.argv[1].endsWith('ainative-postgres-mcp/index.js') ||
|
|
679
|
+
process.argv[1].includes('ainative-postgres-mcp')
|
|
680
|
+
) && !process.argv[1].includes('test');
|
|
681
|
+
|
|
682
|
+
if (isMainModule) {
|
|
683
|
+
// Graceful shutdown
|
|
684
|
+
process.on('SIGINT', () => { console.error('\n Shutting down...'); process.exit(0); });
|
|
685
|
+
process.on('SIGTERM', () => { console.error('\n Shutting down...'); process.exit(0); });
|
|
686
|
+
|
|
687
|
+
main().catch(err => {
|
|
688
|
+
console.error(`[${SERVER_NAME}] Fatal error:`, err.message);
|
|
689
|
+
console.error('\n Set DATABASE_URL or run: npx zerodb-cli init');
|
|
690
|
+
console.error(' Or sign up: https://ainative.studio\n');
|
|
691
|
+
process.exit(1);
|
|
692
|
+
});
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
// Export for testing
|
|
696
|
+
export { PostgresManager, TOOLS, resolveDatabaseUrl, autoProvision };
|
package/package.json
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "ainative-postgres-mcp",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Zero-config Postgres MCP server with auto-provisioning. Drop-in replacement for @modelcontextprotocol/server-postgres — no DATABASE_URL needed. Auto-provisions a managed PostgreSQL instance with pgvector on first run.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"ainative-postgres-mcp": "./index.js"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"start": "node index.js",
|
|
12
|
+
"test": "node --test tests/*.test.js"
|
|
13
|
+
},
|
|
14
|
+
"keywords": [
|
|
15
|
+
"mcp",
|
|
16
|
+
"mcp-server",
|
|
17
|
+
"mcp-server-hosting",
|
|
18
|
+
"model-context-protocol",
|
|
19
|
+
"model-context-protocol-server",
|
|
20
|
+
"modelcontextprotocol",
|
|
21
|
+
"postgres",
|
|
22
|
+
"postgresql",
|
|
23
|
+
"database",
|
|
24
|
+
"auto-provisioning",
|
|
25
|
+
"zerodb",
|
|
26
|
+
"ainative",
|
|
27
|
+
"managed-postgres",
|
|
28
|
+
"serverless-postgres",
|
|
29
|
+
"free-database",
|
|
30
|
+
"pgvector",
|
|
31
|
+
"vector-search",
|
|
32
|
+
"sql",
|
|
33
|
+
"query",
|
|
34
|
+
"schema",
|
|
35
|
+
"claude",
|
|
36
|
+
"claude-code",
|
|
37
|
+
"claude-desktop",
|
|
38
|
+
"claude-ai-agents",
|
|
39
|
+
"cursor",
|
|
40
|
+
"windsurf",
|
|
41
|
+
"codex",
|
|
42
|
+
"n8n-ai",
|
|
43
|
+
"server-postgres",
|
|
44
|
+
"server-postgres-alternative",
|
|
45
|
+
"neon-alternative",
|
|
46
|
+
"supabase-alternative",
|
|
47
|
+
"planetscale-alternative",
|
|
48
|
+
"zero-config",
|
|
49
|
+
"instant-database",
|
|
50
|
+
"ai-database",
|
|
51
|
+
"agent-database",
|
|
52
|
+
"agentic-ai",
|
|
53
|
+
"ai-agents",
|
|
54
|
+
"ai-tool-calling",
|
|
55
|
+
"production-ready",
|
|
56
|
+
"drop-in-replacement",
|
|
57
|
+
"free-tier",
|
|
58
|
+
"cloud-database"
|
|
59
|
+
],
|
|
60
|
+
"author": {
|
|
61
|
+
"name": "AINative Studio",
|
|
62
|
+
"email": "toby@rely.ventures",
|
|
63
|
+
"url": "https://ainative.studio"
|
|
64
|
+
},
|
|
65
|
+
"license": "MIT",
|
|
66
|
+
"repository": {
|
|
67
|
+
"type": "git",
|
|
68
|
+
"url": "https://github.com/AINative-Studio/ainative-postgres-mcp.git"
|
|
69
|
+
},
|
|
70
|
+
"bugs": {
|
|
71
|
+
"url": "https://github.com/AINative-Studio/ainative-postgres-mcp/issues"
|
|
72
|
+
},
|
|
73
|
+
"homepage": "https://github.com/AINative-Studio/ainative-postgres-mcp#readme",
|
|
74
|
+
"dependencies": {
|
|
75
|
+
"@modelcontextprotocol/sdk": "^1.24.0",
|
|
76
|
+
"dotenv": "^16.4.0",
|
|
77
|
+
"pg": "^8.13.0"
|
|
78
|
+
},
|
|
79
|
+
"files": [
|
|
80
|
+
"index.js",
|
|
81
|
+
"README.md",
|
|
82
|
+
"LICENSE",
|
|
83
|
+
".claude/",
|
|
84
|
+
".cody/"
|
|
85
|
+
],
|
|
86
|
+
"publishConfig": {
|
|
87
|
+
"access": "public",
|
|
88
|
+
"registry": "https://registry.npmjs.org/"
|
|
89
|
+
},
|
|
90
|
+
"engines": {
|
|
91
|
+
"node": ">=18.0.0",
|
|
92
|
+
"npm": ">=9.0.0"
|
|
93
|
+
}
|
|
94
|
+
}
|