opencode-pair-autonomy 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.
Files changed (128) hide show
  1. package/README.md +90 -0
  2. package/bin/opencode-pair-autonomy.js +20 -0
  3. package/dist/__tests__/comment-guard.test.d.ts +1 -0
  4. package/dist/__tests__/config.test.d.ts +1 -0
  5. package/dist/__tests__/learning.test.d.ts +1 -0
  6. package/dist/__tests__/plan-mode.test.d.ts +1 -0
  7. package/dist/agents.d.ts +2 -0
  8. package/dist/cli.d.ts +1 -0
  9. package/dist/cli.js +15351 -0
  10. package/dist/commands.d.ts +2 -0
  11. package/dist/config.d.ts +3 -0
  12. package/dist/hooks/comment-guard.d.ts +15 -0
  13. package/dist/hooks/file-edited.d.ts +7 -0
  14. package/dist/hooks/index.d.ts +46 -0
  15. package/dist/hooks/post-tool-use.d.ts +5 -0
  16. package/dist/hooks/pre-compact.d.ts +4 -0
  17. package/dist/hooks/pre-tool-use.d.ts +5 -0
  18. package/dist/hooks/prompt-refiner.d.ts +38 -0
  19. package/dist/hooks/runtime.d.ts +91 -0
  20. package/dist/hooks/sdk.d.ts +6 -0
  21. package/dist/hooks/session-end.d.ts +4 -0
  22. package/dist/hooks/session-start.d.ts +19 -0
  23. package/dist/hooks/stop.d.ts +5 -0
  24. package/dist/i18n/index.d.ts +15 -0
  25. package/dist/index.d.ts +3 -0
  26. package/dist/index.js +17823 -0
  27. package/dist/installer.d.ts +12 -0
  28. package/dist/learning/analyzer.d.ts +15 -0
  29. package/dist/learning/store.d.ts +4 -0
  30. package/dist/learning/types.d.ts +32 -0
  31. package/dist/mcp.d.ts +4 -0
  32. package/dist/project-facts.d.ts +8 -0
  33. package/dist/prompts/coordinator.d.ts +2 -0
  34. package/dist/prompts/shared.d.ts +5 -0
  35. package/dist/prompts/workers.d.ts +8 -0
  36. package/dist/types.d.ts +81 -0
  37. package/dist/utils.d.ts +6 -0
  38. package/examples/opencode-pair-autonomy.jsonc +35 -0
  39. package/examples/opencode.jsonc +17 -0
  40. package/package.json +103 -0
  41. package/vendor/mcp/pg-mcp/README.md +91 -0
  42. package/vendor/mcp/pg-mcp/config.example.json +26 -0
  43. package/vendor/mcp/pg-mcp/config.json +15 -0
  44. package/vendor/mcp/pg-mcp/package-lock.json +1288 -0
  45. package/vendor/mcp/pg-mcp/package.json +18 -0
  46. package/vendor/mcp/pg-mcp/src/config.js +71 -0
  47. package/vendor/mcp/pg-mcp/src/db.js +85 -0
  48. package/vendor/mcp/pg-mcp/src/index.js +203 -0
  49. package/vendor/mcp/pg-mcp/src/sqlGuard.js +75 -0
  50. package/vendor/mcp/pg-mcp/src/tools.js +89 -0
  51. package/vendor/mcp/ssh-mcp/README.md +46 -0
  52. package/vendor/mcp/ssh-mcp/config.example.json +23 -0
  53. package/vendor/mcp/ssh-mcp/config.json +6 -0
  54. package/vendor/mcp/ssh-mcp/package-lock.json +1142 -0
  55. package/vendor/mcp/ssh-mcp/package.json +18 -0
  56. package/vendor/mcp/ssh-mcp/src/config.js +140 -0
  57. package/vendor/mcp/ssh-mcp/src/index.js +130 -0
  58. package/vendor/mcp/ssh-mcp/src/ssh.js +163 -0
  59. package/vendor/mcp/sudo-mcp/README.md +51 -0
  60. package/vendor/mcp/sudo-mcp/config.example.json +28 -0
  61. package/vendor/mcp/sudo-mcp/config.json +28 -0
  62. package/vendor/mcp/sudo-mcp/package-lock.json +1145 -0
  63. package/vendor/mcp/sudo-mcp/package.json +18 -0
  64. package/vendor/mcp/sudo-mcp/src/config.js +57 -0
  65. package/vendor/mcp/sudo-mcp/src/index.js +267 -0
  66. package/vendor/mcp/sudo-mcp/src/runner.js +168 -0
  67. package/vendor/mcp/web-agent-mcp/package-lock.json +2886 -0
  68. package/vendor/mcp/web-agent-mcp/package.json +28 -0
  69. package/vendor/mcp/web-agent-mcp/src/adapters/cloakbrowser/adapter.ts +335 -0
  70. package/vendor/mcp/web-agent-mcp/src/adapters/cloakbrowser/auth-heuristics.ts +324 -0
  71. package/vendor/mcp/web-agent-mcp/src/adapters/cloakbrowser/launcher.ts +1340 -0
  72. package/vendor/mcp/web-agent-mcp/src/config/env.ts +107 -0
  73. package/vendor/mcp/web-agent-mcp/src/core/action-flow.ts +82 -0
  74. package/vendor/mcp/web-agent-mcp/src/core/artifact-store.ts +109 -0
  75. package/vendor/mcp/web-agent-mcp/src/core/errors.ts +108 -0
  76. package/vendor/mcp/web-agent-mcp/src/core/observation-flow.ts +38 -0
  77. package/vendor/mcp/web-agent-mcp/src/core/policy-engine.ts +113 -0
  78. package/vendor/mcp/web-agent-mcp/src/core/retry-policy.ts +42 -0
  79. package/vendor/mcp/web-agent-mcp/src/core/session-manager.ts +670 -0
  80. package/vendor/mcp/web-agent-mcp/src/core/session-restart-policy.ts +34 -0
  81. package/vendor/mcp/web-agent-mcp/src/core/task-history.ts +97 -0
  82. package/vendor/mcp/web-agent-mcp/src/index.ts +3 -0
  83. package/vendor/mcp/web-agent-mcp/src/schemas/act.ts +167 -0
  84. package/vendor/mcp/web-agent-mcp/src/schemas/common.ts +56 -0
  85. package/vendor/mcp/web-agent-mcp/src/schemas/observe.ts +214 -0
  86. package/vendor/mcp/web-agent-mcp/src/schemas/page.ts +21 -0
  87. package/vendor/mcp/web-agent-mcp/src/schemas/policy.ts +42 -0
  88. package/vendor/mcp/web-agent-mcp/src/schemas/runtime.ts +21 -0
  89. package/vendor/mcp/web-agent-mcp/src/schemas/session.ts +63 -0
  90. package/vendor/mcp/web-agent-mcp/src/server.ts +75 -0
  91. package/vendor/mcp/web-agent-mcp/src/tools/act/click.ts +68 -0
  92. package/vendor/mcp/web-agent-mcp/src/tools/act/drag.ts +57 -0
  93. package/vendor/mcp/web-agent-mcp/src/tools/act/enter-code.ts +78 -0
  94. package/vendor/mcp/web-agent-mcp/src/tools/act/fill.ts +65 -0
  95. package/vendor/mcp/web-agent-mcp/src/tools/act/pinch.ts +58 -0
  96. package/vendor/mcp/web-agent-mcp/src/tools/act/press.ts +67 -0
  97. package/vendor/mcp/web-agent-mcp/src/tools/act/shared.ts +73 -0
  98. package/vendor/mcp/web-agent-mcp/src/tools/act/swipe.ts +59 -0
  99. package/vendor/mcp/web-agent-mcp/src/tools/act/wait-for.ts +56 -0
  100. package/vendor/mcp/web-agent-mcp/src/tools/act/wheel.ts +59 -0
  101. package/vendor/mcp/web-agent-mcp/src/tools/observe/a11y.ts +60 -0
  102. package/vendor/mcp/web-agent-mcp/src/tools/observe/auth-state.ts +92 -0
  103. package/vendor/mcp/web-agent-mcp/src/tools/observe/boxes.ts +66 -0
  104. package/vendor/mcp/web-agent-mcp/src/tools/observe/console.ts +67 -0
  105. package/vendor/mcp/web-agent-mcp/src/tools/observe/dom.ts +60 -0
  106. package/vendor/mcp/web-agent-mcp/src/tools/observe/network.ts +67 -0
  107. package/vendor/mcp/web-agent-mcp/src/tools/observe/page-state.ts +93 -0
  108. package/vendor/mcp/web-agent-mcp/src/tools/observe/screenshot.ts +73 -0
  109. package/vendor/mcp/web-agent-mcp/src/tools/observe/text.ts +70 -0
  110. package/vendor/mcp/web-agent-mcp/src/tools/observe/wait-for-network.ts +70 -0
  111. package/vendor/mcp/web-agent-mcp/src/tools/page/navigate.ts +59 -0
  112. package/vendor/mcp/web-agent-mcp/src/tools/policy/recommend-observation.ts +40 -0
  113. package/vendor/mcp/web-agent-mcp/src/tools/register-tools.ts +55 -0
  114. package/vendor/mcp/web-agent-mcp/src/tools/runtime/evaluate-js.ts +83 -0
  115. package/vendor/mcp/web-agent-mcp/src/tools/session/close.ts +41 -0
  116. package/vendor/mcp/web-agent-mcp/src/tools/session/create.ts +86 -0
  117. package/vendor/mcp/web-agent-mcp/src/tools/session/restart.ts +72 -0
  118. package/vendor/mcp/web-agent-mcp/src/utils/fs.ts +28 -0
  119. package/vendor/mcp/web-agent-mcp/src/utils/ids.ts +9 -0
  120. package/vendor/mcp/web-agent-mcp/src/utils/time.ts +7 -0
  121. package/vendor/mcp/web-agent-mcp/tsconfig.json +22 -0
  122. package/vendor/skills/editorial-technical-ui/SKILL.md +84 -0
  123. package/vendor/skills/figma-console/SKILL.md +839 -0
  124. package/vendor/skills/go-fiber-postgres/SKILL.md +31 -0
  125. package/vendor/skills/opencode-plugin-dev/SKILL.md +31 -0
  126. package/vendor/skills/rust-media-desktop/SKILL.md +30 -0
  127. package/vendor/skills/vue-vite-ui/SKILL.md +31 -0
  128. package/vendor/skills/web-agent-browser/SKILL.md +140 -0
