db4app-mcp-server 0.1.5 → 0.1.6

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.
Files changed (3) hide show
  1. package/README.md +61 -40
  2. package/dist/index.js +86 -181
  3. package/package.json +3 -2
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # db4app-mcp-server
2
2
 
3
- MCP server for db4.app - enables LLMs to interact with browser-based Postgres databases via Model Context Protocol.
3
+ MCP server for db4.app - enables LLMs to interact with browser-based Postgres databases via Model Context Protocol using the Postgres TCP protocol.
4
4
 
5
5
  ## Installation
6
6
 
@@ -16,6 +16,8 @@ npx db4app-mcp-server
16
16
 
17
17
  ## Usage
18
18
 
19
+ **Important**: This MCP server uses the **Postgres TCP protocol** to connect directly to your browser database, just like `psql` or other Postgres clients. It uses TLS encryption and password authentication.
20
+
19
21
  ### With LM Studio
20
22
 
21
23
  1. Locate your `mcp.json` file:
@@ -23,7 +25,12 @@ npx db4app-mcp-server
23
25
  - **Windows:** `%APPDATA%\LM Studio\mcp.json`
24
26
  - **Linux:** `~/.config/LM Studio/mcp.json`
25
27
 
26
- 2. Add this to the `mcpServers` object in your `mcp.json`:
28
+ 2. **Get your Connection ID and Auth Token**:
29
+ - Open the Database page in your browser
30
+ - Your **Connection ID** is displayed in the Connection Info section
31
+ - Your **Auth Token** is also shown in the Connection Info section (this is your password)
32
+
33
+ 3. Add this to the `mcpServers` object in your `mcp.json`:
27
34
 
28
35
  ```json
29
36
  {
@@ -35,32 +42,36 @@ npx db4app-mcp-server
35
42
  "db4app-mcp-server"
36
43
  ],
37
44
  "env": {
38
- "MCP_RELAY_HTTP_URL": "http://localhost:8787/query",
45
+ "MCP_POSTGRES_URL": "postgres://postgres:YOUR_AUTH_TOKEN@YOUR_CONNECTION_ID.pg.db4.app",
39
46
  "LM_STUDIO_EMBEDDING_URL": "http://localhost:1234/v1/embeddings",
40
47
  "LM_STUDIO_EMBEDDING_MODEL": "text-embedding-qwen3-embedding-4b",
41
- "MCP_ALLOWED_TABLES": "rag_mcp.documents",
42
- "MCP_PUBLIC_KEY": "YOUR_PUBLIC_KEY_HERE",
43
- "MCP_USE_ENCRYPTION": "true"
48
+ "MCP_SCHEMA": "rag_mcp"
44
49
  }
45
50
  }
46
51
  }
47
52
  }
48
53
  ```
49
54
 
50
- **Note**: Replace `YOUR_PUBLIC_KEY_HERE` with your actual public key from the Database page → Management tab → Encryption section.
55
+ **Note**:
56
+ - Replace `YOUR_AUTH_TOKEN` with your actual auth token from step 2
57
+ - Replace `YOUR_CONNECTION_ID` with your Connection ID from step 2
58
+ - The connection string format is: `postgres://postgres:AUTH_TOKEN@CONNECTION_ID.pg.db4.app`
59
+ - **Postgres TCP Protocol**: This server uses the standard Postgres wire protocol with TLS encryption, just like `psql` or DBeaver
51
60
 
52
- 3. **Install an Embedding Model** (Required for RAG features):
61
+ 4. **Install an Embedding Model** (Required for RAG features):
53
62
  - Open LM Studio → Search tab
54
63
  - Search for embedding models (e.g., "bge", "e5", "text-embedding")
55
64
  - Download and load an embedding-capable model
56
65
  - Update `LM_STUDIO_EMBEDDING_MODEL` in your `mcp.json` with the exact model name
57
66
  - Enable headless server mode in LM Studio
58
67
 
59
- 4. Restart LM Studio to load the configuration.
68
+ 5. Restart LM Studio to load the configuration.
60
69
 
61
70
  ### With Claude Desktop
62
71
 
63
- Add to your `claude_desktop_config.json`:
72
+ 1. **Get your Connection ID and Auth Token** (same as LM Studio setup above)
73
+
74
+ 2. Add to your `claude_desktop_config.json`:
64
75
 
