ajan-sql 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Bora Kilicoglu
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,121 @@
1
+ # ajan-sql
2
+
3
+ AI-safe MCP server for schema-aware, read-only SQL access.
4
+
5
+ ## Overview
6
+
7
+ `ajan-sql` is an npm package for running an MCP server over stdio with a PostgreSQL backend.
8
+
9
+ The project is designed as:
10
+
11
+ > psql + schema awareness + AI-safe guard layer
12
+
13
+ ## Goals
14
+
15
+ - Safe, read-only database access for AI agents
16
+ - Schema inspection and table discovery
17
+ - Reliable PostgreSQL query execution with strict guardrails
18
+ - Simple, maintainable implementation
19
+
20
+ ## Tech Stack
21
+
22
+ - Node.js
23
+ - TypeScript
24
+ - MCP TypeScript SDK v1.x
25
+ - PostgreSQL via `pg`
26
+
27
+ ## Security Model
28
+
29
+ All executed queries must follow these rules:
30
+
31
+ - `SELECT` only
32
+ - Reject `INSERT`
33
+ - Reject `UPDATE`
34
+ - Reject `DELETE`
35
+ - Reject `DROP`
36
+ - Reject `ALTER`
37
+ - Reject `TRUNCATE`
38
+ - Enforce `LIMIT` with a default of `100`
39
+ - Enforce query timeout with a maximum of `5` seconds
40
+ - Enforce maximum result size
41
+ - Reject multi-statement SQL
42
+ - Reject SQL comments
43
+
44
+ These rules should never be bypassed.
45
+
46
+ ## Available MCP Tools
47
+
48
+ - `list_tables`
49
+ - `describe_table`
50
+ - `list_relationships`
51
+ - `run_readonly_query`
52
+ - `explain_query`
53
+ - `sample_rows`
54
+
55
+ ## Available MCP Resources
56
+
57
+ - `schema://snapshot`
58
+ - `schema://table/{name}`
59
+
60
+ ## Local Usage
61
+
62
+ Start the server with a PostgreSQL connection string:
63
+
64
+ ```bash
65
+ DATABASE_URL=postgres://USER:PASSWORD@HOST:PORT/DB npm run dev
66
+ ```
67
+
68
+ Or build and run the compiled server:
69
+
70
+ ```bash
71
+ npm run build
72
+ DATABASE_URL=postgres://USER:PASSWORD@HOST:PORT/DB npm start
73
+ ```
74
+
75
+ ## Client Configuration
76
+
77
+ For MCP clients that launch local stdio servers, point the command to the built CLI and provide `DATABASE_URL`:
78
+
79
+ ```json
80
+ {
81
+ "mcpServers": {
82
+ "ajan-sql": {
83
+ "command": "node",
84
+ "args": ["/absolute/path/to/ajan-sql/dist/index.js"],
85
+ "env": {
86
+ "DATABASE_URL": "postgres://USER:PASSWORD@HOST:PORT/DB"
87
+ }
88
+ }
89
+ }
90
+ }
91
+ ```
92
+
93
+ ## Integration Testing
94
+
95
+ The repository supports local PostgreSQL integration testing during development, but any Docker compose files or seeded local test databases can remain untracked and machine-local.
96
+
97
+ ## Development Principles
98
+
99
+ - Keep functions small and composable
100
+ - Avoid side effects
101
+ - Route all DB logic through `db/`
102
+ - Route all query execution through `query-runner`
103
+ - Route all validation through `guard`
104
+ - Prefer simple working code over abstraction
105
+ - Prioritize correctness, safety, and clarity
106
+
107
+ ## CLI Behavior
108
+
109
+ The CLI will:
110
+
111
+ - Start the MCP server over stdio
112
+ - Read `DATABASE_URL` from the environment
113
+ - Fail fast if `DATABASE_URL` is missing
114
+
115
+ ## Status
116
+
117
+ Early development. Schema inspection, readonly query execution, query explain, and sample row tools are implemented. The CLI is publish-ready for npm packaging, and current package version is `0.1.0`.
118
+
119
+ ## License
120
+
121
+ MIT
package/dist/config.js ADDED
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getRequiredEnv = getRequiredEnv;
4
+ exports.getAppConfig = getAppConfig;
5
+ const DEFAULT_DB_POOL_MAX = 10;
6
+ function getRequiredEnv(name) {
7
+ const value = process.env[name]?.trim();
8
+ if (!value) {
9
+ throw new Error(`Missing required environment variable: ${name}`);
10
+ }
11
+ return value;
12
+ }
13
+ function getAppConfig() {
14
+ return {
15
+ databaseUrl: getRequiredEnv("DATABASE_URL"),
16
+ dbPoolMax: DEFAULT_DB_POOL_MAX,
17
+ };
18
+ }
@@ -0,0 +1,14 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createDbPool = createDbPool;
4
+ exports.closeDbPool = closeDbPool;
5
+ const pg_1 = require("pg");
6
+ function createDbPool(options) {
7
+ return new pg_1.Pool({
8
+ connectionString: options.connectionString,
9
+ max: options.max,
10
+ });
11
+ }
12
+ async function closeDbPool(pool) {
13
+ await pool.end();
14
+ }
@@ -0,0 +1,74 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.listTables = listTables;
4
+ exports.describeTable = describeTable;
5
+ exports.listRelationships = listRelationships;
6
+ async function listTables(pool) {
7
+ const result = await pool.query(`
8
+ select table_schema, table_name
9
+ from information_schema.tables
10
+ where table_type = 'BASE TABLE'
11
+ and table_schema not in ('information_schema', 'pg_catalog')
12
+ order by table_schema, table_name
13
+ `);
14
+ return result.rows.map((row) => ({
15
+ schema: row.table_schema,
16
+ name: row.table_name,
17
+ }));
18
+ }
19
+ async function describeTable(pool, tableName, schemaName = "public") {
20
+ const result = await pool.query(`
21
+ select column_name, data_type, is_nullable, column_default
22
+ from information_schema.columns
23
+ where table_schema = $1
24
+ and table_name = $2
25
+ order by ordinal_position
26
+ `, [schemaName, tableName]);
27
+ if (result.rows.length === 0) {
28
+ return null;
29
+ }
30
+ return {
31
+ schema: schemaName,
32
+ name: tableName,
33
+ columns: result.rows.map((row) => ({
34
+ name: row.column_name,
35
+ dataType: row.data_type,
36
+ isNullable: row.is_nullable === "YES",
37
+ defaultValue: row.column_default,
38
+ })),
39
+ };
40
+ }
41
+ async function listRelationships(pool) {
42
+ const result = await pool.query(`
43
+ select
44
+ tc.constraint_name,
45
+ tc.table_schema as source_schema,
46
+ tc.table_name as source_table,
47
+ kcu.column_name as source_column,
48
+ ccu.table_schema as target_schema,
49
+ ccu.table_name as target_table,
50
+ ccu.column_name as target_column
51
+ from information_schema.table_constraints tc
52
+ join information_schema.key_column_usage kcu
53
+ on tc.constraint_name = kcu.constraint_name
54
+ and tc.table_schema = kcu.table_schema
55
+ join information_schema.constraint_column_usage ccu
56
+ on ccu.constraint_name = tc.constraint_name
57
+ and ccu.table_schema = tc.table_schema
58
+ where tc.constraint_type = 'FOREIGN KEY'
59
+ order by
60
+ tc.table_schema,
61
+ tc.table_name,
62
+ tc.constraint_name,
63
+ kcu.ordinal_position
64
+ `);
65
+ return result.rows.map((row) => ({
66
+ constraintName: row.constraint_name,
67
+ sourceSchema: row.source_schema,
68
+ sourceTable: row.source_table,
69
+ sourceColumn: row.source_column,
70
+ targetSchema: row.target_schema,
71
+ targetTable: row.target_table,
72
+ targetColumn: row.target_column,
73
+ }));
74
+ }
@@ -0,0 +1,85 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getReadonlyDefaults = getReadonlyDefaults;
4
+ exports.guardReadonlyQuery = guardReadonlyQuery;
5
+ exports.quoteIdentifier = quoteIdentifier;
6
+ const DEFAULT_LIMIT = 100;
7
+ const MAX_LIMIT = 100;
8
+ const MAX_TIMEOUT_MS = 5_000;
9
+ const MAX_RESULT_BYTES = 1_000_000;
10
+ const BLOCKED_KEYWORDS = [
11
+ "insert",
12
+ "update",
13
+ "delete",
14
+ "drop",
15
+ "alter",
16
+ "truncate",
17
+ ];
18
+ function getReadonlyDefaults() {
19
+ return {
20
+ defaultLimit: DEFAULT_LIMIT,
21
+ maxLimit: MAX_LIMIT,
22
+ timeoutMs: MAX_TIMEOUT_MS,
23
+ maxResultBytes: MAX_RESULT_BYTES,
24
+ };
25
+ }
26
+ function guardReadonlyQuery(sql, options = {}) {
27
+ const defaults = getReadonlyDefaults();
28
+ const defaultLimit = options.defaultLimit ?? defaults.defaultLimit;
29
+ const maxLimit = options.maxLimit ?? defaults.maxLimit;
30
+ const timeoutMs = Math.min(options.timeoutMs ?? defaults.timeoutMs, defaults.timeoutMs);
31
+ const maxResultBytes = options.maxResultBytes ?? defaults.maxResultBytes;
32
+ const normalizedSql = normalizeSql(sql);
33
+ assertSingleStatement(normalizedSql);
34
+ assertNoSqlComments(normalizedSql);
35
+ assertSelectOnly(normalizedSql);
36
+ assertNoBlockedKeywords(normalizedSql);
37
+ const limitMatch = normalizedSql.match(/\blimit\s+(\d+)\b/i);
38
+ const parsedLimit = limitMatch ? Number.parseInt(limitMatch[1], 10) : null;
39
+ if (parsedLimit !== null && parsedLimit > maxLimit) {
40
+ throw new Error(`Query LIMIT exceeds maximum allowed value of ${maxLimit}`);
41
+ }
42
+ return {
43
+ sql: parsedLimit === null ? `${normalizedSql} LIMIT ${defaultLimit}` : normalizedSql,
44
+ limit: parsedLimit ?? defaultLimit,
45
+ timeoutMs,
46
+ maxResultBytes,
47
+ };
48
+ }
49
+ function quoteIdentifier(identifier) {
50
+ if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(identifier)) {
51
+ throw new Error(`Invalid SQL identifier: ${identifier}`);
52
+ }
53
+ return `"${identifier.replace(/"/g, "\"\"")}"`;
54
+ }
55
+ function normalizeSql(sql) {
56
+ const trimmed = sql.trim().replace(/;+$/, "").trim();
57
+ if (!trimmed) {
58
+ throw new Error("SQL query is required");
59
+ }
60
+ return trimmed;
61
+ }
62
+ function assertSingleStatement(sql) {
63
+ if (sql.includes(";")) {
64
+ throw new Error("Only a single SQL statement is allowed");
65
+ }
66
+ }
67
+ function assertNoSqlComments(sql) {
68
+ if (sql.includes("--") || sql.includes("/*") || sql.includes("*/")) {
69
+ throw new Error("SQL comments are not allowed");
70
+ }
71
+ }
72
+ function assertSelectOnly(sql) {
73
+ if (!/^select\b/i.test(sql)) {
74
+ throw new Error("Only SELECT queries are allowed");
75
+ }
76
+ }
77
+ function assertNoBlockedKeywords(sql) {
78
+ const scrubbedSql = sql.replace(/'(?:''|[^'])*'/g, "''");
79
+ for (const keyword of BLOCKED_KEYWORDS) {
80
+ const pattern = new RegExp(`\\b${keyword}\\b`, "i");
81
+ if (pattern.test(scrubbedSql)) {
82
+ throw new Error(`Blocked SQL keyword detected: ${keyword.toUpperCase()}`);
83
+ }
84
+ }
85
+ }
package/dist/index.js ADDED
@@ -0,0 +1,28 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
5
+ const config_1 = require("./config");
6
+ const pool_1 = require("./db/pool");
7
+ const server_1 = require("./server");
8
+ async function main() {
9
+ const config = (0, config_1.getAppConfig)();
10
+ const pool = (0, pool_1.createDbPool)({
11
+ connectionString: config.databaseUrl,
12
+ max: config.dbPoolMax,
13
+ });
14
+ const server = (0, server_1.createAjanServer)({ pool });
15
+ const transport = new stdio_js_1.StdioServerTransport();
16
+ const shutdown = async () => {
17
+ await (0, pool_1.closeDbPool)(pool);
18
+ process.exit(0);
19
+ };
20
+ process.on("SIGINT", shutdown);
21
+ process.on("SIGTERM", shutdown);
22
+ await server.connect(transport);
23
+ }
24
+ main().catch((error) => {
25
+ const message = error instanceof Error ? error.message : String(error);
26
+ console.error(`[ajan-sql] ${message}`);
27
+ process.exit(1);
28
+ });
@@ -0,0 +1,65 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.runReadonlyQuery = runReadonlyQuery;
4
+ exports.explainReadonlyQuery = explainReadonlyQuery;
5
+ exports.sampleRows = sampleRows;
6
+ const guard_1 = require("../guard");
7
+ async function runReadonlyQuery(pool, sql, options = {}) {
8
+ const guarded = (0, guard_1.guardReadonlyQuery)(sql, options);
9
+ const result = await executeWithReadonlySettings(pool, guarded.sql, guarded.timeoutMs);
10
+ assertRowCount(result.rows.length, guarded.limit);
11
+ assertResultSize(result.rows, guarded.maxResultBytes);
12
+ return {
13
+ sql: guarded.sql,
14
+ rowCount: result.rows.length,
15
+ rows: result.rows,
16
+ };
17
+ }
18
+ async function explainReadonlyQuery(pool, sql) {
19
+ const guarded = (0, guard_1.guardReadonlyQuery)(sql);
20
+ const explainSql = `EXPLAIN (FORMAT JSON) ${guarded.sql}`;
21
+ const result = await executeWithReadonlySettings(pool, explainSql, guarded.timeoutMs);
22
+ const plan = result.rows[0]?.["QUERY PLAN"];
23
+ return {
24
+ sql: guarded.sql,
25
+ plan,
26
+ };
27
+ }
28
+ async function sampleRows(pool, tableName, schemaName = "public", limit = 10) {
29
+ const defaults = (0, guard_1.getReadonlyDefaults)();
30
+ const safeLimit = Math.min(limit, defaults.maxLimit);
31
+ const sql = [
32
+ "SELECT *",
33
+ `FROM ${(0, guard_1.quoteIdentifier)(schemaName)}.${(0, guard_1.quoteIdentifier)(tableName)}`,
34
+ `LIMIT ${safeLimit}`,
35
+ ].join(" ");
36
+ return runReadonlyQuery(pool, sql, { defaultLimit: safeLimit });
37
+ }
38
+ async function executeWithReadonlySettings(pool, sql, timeoutMs) {
39
+ const client = await pool.connect();
40
+ try {
41
+ await client.query("BEGIN");
42
+ await client.query(`SET LOCAL statement_timeout = '${timeoutMs}ms'`);
43
+ const result = await client.query(sql);
44
+ await client.query("ROLLBACK");
45
+ return result;
46
+ }
47
+ catch (error) {
48
+ await client.query("ROLLBACK").catch(() => undefined);
49
+ throw error;
50
+ }
51
+ finally {
52
+ client.release();
53
+ }
54
+ }
55
+ function assertRowCount(rowCount, limit) {
56
+ if (rowCount > limit) {
57
+ throw new Error(`Query returned more rows than allowed limit of ${limit}`);
58
+ }
59
+ }
60
+ function assertResultSize(rows, maxResultBytes) {
61
+ const resultBytes = Buffer.byteLength(JSON.stringify(rows), "utf8");
62
+ if (resultBytes > maxResultBytes) {
63
+ throw new Error(`Query result exceeds maximum allowed size of ${maxResultBytes} bytes`);
64
+ }
65
+ }
@@ -0,0 +1,65 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.registerSchemaResources = registerSchemaResources;
4
+ const mcp_js_1 = require("@modelcontextprotocol/sdk/server/mcp.js");
5
+ const schema_1 = require("../db/schema");
6
+ function registerSchemaResources(server, deps) {
7
+ server.registerResource("schema-snapshot", "schema://snapshot", {
8
+ description: "Snapshot of tables and foreign key relationships.",
9
+ mimeType: "application/json",
10
+ }, async (uri) => {
11
+ const [tables, relationships] = await Promise.all([
12
+ (0, schema_1.listTables)(deps.pool),
13
+ (0, schema_1.listRelationships)(deps.pool),
14
+ ]);
15
+ return {
16
+ contents: [
17
+ {
18
+ uri: uri.href,
19
+ text: JSON.stringify({ tables, relationships }, null, 2),
20
+ mimeType: "application/json",
21
+ },
22
+ ],
23
+ };
24
+ });
25
+ server.registerResource("schema-table", new mcp_js_1.ResourceTemplate("schema://table/{name}", {
26
+ list: async () => {
27
+ const tables = await (0, schema_1.listTables)(deps.pool);
28
+ return {
29
+ resources: tables.map((table) => ({
30
+ uri: `schema://table/${table.name}`,
31
+ name: `${table.schema}.${table.name}`,
32
+ })),
33
+ };
34
+ },
35
+ complete: {
36
+ name: async (value) => {
37
+ const tables = await (0, schema_1.listTables)(deps.pool);
38
+ return tables
39
+ .map((table) => table.name)
40
+ .filter((tableName) => tableName.startsWith(value));
41
+ },
42
+ },
43
+ }), {
44
+ description: "Schema details for a single table.",
45
+ mimeType: "application/json",
46
+ }, async (uri, params) => {
47
+ const tableName = Array.isArray(params.name) ? params.name[0] : params.name;
48
+ if (!tableName) {
49
+ throw new Error("Table name is required");
50
+ }
51
+ const description = await (0, schema_1.describeTable)(deps.pool, tableName);
52
+ if (!description) {
53
+ throw new Error(`Table not found: ${tableName}`);
54
+ }
55
+ return {
56
+ contents: [
57
+ {
58
+ uri: uri.href,
59
+ text: JSON.stringify(description, null, 2),
60
+ mimeType: "application/json",
61
+ },
62
+ ],
63
+ };
64
+ });
65
+ }
package/dist/server.js ADDED
@@ -0,0 +1,15 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createAjanServer = createAjanServer;
4
+ const mcp_js_1 = require("@modelcontextprotocol/sdk/server/mcp.js");
5
+ const schema_resources_1 = require("./resources/schema-resources");
6
+ const schema_tools_1 = require("./tools/schema-tools");
7
+ function createAjanServer(options) {
8
+ const server = new mcp_js_1.McpServer({
9
+ name: "ajan-sql",
10
+ version: "0.1.0",
11
+ });
12
+ (0, schema_tools_1.registerSchemaTools)(server, { pool: options.pool });
13
+ (0, schema_resources_1.registerSchemaResources)(server, { pool: options.pool });
14
+ return server;
15
+ }
@@ -0,0 +1,74 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.registerSchemaTools = registerSchemaTools;
4
+ const zod_1 = require("zod");
5
+ const schema_1 = require("../db/schema");
6
+ const query_runner_1 = require("../query-runner");
7
+ function asTextResult(data) {
8
+ return {
9
+ content: [
10
+ {
11
+ type: "text",
12
+ text: JSON.stringify(data, null, 2),
13
+ },
14
+ ],
15
+ };
16
+ }
17
+ function registerSchemaTools(server, deps) {
18
+ const registerTool = server.registerTool.bind(server);
19
+ registerTool("list_tables", {
20
+ description: "Return all tables in the database.",
21
+ }, async () => {
22
+ const tables = await (0, schema_1.listTables)(deps.pool);
23
+ return asTextResult(tables);
24
+ });
25
+ registerTool("describe_table", {
26
+ description: "Return columns and types for a given table.",
27
+ inputSchema: {
28
+ name: zod_1.z.string().min(1),
29
+ schema: zod_1.z.string().min(1).optional(),
30
+ },
31
+ }, async ({ name, schema }) => {
32
+ const resolvedSchema = schema ?? "public";
33
+ const description = await (0, schema_1.describeTable)(deps.pool, name, resolvedSchema);
34
+ if (!description) {
35
+ throw new Error(`Table not found: ${resolvedSchema}.${name}`);
36
+ }
37
+ return asTextResult(description);
38
+ });
39
+ registerTool("list_relationships", {
40
+ description: "Return foreign key relationships.",
41
+ }, async () => {
42
+ const relationships = await (0, schema_1.listRelationships)(deps.pool);
43
+ return asTextResult(relationships);
44
+ });
45
+ registerTool("run_readonly_query", {
46
+ description: "Execute a safe SELECT query.",
47
+ inputSchema: {
48
+ sql: zod_1.z.string().min(1),
49
+ },
50
+ }, async ({ sql }) => {
51
+ const result = await (0, query_runner_1.runReadonlyQuery)(deps.pool, sql);
52
+ return asTextResult(result);
53
+ });
54
+ registerTool("explain_query", {
55
+ description: "Return query execution plan.",
56
+ inputSchema: {
57
+ sql: zod_1.z.string().min(1),
58
+ },
59
+ }, async ({ sql }) => {
60
+ const result = await (0, query_runner_1.explainReadonlyQuery)(deps.pool, sql);
61
+ return asTextResult(result);
62
+ });
63
+ registerTool("sample_rows", {
64
+ description: "Return example rows from a table.",
65
+ inputSchema: {
66
+ name: zod_1.z.string().min(1),
67
+ schema: zod_1.z.string().min(1).optional(),
68
+ limit: zod_1.z.number().int().positive().max(100).optional(),
69
+ },
70
+ }, async ({ name, schema, limit }) => {
71
+ const result = await (0, query_runner_1.sampleRows)(deps.pool, name, schema ?? "public", limit ?? 10);
72
+ return asTextResult(result);
73
+ });
74
+ }
package/package.json ADDED
@@ -0,0 +1,52 @@
1
+ {
2
+ "name": "ajan-sql",
3
+ "version": "0.1.0",
4
+ "description": "AI-safe MCP server for schema-aware, read-only SQL access.",
5
+ "bin": {
6
+ "ajan-sql": "./dist/index.js"
7
+ },
8
+ "files": [
9
+ "dist",
10
+ "README.md",
11
+ "LICENSE"
12
+ ],
13
+ "main": "dist/index.js",
14
+ "scripts": {
15
+ "build": "tsc -p tsconfig.json",
16
+ "dev": "tsx src/index.ts",
17
+ "docs:build": "vitepress build docs",
18
+ "docs:dev": "vitepress dev docs",
19
+ "docs:preview": "vitepress preview docs",
20
+ "prepublishOnly": "npm run build && npm test",
21
+ "prepare": "husky",
22
+ "start": "node dist/index.js",
23
+ "test": "vitest run",
24
+ "test:watch": "vitest"
25
+ },
26
+ "keywords": [
27
+ "mcp",
28
+ "model-context-protocol",
29
+ "postgres",
30
+ "postgresql",
31
+ "sql",
32
+ "ai",
33
+ "cli"
34
+ ],
35
+ "author": "Bora Kilicoglu",
36
+ "license": "MIT",
37
+ "type": "commonjs",
38
+ "dependencies": {
39
+ "@modelcontextprotocol/sdk": "^1.18.0",
40
+ "pg": "^8.16.3",
41
+ "zod": "^3.25.76"
42
+ },
43
+ "devDependencies": {
44
+ "@types/node": "^24.0.0",
45
+ "@types/pg": "^8.15.6",
46
+ "husky": "^9.1.7",
47
+ "tsx": "^4.20.6",
48
+ "typescript": "^5.9.0",
49
+ "vitepress": "^1.6.4",
50
+ "vitest": "^3.2.0"
51
+ }
52
+ }