@@ -0,0 +1,18 @@
1
+ {
2
+ "name": "pg-mcp-server",
3
+ "version": "1.0.0",
4
+ "description": "PostgreSQL icin basit ve salt-okunur MCP sunucusu",
5
+ "type": "module",
6
+ "main": "src/index.js",
7
+ "bin": {
8
+ "pg-mcp": "./src/index.js"
9
+ },
10
+ "scripts": {
11
+ "start": "node src/index.js",
12
+ "check": "node --check src/index.js && node --check src/config.js && node --check src/db.js && node --check src/sqlGuard.js && node --check src/tools.js"
13
+ },
14
+ "dependencies": {
15
+ "@modelcontextprotocol/sdk": "^1.0.0",
16
+ "pg": "^8.13.1"
17
+ }
18
+ }
@@ -0,0 +1,71 @@
1
+ import { existsSync, readFileSync } from "fs";
2
+ import { dirname, join } from "path";
3
+ import { fileURLToPath } from "url";
4
+
5
+ const __dirname = dirname(fileURLToPath(import.meta.url));
6
+
7
+ function toPositiveInt(value, fallback) {
8
+ const n = Number(value);
9
+ if (!Number.isFinite(n) || n <= 0) return fallback;
10
+ return Math.floor(n);
11
+ }
12
+
13
+ function normalizeConnection(connection) {
14
+ return {
15
+ host: connection.host || "127.0.0.1",
16
+ port: toPositiveInt(connection.port, 5432),
17
+ user: connection.user || "postgres",
18
+ password: connection.password || "",
19
+ database: connection.database || "postgres",
20
+ description: connection.description || "",
21
+ statement_timeout_ms: toPositiveInt(connection.statement_timeout_ms, 10000),
22
+ default_row_limit: toPositiveInt(connection.default_row_limit, 100),
23
+ max_row_limit: toPositiveInt(connection.max_row_limit, 1000),
24
+ ssl: Boolean(connection.ssl),
25
+ };
26
+ }
27
+
28
+ export function loadConfig() {
29
+ const candidates = [
30
+ process.env.PG_MCP_CONFIG_PATH,
31
+ join(__dirname, "../config.json"),
32
+ join(process.cwd(), "config.json"),
33
+ ].filter(Boolean);
34
+
35
+ let rawConfig;
36
+ for (const configPath of candidates) {
37
+ if (existsSync(configPath)) {
38
+ rawConfig = JSON.parse(readFileSync(configPath, "utf8"));
39
+ break;
40
+ }
41
+ }
42
+
43
+ if (!rawConfig) {
44
+ throw new Error("config.json bulunamadi. PG_MCP_CONFIG_PATH ayarlayin veya proje kokune config.json koyun.");
45
+ }
46
+
47
+ if (!rawConfig.connections || typeof rawConfig.connections !== "object") {
48
+ throw new Error("config.json icinde 'connections' nesnesi olmalidir.");
49
+ }
50
+
51
+ const entries = Object.entries(rawConfig.connections);
52
+ if (entries.length === 0) {
53
+ throw new Error("En az bir baglanti tanimlamalisiniz (connections).");
54
+ }
55
+
56
+ const connections = {};
57
+ for (const [name, connection] of entries) {
58
+ if (!connection || typeof connection !== "object") {
59
+ throw new Error(`'${name}' baglantisi gecersiz.`);
60
+ }
61
+
62
+ const normalized = normalizeConnection(connection);
63
+ if (normalized.default_row_limit > normalized.max_row_limit) {
64
+ throw new Error(`'${name}' icin default_row_limit, max_row_limit degerinden buyuk olamaz.`);
65
+ }
66
+
67
+ connections[name] = normalized;
68
+ }
69
+
70
+ return { connections };
71
+ }
@@ -0,0 +1,85 @@
1
+ import pg from "pg";
2
+
3
+ const { Pool } = pg;
4
+
5
+ function normalizeTimeout(value, fallback = 10000) {
6
+ const n = Number(value);
7
+ if (!Number.isFinite(n) || n <= 0) return fallback;
8
+ return Math.floor(n);
9
+ }
10
+
11
+ export class DbManager {
12
+ constructor(config) {
13
+ this.config = config;
14
+ this.pools = new Map();
15
+ }
16
+
17
+ getConnectionNames() {
18
+ return Object.keys(this.config.connections);
19
+ }
20
+
21
+ getConnectionConfig(name) {
22
+ const cfg = this.config.connections[name];
23
+ if (!cfg) {
24
+ const available = this.getConnectionNames().join(", ");
25
+ throw new Error(`Baglanti bulunamadi: '${name}'. Mevcut baglantilar: ${available}`);
26
+ }
27
+ return cfg;
28
+ }
29
+
30
+ getPool(name, databaseOverride = null) {
31
+ const connection = this.getConnectionConfig(name);
32
+ const database = databaseOverride || connection.database;
33
+ const poolKey = `${name}::${database}`;
34
+
35
+ if (!this.pools.has(poolKey)) {
36
+ const c = this.getConnectionConfig(name);
37
+ this.pools.set(
38
+ poolKey,
39
+ new Pool({
40
+ host: c.host,
41
+ port: c.port,
42
+ user: c.user,
43
+ password: c.password,
44
+ database,
45
+ ssl: c.ssl ? { rejectUnauthorized: false } : false,
46
+ max: 5,
47
+ })
48
+ );
49
+ }
50
+
51
+ return this.pools.get(poolKey);
52
+ }
53
+
54
+ async runReadOnly(connectionName, sql, params = [], options = {}) {
55
+ const connection = this.getConnectionConfig(connectionName);
56
+ const statementTimeout = normalizeTimeout(connection.statement_timeout_ms);
57
+ const database = options.database || null;
58
+
59
+ const client = await this.getPool(connectionName, database).connect();
60
+ try {
61
+ await client.query("BEGIN READ ONLY");
62
+ await client.query(`SET LOCAL statement_timeout = ${statementTimeout}`);
63
+ const result = await client.query(sql, params);
64
+ await client.query("COMMIT");
65
+ return result;
66
+ } catch (error) {
67
+ try {
68
+ await client.query("ROLLBACK");
69
+ } catch {
70
+ // ROLLBACK hatasi olursa asil hatayi koruyoruz.
71
+ }
72
+ throw error;
73
+ } finally {
74
+ client.release();
75
+ }
76
+ }
77
+
78
+ async closeAll() {
79
+ const tasks = [];
80
+ for (const pool of this.pools.values()) {
81
+ tasks.push(pool.end());
82
+ }
83
+ await Promise.all(tasks);
84
+ }
85
+ }
@@ -0,0 +1,203 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
4
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
5
+ import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
6
+ import { loadConfig } from "./config.js";
7
+ import { DbManager } from "./db.js";
8
+ import { buildLimitedQuery, resolveRowLimit, validateAndNormalizeSelect } from "./sqlGuard.js";
9
+ import { buildToolDefinitions, fail, ok } from "./tools.js";
10
+
11
+ const config = loadConfig();
12
+ const db = new DbManager(config);
13
+ const connectionNames = db.getConnectionNames();
14
+
15
+ function normalizeDatabaseName(value) {
16
+ if (value == null) return null;
17
+ if (typeof value !== "string") {
18
+ throw new Error("'database' alani metin olmalidir.");
19
+ }
20
+
21
+ const trimmed = value.trim();
22
+ if (!trimmed) return null;
23
+ if (!/^[a-zA-Z0-9_\-]+$/.test(trimmed)) {
24
+ throw new Error("'database' alani gecersiz karakter iceriyor.");
25
+ }
26
+
27
+ return trimmed;
28
+ }
29
+
30
+ const server = new Server(
31
+ { name: "pg-mcp-server", version: "1.0.0" },
32
+ { capabilities: { tools: {} } }
33
+ );
34
+
35
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({
36
+ tools: buildToolDefinitions(connectionNames),
37
+ }));
38
+
39
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
40
+ const { name, arguments: args } = request.params;
41
+
42
+ try {
43
+ switch (name) {
44
+ case "list_connections": {
45
+ const list = connectionNames.map((connectionName) => {
46
+ const c = db.getConnectionConfig(connectionName);
47
+ return {
48
+ name: connectionName,
49
+ description: c.description,
50
+ host: c.host,
51
+ port: c.port,
52
+ database: c.database,
53
+ statement_timeout_ms: c.statement_timeout_ms,
54
+ default_row_limit: c.default_row_limit,
55
+ max_row_limit: c.max_row_limit,
56
+ };
57
+ });
58
+ return ok(list);
59
+ }
60
+
61
+ case "list_databases": {
62
+ const connection = args?.connection;
63
+ if (!connection) return fail("'connection' alani zorunludur.");
64
+
65
+ const result = await db.runReadOnly(
66
+ connection,
67
+ `
68
+ SELECT datname AS database_name
69
+ FROM pg_database
70
+ WHERE datallowconn = true
71
+ AND NOT datistemplate
72
+ ORDER BY datname
73
+ `
74
+ );
75
+
76
+ return ok(result.rows);
77
+ }
78
+
79
+ case "list_schemas": {
80
+ const connection = args?.connection;
81
+ const database = normalizeDatabaseName(args?.database);
82
+ if (!connection) return fail("'connection' alani zorunludur.");
83
+
84
+ const result = await db.runReadOnly(
85
+ connection,
86
+ `
87
+ SELECT schema_name
88
+ FROM information_schema.schemata
89
+ WHERE schema_name NOT IN ('pg_catalog', 'information_schema')
90
+ ORDER BY schema_name
91
+ `,
92
+ [],
93
+ { database }
94
+ );
95
+
96
+ return ok(result.rows);
97
+ }
98
+
99
+ case "list_tables": {
100
+ const connection = args?.connection;
101
+ const database = normalizeDatabaseName(args?.database);
102
+ const schema = args?.schema ?? null;
103
+ if (!connection) return fail("'connection' alani zorunludur.");
104
+
105
+ const result = await db.runReadOnly(
106
+ connection,
107
+ `
108
+ SELECT table_schema, table_name, table_type
109
+ FROM information_schema.tables
110
+ WHERE table_schema NOT IN ('pg_catalog', 'information_schema')
111
+ AND ($1::text IS NULL OR table_schema = $1)
112
+ ORDER BY table_schema, table_name
113
+ `,
114
+ [schema],
115
+ { database }
116
+ );
117
+
118
+ return ok(result.rows);
119
+ }
120
+
121
+ case "describe_table": {
122
+ const connection = args?.connection;
123
+ const database = normalizeDatabaseName(args?.database);
124
+ const table = args?.table;
125
+ const schema = args?.schema ?? null;
126
+ if (!connection) return fail("'connection' alani zorunludur.");
127
+ if (!table) return fail("'table' alani zorunludur.");
128
+
129
+ const result = await db.runReadOnly(
130
+ connection,
131
+ `
132
+ SELECT
133
+ table_schema,
134
+ table_name,
135
+ column_name,
136
+ ordinal_position,
137
+ data_type,
138
+ is_nullable,
139
+ column_default
140
+ FROM information_schema.columns
141
+ WHERE table_name = $1
142
+ AND ($2::text IS NULL OR table_schema = $2)
143
+ ORDER BY table_schema, table_name, ordinal_position
144
+ `,
145
+ [table, schema],
146
+ { database }
147
+ );
148
+
149
+ if (result.rows.length === 0) {
150
+ return fail("Tablo bulunamadi. 'schema' alanini gondererek tekrar deneyin.");
151
+ }
152
+
153
+ return ok(result.rows);
154
+ }
155
+
156
+ case "execute_select": {
157
+ const connection = args?.connection;
158
+ const database = normalizeDatabaseName(args?.database);
159
+ const query = args?.query;
160
+ const requestedRowLimit = args?.row_limit;
161
+
162
+ if (!connection) return fail("'connection' alani zorunludur.");
163
+ if (!query) return fail("'query' alani zorunludur.");
164
+
165
+ const connectionConfig = db.getConnectionConfig(connection);
166
+ const normalizedQuery = validateAndNormalizeSelect(query);
167
+ const appliedRowLimit = resolveRowLimit(requestedRowLimit, connectionConfig);
168
+ const limitedQuery = buildLimitedQuery(normalizedQuery, appliedRowLimit);
169
+ const result = await db.runReadOnly(connection, limitedQuery, [], { database });
170
+
171
+ return ok({
172
+ row_count: result.rowCount,
173
+ row_limit: appliedRowLimit,
174
+ rows: result.rows,
175
+ });
176
+ }
177
+
178
+ default:
179
+ return fail(`Bilinmeyen arac: ${name}`);
180
+ }
181
+ } catch (error) {
182
+ const message = error instanceof Error ? error.message : String(error);
183
+ return fail(`Hata: ${message}`);
184
+ }
185
+ });
186
+
187
+ async function main() {
188
+ const transport = new StdioServerTransport();
189
+ await server.connect(transport);
190
+ }
191
+
192
+ main().catch((error) => {
193
+ const message = error instanceof Error ? error.message : String(error);
194
+ console.error(`Sunucu baslatilamadi: ${message}`);
195
+ process.exit(1);
196
+ });
197
+
198
+ for (const signal of ["SIGINT", "SIGTERM"]) {
199
+ process.on(signal, async () => {
200
+ await db.closeAll();
201
+ process.exit(0);
202
+ });
203
+ }
@@ -0,0 +1,75 @@
1
+ const BLOCKED_KEYWORDS = [
2
+ "INSERT",
3
+ "UPDATE",
4
+ "DELETE",
5
+ "UPSERT",
6
+ "MERGE",
7
+ "DROP",
8
+ "ALTER",
9
+ "CREATE",
10
+ "TRUNCATE",
11
+ "GRANT",
12
+ "REVOKE",
13
+ "COPY",
14
+ "CALL",
15
+ "DO",
16
+ "VACUUM",
17
+ "ANALYZE",
18
+ "REINDEX",
19
+ "COMMENT",
20
+ "CLUSTER",
21
+ "LOCK",
22
+ "SET",
23
+ "RESET",
24
+ "BEGIN",
25
+ "COMMIT",
26
+ "ROLLBACK",
27
+ "INTO",
28
+ ];
29
+
30
+ const blockedKeywordRegex = new RegExp(`\\b(${BLOCKED_KEYWORDS.join("|")})\\b`, "i");
31
+
32
+ export function validateAndNormalizeSelect(rawQuery) {
33
+ if (typeof rawQuery !== "string") {
34
+ throw new Error("Sorgu metin (string) olmalidir.");
35
+ }
36
+
37
+ let query = rawQuery.trim();
38
+ if (!query) {
39
+ throw new Error("Sorgu bos olamaz.");
40
+ }
41
+
42
+ while (query.endsWith(";")) {
43
+ query = query.slice(0, -1).trim();
44
+ }
45
+
46
+ if (query.includes(";")) {
47
+ throw new Error("Tek seferde yalnizca tek bir SQL ifadesi calistirilabilir.");
48
+ }
49
+
50
+ if (!/^(SELECT|WITH)\b/i.test(query)) {
51
+ throw new Error("Yalnizca SELECT sorgularina izin verilir.");
52
+ }
53
+
54
+ if (blockedKeywordRegex.test(query)) {
55
+ throw new Error("Sorgu yazma/DDL iceren ifadeler barindiriyor. Yalnizca salt-okunur SELECT kullanin.");
56
+ }
57
+
58
+ return query;
59
+ }
60
+
61
+ export function resolveRowLimit(requestedRowLimit, connectionConfig) {
62
+ const defaultLimit = connectionConfig.default_row_limit;
63
+ const maxLimit = connectionConfig.max_row_limit;
64
+ const chosen = requestedRowLimit == null ? defaultLimit : Number(requestedRowLimit);
65
+
66
+ if (!Number.isFinite(chosen) || chosen <= 0) {
67
+ throw new Error("row_limit pozitif bir sayi olmalidir.");
68
+ }
69
+
70
+ return Math.min(Math.floor(chosen), maxLimit);
71
+ }
72
+
73
+ export function buildLimitedQuery(query, rowLimit) {
74
+ return `SELECT * FROM (${query}) AS mcp_readonly_query LIMIT ${rowLimit}`;
75
+ }
@@ -0,0 +1,89 @@
1
+ export function buildToolDefinitions(connectionNames) {
2
+ return [
3
+ {
4
+ name: "list_connections",
5
+ description: "Tanimli PostgreSQL baglantilarini listeler.",
6
+ inputSchema: {
7
+ type: "object",
8
+ properties: {},
9
+ },
10
+ },
11
+ {
12
+ name: "list_databases",
13
+ description: "Secilen baglantidaki erisilebilir veritabanlarini listeler.",
14
+ inputSchema: {
15
+ type: "object",
16
+ properties: {
17
+ connection: { type: "string", enum: connectionNames },
18
+ },
19
+ required: ["connection"],
20
+ },
21
+ },
22
+ {
23
+ name: "list_schemas",
24
+ description: "Secilen baglantidaki semalari listeler.",
25
+ inputSchema: {
26
+ type: "object",
27
+ properties: {
28
+ connection: { type: "string", enum: connectionNames },
29
+ database: { type: "string" },
30
+ },
31
+ required: ["connection"],
32
+ },
33
+ },
34
+ {
35
+ name: "list_tables",
36
+ description: "Secilen baglantida tablolari listeler. Isterseniz schema verebilirsiniz.",
37
+ inputSchema: {
38
+ type: "object",
39
+ properties: {
40
+ connection: { type: "string", enum: connectionNames },
41
+ database: { type: "string" },
42
+ schema: { type: "string" },
43
+ },
44
+ required: ["connection"],
45
+ },
46
+ },
47
+ {
48
+ name: "describe_table",
49
+ description: "Bir tablonun kolon bilgilerini dondurur.",
50
+ inputSchema: {
51
+ type: "object",
52
+ properties: {
53
+ connection: { type: "string", enum: connectionNames },
54
+ database: { type: "string" },
55
+ table: { type: "string" },
56
+ schema: { type: "string" },
57
+ },
58
+ required: ["connection", "table"],
59
+ },
60
+ },
61
+ {
62
+ name: "execute_select",
63
+ description: "Salt-okunur SELECT sorgusu calistirir. Satir limiti uygulanir.",
64
+ inputSchema: {
65
+ type: "object",
66
+ properties: {
67
+ connection: { type: "string", enum: connectionNames },
68
+ database: { type: "string" },
69
+ query: { type: "string" },
70
+ row_limit: { type: "number" },
71
+ },
72
+ required: ["connection", "query"],
73
+ },
74
+ },
75
+ ];
76
+ }
77
+
78
+ export function ok(payload) {
79
+ return {
80
+ content: [{ type: "text", text: JSON.stringify(payload, null, 2) }],
81
+ };
82
+ }
83
+
84
+ export function fail(message) {
85
+ return {
86
+ content: [{ type: "text", text: message }],
87
+ isError: true,
88
+ };
89
+ }
@@ -0,0 +1,46 @@
1
+ # SSH MCP Server (v2)
2
+
3
+ Modernized SSH MCP server with stricter validation, timeout controls, output limits, and optional command allowlists.
4
+
5
+ ## Features
6
+
7
+ - Config validation with `zod`
8
+ - Per-host timeout and output limits
9
+ - Optional per-host command allowlist
10
+ - Password auth (`password` or `passwordEnv`) and key auth (`keyPath`)
11
+ - Structured JSON responses for `list_hosts`, `test_connection`, `run_command`
12
+
13
+ ## Install
14
+
15
+ ```bash
16
+ npm install
17
+ ```
18
+
19
+ ## Configuration
20
+
21
+ 1. Copy the example file:
22
+
23
+ ```bash
24
+ cp config.example.json config.json
25
+ ```
26
+
27
+ 2. Fill your host definitions.
28
+
29
+ By default, the server looks for config in this order:
30
+
31
+ 1. `SSH_MCP_CONFIG_PATH`
32
+ 2. `./config.json` (next to this package)
33
+ 3. `config.json` in current working directory
34
+
35
+ ## Tools
36
+
37
+ - `list_hosts`
38
+ - `test_connection`
39
+ - `run_command`
40
+
41
+ ## Security Notes
42
+
43
+ - Prefer `keyPath` over plain password.
44
+ - Prefer `passwordEnv` over hardcoded passwords.
45
+ - Use `command_allowlist` for production hosts.
46
+ - Keep `StrictHostKeyChecking` at `accept-new` or `yes` unless you have a clear reason not to.
@@ -0,0 +1,23 @@
1
+ {
2
+ "default_timeout_seconds": 60,
3
+ "max_timeout_seconds": 600,
4
+ "max_output_bytes": 131072,
5
+ "hosts": {
6
+ "example": {
7
+ "host": "203.0.113.10",
8
+ "port": 22,
9
+ "user": "root",
10
+ "description": "Example host",
11
+ "keyPath": "~/.ssh/id_rsa",
12
+ "passwordEnv": "SSH_PASSWORD_EXAMPLE",
13
+ "connect_timeout_seconds": 15,
14
+ "ready_command": "echo SSH_OK",
15
+ "strict_host_key_checking": "accept-new",
16
+ "command_allowlist": [
17
+ "ls *",
18
+ "df -h*",
19
+ "systemctl status *"
20
+ ]
21
+ }
22
+ }
23
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "default_timeout_seconds": 60,
3
+ "max_timeout_seconds": 600,
4
+ "max_output_bytes": 131072,
5
+ "hosts": {}
6
+ }