65
76
  ```json
66
77
  {
@@ -72,63 +83,73 @@ Add to your `claude_desktop_config.json`:
72
83
  "db4app-mcp-server"
73
84
  ],
74
85
  "env": {
75
- "MCP_RELAY_HTTP_URL": "http://localhost:8787/query",
86
+ "MCP_POSTGRES_URL": "postgres://postgres:YOUR_AUTH_TOKEN@YOUR_CONNECTION_ID.pg.db4.app",
76
87
  "LM_STUDIO_EMBEDDING_URL": "http://localhost:1234/v1/embeddings",
77
88
  "LM_STUDIO_EMBEDDING_MODEL": "text-embedding-qwen3-embedding-4b",
78
- "MCP_ALLOWED_TABLES": "rag_mcp.documents",
79
- "MCP_PUBLIC_KEY": "YOUR_PUBLIC_KEY_HERE",
80
- "MCP_USE_ENCRYPTION": "true"
89
+ "MCP_SCHEMA": "rag_mcp"
81
90
  }
82
91
  }
83
92
  }
84
93
  }
85
94
  ```
86
95
 
87
- **Note**: Replace `YOUR_PUBLIC_KEY_HERE` with your actual public key from the Database page → Management tab → Encryption section.
96
+ **Note**:
97
+ - Replace `YOUR_AUTH_TOKEN` with your actual auth token
98
+ - Replace `YOUR_CONNECTION_ID` with your Connection ID
99
+ - The connection string format is: `postgres://postgres:AUTH_TOKEN@CONNECTION_ID.pg.db4.app`
88
100
 
89
101
  ## Configuration
90
102
 
91
103
  All configuration is done via environment variables:
92
104
 
93
- - `MCP_RELAY_HTTP_URL` - HTTP relay endpoint (default: `http://localhost:8787/query`)
105
+ - `MCP_POSTGRES_URL` - **Required**: Postgres connection string. Format: `postgres://postgres:AUTH_TOKEN@CONNECTION_ID.pg.db4.app`. Get your Connection ID and Auth Token from the Database page → Connection Info section.
106
+ - `MCP_CONNECTION_ID` - **Optional**: Browser connection ID. Used to construct connection URL if `MCP_POSTGRES_URL` is not provided (requires `MCP_AUTH_TOKEN`).
107
+ - `MCP_AUTH_TOKEN` - **Optional**: Auth token for authentication. Used to construct connection URL if `MCP_POSTGRES_URL` is not provided (requires `MCP_CONNECTION_ID`).
94
108
  - `LM_STUDIO_EMBEDDING_URL` - LM Studio embedding API endpoint (default: `http://localhost:1234/v1/embeddings`)
95
109
  - `LM_STUDIO_EMBEDDING_MODEL` - Optional model name for embeddings
96
- - `MCP_ALLOWED_TABLES` - **Required for recipes**: Comma-separated list of allowed tables (e.g., `"schema.table1,schema.table2"`). The relay validates all SQL queries against this whitelist. If not set, the MCP server can access all tables (for direct use, not recommended for recipes).
97
- - `MCP_PUBLIC_KEY` - **Optional**: Base64-encoded public key for end-to-end encryption. If not provided, the MCP server will attempt to fetch it from the relay's `/public-key` endpoint. Get your public key from the Database page → Management tab → Encryption section.
98
- - `MCP_USE_ENCRYPTION` - Enable end-to-end encryption (default: `true` if `MCP_PUBLIC_KEY` is set, otherwise `false`). When enabled, all SQL queries are encrypted with RSA-OAEP before being sent to the relay, ensuring the relay cannot decrypt them.
110
+ - `MCP_SCHEMA` - **Optional**: Schema name for RAG functions (`remember`, `search_memory`). Defaults to `public`. For recipes, set this to the recipe's schema (e.g., `rag_mcp`).
99
111
 
100
- ## Available Tools
112
+ **Note**: This MCP server uses the **Postgres TCP protocol** with TLS encryption, just like standard Postgres clients. No HTTP or RSA-OAEP encryption is used.
101
113
 
102
- - `query_database` - Execute SQL queries (respects `MCP_ALLOWED_TABLES` if set)
103
- - `list_tables` - List tables in the database (only shows whitelisted tables if `MCP_ALLOWED_TABLES` is set)
104
- - `remember` - Store information in memory with automatic embedding (requires `MCP_ALLOWED_TABLES` for recipe use)
105
- - `search_memory` - Search through stored memories using semantic similarity (requires `MCP_ALLOWED_TABLES` for recipe use)
106
-
107
- **Note**: When used with recipes, `MCP_ALLOWED_TABLES` must be set to restrict access to only the recipe's tables. The relay server validates all queries against this whitelist.
108
-
109
- ## End-to-End Encryption
114
+ ## Available Tools
110
115
 
111
- This MCP server supports end-to-end encryption of SQL queries using RSA-OAEP (2048-bit). When encryption is enabled:
116
+ - `query_database` - Execute SQL queries against the database
117
+ - `list_tables` - List tables in the database
118
+ - `remember` - Store information in memory with automatic embedding (uses `MCP_SCHEMA` for the target schema)
119
+ - `search_memory` - Search through stored memories using semantic similarity (uses `MCP_SCHEMA` for the target schema)
112
120
 
113
- - SQL queries are encrypted in the MCP server using the browser's public key
114
- - The relay server relays encrypted queries without being able to decrypt them
115
- - Only the browser (with the private key) can decrypt and execute queries
116
- - This ensures complete privacy: even the relay cannot see your SQL queries
121
+ ## Security
117
122
 
118
- To enable encryption:
119
- 1. Get your public key from the Database page Management tab Encryption section
120
- 2. Set `MCP_PUBLIC_KEY` to your public key (base64 string)
121
- 3. Set `MCP_USE_ENCRYPTION` to `"true"`
123
+ This MCP server uses the standard Postgres TCP protocol with:
124
+ - **TLS encryption**: All connections are encrypted using TLS (same as `psql` with SSL)
125
+ - **Password authentication**: Uses the auth token as the password
122
126
 
123
- If `MCP_PUBLIC_KEY` is not provided, the MCP server will automatically fetch it from the relay's `/public-key` endpoint.
127
+ **Note**: With password authentication, anyone with the auth token can connect with any Postgres client and access all tables. The security model relies on:
128
+ - Not sharing the auth token
129
+ - Schema isolation (recipes operate in their own schemas)
130
+ - Standard Postgres access control (if you have the password, you have access)
124
131
 
125
132
  ## Requirements
126
133
 
127
134
  - Node.js 18+
128
- - Postgres WASM relay running (default: `http://localhost:8787/query`)
135
+ - Browser tab with db4.app open and relay connected
136
+ - Connection ID and Auth Token from the Database page
129
137
  - For RAG features: LM Studio with embedding-capable model loaded
130
138
 
139
+ ## Connection String Format
140
+
141
+ The connection string follows the standard Postgres URL format:
142
+
143
+ ```
144
+ postgres://postgres:AUTH_TOKEN@CONNECTION_ID.pg.db4.app
145
+ ```
146
+
147
+ Where:
148
+ - `postgres` is the username (fixed)
149
+ - `AUTH_TOKEN` is your auth token (password)
150
+ - `CONNECTION_ID` is your connection ID (hostname)
151
+ - Port defaults to 5432 (standard Postgres port)
152
+
131
153
  ## License
132
154
 
133
155
  MIT
134
-
package/dist/index.js CHANGED
@@ -14658,57 +14658,98 @@ var StdioServerTransport = class {
14658
14658
  };
14659
14659
 
14660
14660
  // src/index.js
14661
- import { webcrypto } from "node:crypto";
14662
- var DEFAULT_RELAY_URL = process.env.MCP_RELAY_HTTP_URL ?? process.env.MCP_PROXY_HTTP_URL ?? "http://localhost:8787/query";
14661
+ import pg from "pg";
14662
+ var { Client } = pg;
14663
+ var DEFAULT_POSTGRES_URL = process.env.MCP_POSTGRES_URL ?? null;
14663
14664
  var DEFAULT_EMBEDDING_URL = process.env.LM_STUDIO_EMBEDDING_URL ?? "http://localhost:1234/v1/embeddings";
14664
14665
  var DEFAULT_EMBEDDING_MODEL = process.env.LM_STUDIO_EMBEDDING_MODEL ?? "";
14665
- var MCP_PUBLIC_KEY = process.env.MCP_PUBLIC_KEY ?? null;
14666
- var USE_ENCRYPTION = process.env.MCP_USE_ENCRYPTION === "true" || MCP_PUBLIC_KEY !== null;
14667
14666
  var MCP_CONNECTION_ID = process.env.MCP_CONNECTION_ID ?? null;
14667
+ var MCP_AUTH_TOKEN = process.env.MCP_AUTH_TOKEN ?? null;
14668
+ var MCP_SCHEMA = process.env.MCP_SCHEMA ?? "public";
14669
+ var clientPool = /* @__PURE__ */ new Map();
14670
+ function getClient(postgresUrl) {
14671
+ if (!postgresUrl) {
14672
+ throw new Error("Postgres connection URL is required. Set MCP_POSTGRES_URL or pass postgresUrl in the tool call.");
14673
+ }
14674
+ if (clientPool.has(postgresUrl)) {
14675
+ return clientPool.get(postgresUrl);
14676
+ }
14677
+ const client = new Client({
14678
+ connectionString: postgresUrl,
14679
+ ssl: postgresUrl.includes("db4.app") ? { rejectUnauthorized: true } : false
14680
+ });
14681
+ clientPool.set(postgresUrl, client);
14682
+ return client;
14683
+ }
14684
+ async function ensureConnected(client) {
14685
+ if (client._ending || client._ending === void 0) {
14686
+ await client.connect();
14687
+ }
14688
+ }
14668
14689
  var mcpServer = new McpServer({
14669
14690
  name: "db4app-bridge",
14670
- version: "0.1.0"
14691
+ version: "0.2.0"
14671
14692
  });
14672
14693
  var RunSqlSchema = external_exports.object({
14673
14694
  sql: external_exports.string().min(1).describe("SQL statement to execute against the browser-backed Postgres engine"),
14674
14695
  params: external_exports.array(external_exports.union([external_exports.string(), external_exports.number(), external_exports.boolean(), external_exports.null()])).describe("Optional positional parameters for the SQL statement").optional(),
14675
- relayUrl: external_exports.string().url().describe("Override the HTTP relay endpoint (defaults to MCP_RELAY_HTTP_URL or http://localhost:8787/query)").optional(),
14676
- connectionId: external_exports.string().uuid().describe("Browser connection ID (required for multi-user support, defaults to MCP_CONNECTION_ID)").optional()
14696
+ postgresUrl: external_exports.string().url().describe("Override the Postgres connection URL (defaults to MCP_POSTGRES_URL). Format: postgres://postgres:AUTH_TOKEN@CONNECTION_ID.pg.db4.app").optional(),
14697
+ connectionId: external_exports.string().uuid().describe("Browser connection ID (used to construct connection URL if postgresUrl not provided, defaults to MCP_CONNECTION_ID)").optional(),
14698
+ authToken: external_exports.string().describe("Auth token for authentication (used to construct connection URL if postgresUrl not provided, defaults to MCP_AUTH_TOKEN)").optional()
14677
14699
  });
14678
14700
  var ListTablesSchema = external_exports.object({
14679
14701
  schema: external_exports.string().describe("Schema name to filter by (default: public)").optional(),
14680
- relayUrl: external_exports.string().url().describe("Override the HTTP relay endpoint (defaults to MCP_RELAY_HTTP_URL or http://localhost:8787/query)").optional(),
14681
- connectionId: external_exports.string().uuid().describe("Browser connection ID (required for multi-user support, defaults to MCP_CONNECTION_ID)").optional()
14702
+ postgresUrl: external_exports.string().url().describe("Override the Postgres connection URL (defaults to MCP_POSTGRES_URL)").optional(),
14703
+ connectionId: external_exports.string().uuid().describe("Browser connection ID (used to construct connection URL if postgresUrl not provided)").optional(),
14704
+ authToken: external_exports.string().describe("Auth token for authentication (used to construct connection URL if postgresUrl not provided)").optional()
14682
14705
  });
14683
14706
  var RememberSchema = external_exports.object({
14684
14707
  content: external_exports.string().min(1).describe("Information to remember and store in memory. This will be automatically embedded and indexed for semantic search."),
14685
14708
  source: external_exports.string().describe('Optional source identifier for this memory (e.g. "conversation", "user_input", file path)').optional(),
14686
14709
  metadata: external_exports.record(external_exports.string(), external_exports.unknown()).describe("Optional metadata to store alongside the memory (JSON object).").optional(),
14687
- relayUrl: external_exports.string().url().describe("Override the HTTP relay endpoint (defaults to MCP_RELAY_HTTP_URL or http://localhost:8787/query)").optional(),
14710
+ postgresUrl: external_exports.string().url().describe("Override the Postgres connection URL (defaults to MCP_POSTGRES_URL)").optional(),
14688
14711
  embeddingUrl: external_exports.string().url().describe("Override the LM Studio embedding endpoint (defaults to LM_STUDIO_EMBEDDING_URL or http://localhost:1234/v1/embeddings)").optional(),
14689
14712
  embeddingModel: external_exports.string().describe("Optional model name for embedding generation (defaults to LM_STUDIO_EMBEDDING_MODEL)").optional(),
14690
- connectionId: external_exports.string().uuid().describe("Browser connection ID (required for multi-user support, defaults to MCP_CONNECTION_ID)").optional()
14713
+ connectionId: external_exports.string().uuid().describe("Browser connection ID (used to construct connection URL if postgresUrl not provided)").optional(),
14714
+ authToken: external_exports.string().describe("Auth token for authentication (used to construct connection URL if postgresUrl not provided)").optional()
14691
14715
  });
14692
14716
  var SemanticSearchSchema = external_exports.object({
14693
14717
  query: external_exports.string().min(1).describe("Natural language query for semantic search"),
14694
14718
  embedding: external_exports.array(external_exports.number()).nonempty().describe("Embedding for the query. If not provided, will be computed automatically using LM Studio.").optional(),
14695
14719
  topK: external_exports.number().int().positive().max(100).describe("Maximum number of matching chunks to return").default(5),
14696
14720
  source: external_exports.string().describe("Optional source identifier to filter results by").optional(),
14697
- relayUrl: external_exports.string().url().describe("Override the HTTP relay endpoint (defaults to MCP_RELAY_HTTP_URL or http://localhost:8787/query)").optional(),
14721
+ postgresUrl: external_exports.string().url().describe("Override the Postgres connection URL (defaults to MCP_POSTGRES_URL)").optional(),
14698
14722
  embeddingUrl: external_exports.string().url().describe("Override the LM Studio embedding endpoint (used only if embedding is not provided)").optional(),
14699
14723
  embeddingModel: external_exports.string().describe("Optional model name for embedding generation (used only if embedding is not provided)").optional(),
14700
- connectionId: external_exports.string().uuid().describe("Browser connection ID (required for multi-user support, defaults to MCP_CONNECTION_ID)").optional()
14724
+ connectionId: external_exports.string().uuid().describe("Browser connection ID (used to construct connection URL if postgresUrl not provided)").optional(),
14725
+ authToken: external_exports.string().describe("Auth token for authentication (used to construct connection URL if postgresUrl not provided)").optional()
14701
14726
  });
14727
+ function buildPostgresUrl(connectionId, authToken, providedUrl) {
14728
+ if (providedUrl) {
14729
+ return providedUrl;
14730
+ }
14731
+ const effectiveConnectionId = connectionId ?? MCP_CONNECTION_ID;
14732
+ const effectiveAuthToken = authToken ?? MCP_AUTH_TOKEN;
14733
+ if (!effectiveConnectionId || !effectiveAuthToken) {
14734
+ if (DEFAULT_POSTGRES_URL) {
14735
+ return DEFAULT_POSTGRES_URL;
14736
+ }
14737
+ throw new Error(
14738
+ "Postgres connection URL is required. Either provide postgresUrl, set MCP_POSTGRES_URL, or provide both connectionId and authToken (or set MCP_CONNECTION_ID and MCP_AUTH_TOKEN)."
14739
+ );
14740
+ }
14741
+ return `postgres://postgres:${encodeURIComponent(effectiveAuthToken)}@${effectiveConnectionId}.pg.db4.app`;
14742
+ }
14702
14743
  mcpServer.registerTool(
14703
14744
  "query_database",
14704
14745
  {
14705
- description: "Execute SQL queries against the database. Use this when you need to SELECT data, INSERT rows, UPDATE records, DELETE data, CREATE tables, or run any other SQL statement. This is the primary tool for interacting with the database schema and data. Note: This tool respects table permissions - you can only access tables specified in MCP_ALLOWED_TABLES.",
14746
+ description: "Execute SQL queries against the database. Use this when you need to SELECT data, INSERT rows, UPDATE records, DELETE data, CREATE tables, or run any other SQL statement. This is the primary tool for interacting with the database schema and data.",
14706
14747
  inputSchema: RunSqlSchema
14707
14748
  },
14708
- async ({ sql, params = [], relayUrl, connectionId }) => {
14749
+ async ({ sql, params = [], postgresUrl, connectionId, authToken }) => {
14709
14750
  try {
14710
- const allowedTables = process.env.MCP_ALLOWED_TABLES ? process.env.MCP_ALLOWED_TABLES.split(",").map((t) => t.trim()) : [];
14711
- const result = await executeSql(relayUrl ?? DEFAULT_RELAY_URL, sql, params, allowedTables, connectionId);
14751
+ const url = buildPostgresUrl(connectionId, authToken, postgresUrl);
14752
+ const result = await executeSql(url, sql, params);
14712
14753
  return formatToolResult(result);
14713
14754
  } catch (error) {
14714
14755
  return mcpServer.createToolError(error instanceof Error ? error.message : String(error));
@@ -14718,35 +14759,21 @@ mcpServer.registerTool(
14718
14759
  mcpServer.registerTool(
14719
14760
  "list_tables",
14720
14761
  {
14721
- description: "List tables in the database that you have access to. Use this when you need to discover what tables exist, explore the database schema, or find out what data is available before writing queries. Returns table names and their schemas. Note: Only shows tables you have permission to access.",
14762
+ description: "List tables in the database. Use this when you need to discover what tables exist, explore the database schema, or find out what data is available before writing queries. Returns table names and their schemas.",
14722
14763
  inputSchema: ListTablesSchema
14723
14764
  },
14724
- async ({ schema, relayUrl, connectionId }) => {
14725
- const allowedTables = process.env.MCP_ALLOWED_TABLES ? process.env.MCP_ALLOWED_TABLES.split(",").map((t) => t.trim()) : [];
14726
- let sql = `
14765
+ async ({ schema, postgresUrl, connectionId, authToken }) => {
14766
+ const sql = `
14727
14767
  SELECT table_schema, table_name
14728
14768
  FROM information_schema.tables
14729
14769
  WHERE table_type = 'BASE TABLE'
14730
14770
  AND table_schema NOT IN ('pg_catalog', 'information_schema')
14731
14771
  AND ($1::text IS NULL OR table_schema = $1::text)
14772
+ ORDER BY table_schema, table_name;
14732
14773
  `;
14733
- if (allowedTables.length > 0) {
14734
- const tableFilters = allowedTables.map((table, idx) => {
14735
- const [schemaName, tableName] = table.includes(".") ? table.split(".") : ["public", table];
14736
- return `(table_schema = $${idx + 2}::text AND table_name = $${idx + 3}::text)`;
14737
- });
14738
- sql += ` AND (${tableFilters.join(" OR ")})`;
14739
- }
14740
- sql += ` ORDER BY table_schema, table_name;`;
14741
- const params = [schema ?? null];
14742
- if (allowedTables.length > 0) {
14743
- allowedTables.forEach((table) => {
14744
- const [schemaName, tableName] = table.includes(".") ? table.split(".") : ["public", table];
14745
- params.push(schemaName, tableName);
14746
- });
14747
- }
14748
14774
  try {
14749
- const result = await executeSql(relayUrl ?? DEFAULT_RELAY_URL, sql, params, allowedTables, connectionId);
14775
+ const url = buildPostgresUrl(connectionId, authToken, postgresUrl);
14776
+ const result = await executeSql(url, sql, [schema ?? null]);
14750
14777
  return formatToolResult(result);
14751
14778
  } catch (error) {
14752
14779
  return mcpServer.createToolError(error instanceof Error ? error.message : String(error));
@@ -14759,18 +14786,18 @@ mcpServer.registerTool(
14759
14786
  description: 'Store information in long-term memory for later retrieval. Use this when the user tells you something they want you to remember, such as personal facts, preferences, context from previous conversations, or any information that should persist across sessions. Automatically generates embeddings and makes the content searchable via semantic search. Examples: "Remember that I prefer dark mode", "Remember that Max is from Sweden", "Remember my API key is...".',
14760
14787
  inputSchema: RememberSchema
14761
14788
  },
14762
- async ({ content, source = null, metadata = null, relayUrl, embeddingUrl, embeddingModel, connectionId }) => {
14789
+ async ({ content, source = null, metadata = null, postgresUrl, embeddingUrl, embeddingModel, connectionId, authToken }) => {
14763
14790
  try {
14764
14791
  const embedding = await generateEmbedding(content, embeddingUrl, embeddingModel);
14765
- const allowedTables = process.env.MCP_ALLOWED_TABLES ? process.env.MCP_ALLOWED_TABLES.split(",").map((t) => t.trim()) : [];
14766
- const schema = allowedTables.length > 0 && allowedTables[0].includes(".") ? allowedTables[0].split(".")[0] : "public";
14792
+ const schema = MCP_SCHEMA;
14767
14793
  const sql = `
14768
14794
  INSERT INTO ${schema}.documents (source, chunk_index, content, metadata, embedding)
14769
14795
  VALUES ($1, $2, $3, $4::jsonb, $5::double precision[])
14770
14796
  RETURNING id, source, chunk_index, content;
14771
14797
  `;
14772
14798
  const params = [source, null, content, metadata ? JSON.stringify(metadata) : null, embedding];
14773
- const result = await executeSql(relayUrl ?? DEFAULT_RELAY_URL, sql, params, allowedTables, connectionId);
14799
+ const url = buildPostgresUrl(connectionId, authToken, postgresUrl);
14800
+ const result = await executeSql(url, sql, params);
14774
14801
  const insertedRow = result.rows[0];
14775
14802
  return {
14776
14803
  content: [
@@ -14797,14 +14824,13 @@ mcpServer.registerTool(
14797
14824
  description: 'Search through previously stored memories and information using semantic similarity. Use this when the user asks about something you might have remembered earlier, or when you need to recall information from past conversations. Examples: "Where is Max from?", "What did I tell you about my preferences?", "What information do you have about X?". Automatically computes embeddings if not provided. Returns the most relevant stored information ranked by similarity.',
14798
14825
  inputSchema: SemanticSearchSchema
14799
14826
  },
14800
- async ({ query, embedding, topK, source = null, relayUrl, embeddingUrl, embeddingModel, connectionId }) => {
14827
+ async ({ query, embedding, topK, source = null, postgresUrl, embeddingUrl, embeddingModel, connectionId, authToken }) => {
14801
14828
  try {
14802
14829
  let queryEmbedding = embedding;
14803
14830
  if (!queryEmbedding) {
14804
14831
  queryEmbedding = await generateEmbedding(query, embeddingUrl, embeddingModel);
14805
14832
  }
14806
- const allowedTables = process.env.MCP_ALLOWED_TABLES ? process.env.MCP_ALLOWED_TABLES.split(",").map((t) => t.trim()) : [];
14807
- const schema = allowedTables.length > 0 && allowedTables[0].includes(".") ? allowedTables[0].split(".")[0] : "public";
14833
+ const schema = MCP_SCHEMA;
14808
14834
  const sql = `
14809
14835
  SELECT
14810
14836
  id,
@@ -14820,7 +14846,8 @@ mcpServer.registerTool(
14820
14846
  LIMIT $3::int;
14821
14847
  `;
14822
14848
  const params = [queryEmbedding, source, topK];
14823
- const result = await executeSql(relayUrl ?? DEFAULT_RELAY_URL, sql, params, allowedTables, connectionId);
14849
+ const url = buildPostgresUrl(connectionId, authToken, postgresUrl);
14850
+ const result = await executeSql(url, sql, params);
14824
14851
  result.command = "RAG_SEMANTIC_SEARCH";
14825
14852
  return formatToolResult(result);
14826
14853
  } catch (error) {
@@ -14873,144 +14900,22 @@ Troubleshooting:
14873
14900
  }
14874
14901
  throw new Error("Unexpected embedding response format from LM Studio");
14875
14902
  }
14876
- async function importPublicKey(publicKeyString) {
14877
- const buffer = Buffer.from(publicKeyString, "base64");
14878
- return webcrypto.subtle.importKey(
14879
- "spki",
14880
- buffer,
14881
- {
14882
- name: "RSA-OAEP",
14883
- hash: "SHA-256"
14884
- },
14885
- true,
14886
- ["encrypt"]
14887
- );
14888
- }
14889
- async function encryptWithPublicKey(publicKey, data) {
14890
- const encoder = new TextEncoder();
14891
- const dataBuffer = encoder.encode(data);
14892
- const encrypted = await webcrypto.subtle.encrypt(
14893
- {
14894
- name: "RSA-OAEP"
14895
- },
14896
- publicKey,
14897
- dataBuffer
14898
- );
14899
- return Buffer.from(encrypted).toString("base64");
14900
- }
14901
- async function fetchPublicKey(relayUrl, connectionId) {
14903
+ async function executeSql(postgresUrl, sql, params = []) {
14904
+ const client = getClient(postgresUrl);
14902
14905
  try {
14903
- const relayUrlObj = new URL(relayUrl);
14904
- const pathParts = relayUrlObj.pathname.split("/").filter(Boolean);
14905
- let effectiveConnectionId = connectionId;
14906
- if (pathParts.length >= 2 && (pathParts[1] === "query" || pathParts[1] === "public-key")) {
14907
- effectiveConnectionId = pathParts[0];
14908
- } else if (pathParts[0] === "query" && pathParts[1]) {
14909
- effectiveConnectionId = pathParts[1];
14910
- } else if (pathParts[0] === "public-key" && pathParts[1]) {
14911
- effectiveConnectionId = pathParts[1];
14912
- } else if (!effectiveConnectionId) {
14913
- effectiveConnectionId = MCP_CONNECTION_ID;
14914
- }
14915
- if (!effectiveConnectionId) {
14916
- throw new Error("Connection ID is required to fetch public key. Include it in MCP_RELAY_HTTP_URL as /{connectionId}/query or set MCP_CONNECTION_ID");
14917
- }
14918
- relayUrlObj.pathname = `/${effectiveConnectionId}/public-key`;
14919
- const publicKeyUrl = relayUrlObj.toString();
14920
- const response = await fetch(publicKeyUrl);
14921
- if (!response.ok) {
14922
- throw new Error(`Failed to fetch public key: ${response.status}`);
14923
- }
14924
- const data = await response.json();
14925
- return data.publicKey;
14926
- } catch (err) {
14927
- throw new Error(`Failed to fetch public key from relay: ${err.message}`);
14928
- }
14929
- }
14930
- var publicKeyCache = /* @__PURE__ */ new Map();
14931
- async function getPublicKey(relayUrl, connectionId) {
14932
- const cacheKey = connectionId ?? "default";
14933
- if (publicKeyCache.has(cacheKey)) {
14934
- return publicKeyCache.get(cacheKey);
14935
- }
14936
- let publicKeyString = MCP_PUBLIC_KEY;
14937
- if (!publicKeyString) {
14938
- if (!connectionId && !MCP_CONNECTION_ID) {
14939
- throw new Error("Connection ID is required to fetch public key. Set MCP_CONNECTION_ID or pass connectionId.");
14940
- }
14941
- publicKeyString = await fetchPublicKey(relayUrl, connectionId ?? MCP_CONNECTION_ID);
14942
- }
14943
- const publicKey = await importPublicKey(publicKeyString);
14944
- publicKeyCache.set(cacheKey, publicKey);
14945
- return publicKey;
14946
- }
14947
- async function executeSql(relayUrl, sql, params, allowedTables, connectionId) {
14948
- if (!relayUrl) {
14949
- throw new Error("Relay URL is not set. Provide MCP_RELAY_HTTP_URL or pass relayUrl in the tool call.");
14950
- }
14951
- const relayUrlObj = new URL(relayUrl);
14952
- const pathParts = relayUrlObj.pathname.split("/").filter(Boolean);
14953
- let queryUrl = relayUrl;
14954
- let effectiveConnectionId = connectionId;
14955
- if (pathParts.length >= 2 && pathParts[1] === "query") {
14956
- effectiveConnectionId = pathParts[0];
14957
- queryUrl = relayUrl;
14958
- } else if (pathParts[0] === "query" && pathParts[1]) {
14959
- effectiveConnectionId = pathParts[1];
14960
- queryUrl = relayUrl;
14961
- } else {
14962
- effectiveConnectionId = connectionId ?? MCP_CONNECTION_ID;
14963
- if (!effectiveConnectionId) {
14964
- throw new Error(
14965
- "Connection ID is required. Either include it in MCP_RELAY_HTTP_URL as /{connectionId}/query, or set MCP_CONNECTION_ID environment variable, or pass connectionId in the tool call."
14966
- );
14967
- }
14968
- relayUrlObj.pathname = `/${effectiveConnectionId}/query`;
14969
- queryUrl = relayUrlObj.toString();
14970
- }
14971
- let requestBody;
14972
- if (USE_ENCRYPTION) {
14973
- try {
14974
- const publicKey = await getPublicKey(relayUrl, effectiveConnectionId);
14975
- const payloadToEncrypt = JSON.stringify({ sql, params });
14976
- const encryptedPayload = await encryptWithPublicKey(publicKey, payloadToEncrypt);
14977
- requestBody = {
14978
- encrypted: true,
14979
- encryptedPayload
14980
- };
14981
- if (allowedTables && allowedTables.length > 0) {
14982
- requestBody.allowedTables = allowedTables;
14983
- }
14984
- } catch (encryptErr) {
14985
- console.error("[MCP] Encryption failed, falling back to unencrypted:", encryptErr.message);
14986
- requestBody = { sql, params };
14987
- if (allowedTables && allowedTables.length > 0) {
14988
- requestBody.allowedTables = allowedTables;
14989
- }
14990
- }
14991
- } else {
14992
- requestBody = { sql, params };
14993
- if (allowedTables && allowedTables.length > 0) {
14994
- requestBody.allowedTables = allowedTables;
14995
- }
14996
- }
14997
- console.log("[MCP] Executing SQL:", { queryUrl, effectiveConnectionId, sql: sql.substring(0, 100) + "..." });
14998
- const response = await fetch(queryUrl, {
14999
- method: "POST",
15000
- headers: { "content-type": "application/json" },
15001
- body: JSON.stringify(requestBody)
15002
- });
15003
- if (!response.ok) {
15004
- const errorData = await response.json().catch(() => ({}));
15005
- const errorMessage = errorData.error ?? `Relay request failed (${response.status})`;
15006
- console.error("[MCP] Relay error:", { status: response.status, statusText: response.statusText, error: errorMessage, url: queryUrl });
15007
- throw new Error(errorMessage);
15008
- }
15009
- const payload = await response.json();
15010
- if (!payload.ok) {
15011
- throw new Error(payload.error ?? "Relay returned an error");
14906
+ await ensureConnected(client);
14907
+ console.log("[MCP] Executing SQL:", { postgresUrl: postgresUrl.replace(/:[^:@]+@/, ":****@"), sql: sql.substring(0, 100) + "..." });
14908
+ const result = await client.query(sql, params);
14909
+ return {
14910
+ columns: result.fields?.map((f) => f.name) ?? [],
14911
+ rows: result.rows ?? [],
14912
+ command: result.command ?? "QUERY",
14913
+ rowCount: result.rowCount ?? 0
14914
+ };
14915
+ } catch (error) {
14916
+ console.error("[MCP] Postgres error:", error.message);
14917
+ throw error;
15012
14918
  }
15013
- return payload.result ?? { columns: [], rows: [], command: "QUERY", rowCount: 0 };
15014
14919
  }
15015
14920
  function formatToolResult(result) {
15016
14921
  const { columns = [], rows = [], command = "QUERY", rowCount = rows.length ?? 0 } = result;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "db4app-mcp-server",
3
- "version": "0.1.5",
3
+ "version": "0.1.6",
4
4
  "description": "MCP server for db4.app - enables LLMs to interact with browser-based Postgres databases via Model Context Protocol",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -13,7 +13,7 @@
13
13
  "LICENSE"
14
14
  ],
15
15
  "scripts": {
16
- "build": "esbuild src/index.js --bundle --platform=node --format=esm --outfile=dist/index.js --external:node:*",
16
+ "build": "esbuild src/index.js --bundle --platform=node --format=esm --outfile=dist/index.js --external:node:* --external:pg --external:pg-native",
17
17
  "lint": "eslint src --ext .js",
18
18
  "prepublishOnly": "npm run build",
19
19
  "start": "node dist/index.js"
@@ -40,6 +40,7 @@
40
40
  },
41
41
  "dependencies": {
42
42
  "@modelcontextprotocol/sdk": "^1.22.0",
43
+ "pg": "^8.11.3",
43
44
  "zod": "^3.25.76"
44
45
  },
45
46
  "devDependencies": {