@tacuchi/agent-workflow-cli 23.0.0 → 24.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/README.md +37 -1
- package/dist/adapters/postgres-readonly-tools.js +277 -0
- package/dist/adapters/postgres-readonly-tools.js.map +1 -0
- package/dist/application/database-mcp-server.js +168 -0
- package/dist/application/database-mcp-server.js.map +1 -0
- package/dist/application/database-mcp-stdio.js +137 -0
- package/dist/application/database-mcp-stdio.js.map +1 -0
- package/dist/application/database-tool-catalog.js +226 -0
- package/dist/application/database-tool-catalog.js.map +1 -0
- package/dist/application/dsn-reader-service.js +1 -1
- package/dist/application/lock-service.js +96 -31
- package/dist/application/lock-service.js.map +1 -1
- package/dist/application/logging/log-events.js +19 -3
- package/dist/application/logging/log-events.js.map +1 -1
- package/dist/application/logging/logger.js +2 -2
- package/dist/application/logging/logger.js.map +1 -1
- package/dist/application/mcp-connections-service.js +115 -41
- package/dist/application/mcp-connections-service.js.map +1 -1
- package/dist/application/mcp-doctor-service.js +98 -54
- package/dist/application/mcp-doctor-service.js.map +1 -1
- package/dist/application/mcp-entry-classification.js +60 -0
- package/dist/application/mcp-entry-classification.js.map +1 -0
- package/dist/application/mcp-host-reader.js +102 -86
- package/dist/application/mcp-host-reader.js.map +1 -1
- package/dist/application/mcp-host-receipt-service.js +399 -0
- package/dist/application/mcp-host-receipt-service.js.map +1 -0
- package/dist/application/mcp-host-receipt-store.js +122 -0
- package/dist/application/mcp-host-receipt-store.js.map +1 -0
- package/dist/application/mcp-host-receipts.js +28 -0
- package/dist/application/mcp-host-receipts.js.map +1 -0
- package/dist/application/mcp-host-writer.js +209 -38
- package/dist/application/mcp-host-writer.js.map +1 -1
- package/dist/application/mcp-launch-probe-service.js +142 -0
- package/dist/application/mcp-launch-probe-service.js.map +1 -0
- package/dist/application/mcp-migration-service.js +351 -0
- package/dist/application/mcp-migration-service.js.map +1 -0
- package/dist/application/mcp-native-host-check-service.js +154 -0
- package/dist/application/mcp-native-host-check-service.js.map +1 -0
- package/dist/application/mcp-receipt-registration-service.js +105 -0
- package/dist/application/mcp-receipt-registration-service.js.map +1 -0
- package/dist/application/mcp-receipt-removal-service.js +62 -0
- package/dist/application/mcp-receipt-removal-service.js.map +1 -0
- package/dist/application/mcp-remove-service.js +83 -24
- package/dist/application/mcp-remove-service.js.map +1 -1
- package/dist/application/mcp-scope-common.js +1 -1
- package/dist/application/mcp-scope-common.js.map +1 -1
- package/dist/application/mcp-setup-service.js +59 -24
- package/dist/application/mcp-setup-service.js.map +1 -1
- package/dist/application/mcp-stdio-probe.js +526 -0
- package/dist/application/mcp-stdio-probe.js.map +1 -0
- package/dist/application/mcp-test-connection-service.js +23 -50
- package/dist/application/mcp-test-connection-service.js.map +1 -1
- package/dist/application/self/host-states.js.map +1 -1
- package/dist/application/self/install-skill.js +4 -34
- package/dist/application/self/install-skill.js.map +1 -1
- package/dist/application/self/mcp-config.js +369 -59
- package/dist/application/self/mcp-config.js.map +1 -1
- package/dist/application/self/mcp-via-state.js +9 -6
- package/dist/application/self/mcp-via-state.js.map +1 -1
- package/dist/application/self/uninstall-skill.js +3 -18
- package/dist/application/self/uninstall-skill.js.map +1 -1
- package/dist/application/self/uninstall.js +4 -23
- package/dist/application/self/uninstall.js.map +1 -1
- package/dist/application/workspace-init-service.js +4 -6
- package/dist/application/workspace-init-service.js.map +1 -1
- package/dist/cli/commands/index.js +2 -0
- package/dist/cli/commands/index.js.map +1 -1
- package/dist/cli/commands/mcp.js +642 -55
- package/dist/cli/commands/mcp.js.map +1 -1
- package/dist/cli/commands/tool.js +141 -0
- package/dist/cli/commands/tool.js.map +1 -0
- package/dist/cli/help-groups.js +1 -1
- package/dist/cli/help-groups.js.map +1 -1
- package/dist/cli/main.js +189 -57
- package/dist/cli/main.js.map +1 -1
- package/dist/cli/parser.js +5 -1
- package/dist/cli/parser.js.map +1 -1
- package/dist/cli/registry.js.map +1 -1
- package/dist/cli/render.js +15 -2
- package/dist/cli/render.js.map +1 -1
- package/dist/cli/tui/components/host-admin-section.js +2 -46
- package/dist/cli/tui/components/host-admin-section.js.map +1 -1
- package/dist/cli/tui/tabs/mcp-tab-helpers.js +71 -0
- package/dist/cli/tui/tabs/mcp-tab-helpers.js.map +1 -1
- package/dist/cli/tui/tabs/mcp-tab.js +101 -42
- package/dist/cli/tui/tabs/mcp-tab.js.map +1 -1
- package/dist/domain/database-tools.js +445 -0
- package/dist/domain/database-tools.js.map +1 -0
- package/dist/domain/flow/authority.js +4 -0
- package/dist/domain/flow/authority.js.map +1 -1
- package/dist/domain/mcp-entry.js +131 -17
- package/dist/domain/mcp-entry.js.map +1 -1
- package/dist/domain/postgres-object-search.js +320 -0
- package/dist/domain/postgres-object-search.js.map +1 -0
- package/dist/domain/redaction.js +47 -0
- package/dist/domain/redaction.js.map +1 -0
- package/dist/domain/workline-mcp-entry.js +6 -9
- package/dist/domain/workline-mcp-entry.js.map +1 -1
- package/dist/ports/postgres-tools.js +10 -0
- package/dist/ports/postgres-tools.js.map +1 -0
- package/package.json +5 -1
- package/dist/application/mcp-dbhub-launcher.js +0 -216
- package/dist/application/mcp-dbhub-launcher.js.map +0 -1
- package/dist/application/self/mcp-offer.js +0 -96
- package/dist/application/self/mcp-offer.js.map +0 -1
package/README.md
CHANGED
|
@@ -124,10 +124,46 @@ Running `agent-workflow` (or `aw`) with no arguments opens the tab-based TUI:
|
|
|
124
124
|
| **Status** | Doctor dashboard: CLI / hosts / hooks / MCP tiles + daily operational logs. The hosts tile jumps to [Workline]. |
|
|
125
125
|
| **Workline** | Per-host administration of the bundled `w` SKILL (install / reinstall / uninstall, `hooks armed` state) plus a compact flows overview. |
|
|
126
126
|
| **Project** | Workspace sources, branches and git-flow actions. |
|
|
127
|
-
| **MCP** |
|
|
127
|
+
| **MCP** | Tools PostgreSQL de solo lectura. `mcp-connections.json` v2 es la única autoridad de alias, provider y variable DSN: registrá con `aw self mcp use-env --name alpha --dsn-var ALPHA_DATABASE_URL`. La TUI instala descriptores absolutos en user scope; `aw mcp setup` conserva además el modo workspace portable dependiente de `PATH`. Cada host muestra configuración, launchability, recarga pendiente y carga observada; Codex queda opcional porque existe el fallback local `aw tool call`. |
|
|
128
128
|
| **Skills** | Standalone third-party skills manager (skills.sh model): register from `owner/repo`, a git URL (`#ref` supported) or an absolute local path; install materializes a canonical copy in `~/.agents/skills/<name>` (the open-standard dir every non-Claude host scans) plus a symlink replica in `~/.claude/skills/<name>` (copy fallback where symlinks are unavailable). Seeded with the recommended external skills from the companion marketplace README — keep both lists in sync. |
|
|
129
129
|
| **Config** | Namespace, host-targeting preferences, and the workspace branch defaults (written to the WORKSPACE block). |
|
|
130
130
|
|
|
131
|
+
### PostgreSQL MCP y fallback local
|
|
132
|
+
|
|
133
|
+
V1 publica sólo `execute_sql` y `search_objects` para PostgreSQL. Ambas rutas usan el
|
|
134
|
+
mismo catálogo y el mismo JSON canónico: el servidor MCP devuelve ese JSON en
|
|
135
|
+
`content[0].text`, y el CLI lo imprime directamente, sin el envelope general.
|
|
136
|
+
|
|
137
|
+
```sh
|
|
138
|
+
aw tool list --connection qtc-cert
|
|
139
|
+
aw tool call execute_sql --connection qtc-cert --input-json '{"sql":"SELECT 1"}'
|
|
140
|
+
printf '%s' '{"object_type":"table","pattern":"user%"}' \
|
|
141
|
+
| aw tool call search_objects --connection qtc-cert --input-json -
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
`execute_sql` acepta una sola sentencia de lectura y ejecuta cada llamada en una conexión
|
|
145
|
+
nueva, transacción `READ ONLY`, timeout de 30 s y rollback final. El resultado se limita a
|
|
146
|
+
1.000 filas y 4 MiB; la entrada JSON se limita a 1 MiB. Nunca pongas un DSN, SQL ni resultados
|
|
147
|
+
en los archivos de host, recibos u logs operativos.
|
|
148
|
+
|
|
149
|
+
`search_objects` conserva el envelope de DBHub 1.2.1: `object_type`, `pattern`, filtros
|
|
150
|
+
opcionales, `detail_level`, `count`, `results` y `truncated`. Sus siete tipos y tres niveles
|
|
151
|
+
mantienen los campos de descubrimiento de DBHub; `truncated` sólo vale `true` cuando se observó
|
|
152
|
+
una fila adicional que quedó fuera del límite.
|
|
153
|
+
|
|
154
|
+
`READ ONLY` no reemplaza una cuenta PostgreSQL de mínimo privilegio: no uses superuser, permisos
|
|
155
|
+
de escritura/creación, ni membresía o vía `SET ROLE` hacia `pg_signal_backend`,
|
|
156
|
+
`pg_signal_autovacuum_worker`, `pg_read_server_files`, `pg_write_server_files` o
|
|
157
|
+
`pg_execute_server_program`. El ejecutor rechaza superuser y esos roles de servidor; con
|
|
158
|
+
`aw mcp doctor --probe data`, los demás privilegios de escritura se informan como advertencia y la
|
|
159
|
+
conexión no queda marcada como segura. Tampoco concedas `EXECUTE` sobre extensiones o funciones con
|
|
160
|
+
efectos externos.
|
|
161
|
+
|
|
162
|
+
`aw mcp serve-db --instance <nombre>` es el servidor stdio; `aw mcp dbhub` sigue durante una
|
|
163
|
+
versión como alias deprecado. `aw mcp doctor --probe launch` comprueba `initialize → initialized
|
|
164
|
+
→ tools/list`; `--probe data` añade `SELECT 1 AS ok`. `aw mcp migrate` sólo muestra preview;
|
|
165
|
+
escribir requiere `--apply --force` y no se ejecuta automáticamente.
|
|
166
|
+
|
|
131
167
|
## Namespace resolution
|
|
132
168
|
|
|
133
169
|
Workspace artifacts live under `.<namespace>/`. Resolution order (first match wins):
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
import { Client } from "pg";
|
|
2
|
+
import Cursor from "pg-cursor";
|
|
3
|
+
import { MAX_TOOL_RESULT_BYTES, MAX_TOOL_ROWS } from "../domain/database-tools.js";
|
|
4
|
+
import { PostgresToolError } from "../ports/postgres-tools.js";
|
|
5
|
+
const CONNECTION_TIMEOUT_MS = 10_000;
|
|
6
|
+
const STATEMENT_TIMEOUT_MS = 30_000;
|
|
7
|
+
/**
|
|
8
|
+
* The PostgreSQL adapter owns the safety envelope for every catalog call:
|
|
9
|
+
* connection lifetime, transaction read-only state, timeout, bounded rows and
|
|
10
|
+
* disposal. It never returns a driver error or DSN to the caller.
|
|
11
|
+
*/
|
|
12
|
+
export class PostgresReadonlyTools {
|
|
13
|
+
async execute(sql, dsn, options = {}) {
|
|
14
|
+
return await this.withReadonlyClient(dsn, async (client) => await readBoundedCursor(client, sql, [], options.signal), ...(options.signal === undefined ? [] : [{ signal: options.signal }]));
|
|
15
|
+
}
|
|
16
|
+
async query(sql, values, dsn, options = {}) {
|
|
17
|
+
return await this.withReadonlyClient(dsn, async (client) => await readBoundedCursor(client, sql, values, options.signal), ...(options.signal === undefined ? [] : [{ signal: options.signal }]));
|
|
18
|
+
}
|
|
19
|
+
async inspectRole(dsn) {
|
|
20
|
+
return await this.withReadonlyClient(dsn, async (client) => {
|
|
21
|
+
const result = await client.query(`SELECT
|
|
22
|
+
r.rolsuper AS superuser,
|
|
23
|
+
r.rolcreaterole AS can_create_role,
|
|
24
|
+
r.rolcreatedb AS can_create_database,
|
|
25
|
+
(
|
|
26
|
+
has_database_privilege(current_database(), 'CREATE')
|
|
27
|
+
OR EXISTS (
|
|
28
|
+
SELECT 1 FROM pg_namespace n
|
|
29
|
+
WHERE has_schema_privilege(n.oid, 'CREATE')
|
|
30
|
+
)
|
|
31
|
+
OR EXISTS (
|
|
32
|
+
SELECT 1 FROM pg_class c
|
|
33
|
+
WHERE c.relkind IN ('r', 'p', 'v', 'm', 'f')
|
|
34
|
+
AND (
|
|
35
|
+
has_table_privilege(c.oid, 'INSERT, UPDATE, DELETE, TRUNCATE')
|
|
36
|
+
OR has_any_column_privilege(c.oid, 'INSERT, UPDATE')
|
|
37
|
+
)
|
|
38
|
+
)
|
|
39
|
+
OR EXISTS (
|
|
40
|
+
SELECT 1 FROM pg_class sequence_class
|
|
41
|
+
WHERE sequence_class.relkind = 'S'
|
|
42
|
+
AND has_sequence_privilege(sequence_class.oid, 'UPDATE')
|
|
43
|
+
)
|
|
44
|
+
) AS can_write,
|
|
45
|
+
COALESCE(
|
|
46
|
+
(
|
|
47
|
+
SELECT bool_or(
|
|
48
|
+
pg_has_role(current_user, dangerous_role.oid, 'USAGE')
|
|
49
|
+
OR pg_has_role(current_user, dangerous_role.oid, 'MEMBER')
|
|
50
|
+
)
|
|
51
|
+
FROM pg_roles dangerous_role
|
|
52
|
+
WHERE dangerous_role.rolname IN (
|
|
53
|
+
'pg_signal_backend',
|
|
54
|
+
'pg_signal_autovacuum_worker',
|
|
55
|
+
'pg_read_server_files',
|
|
56
|
+
'pg_write_server_files',
|
|
57
|
+
'pg_execute_server_program'
|
|
58
|
+
)
|
|
59
|
+
),
|
|
60
|
+
false
|
|
61
|
+
) AS unsafe_server_role
|
|
62
|
+
FROM pg_roles r
|
|
63
|
+
WHERE r.rolname = current_user`);
|
|
64
|
+
const role = result.rows[0];
|
|
65
|
+
if (role === undefined) {
|
|
66
|
+
throw new PostgresToolError("DATABASE_ROLE_UNKNOWN", "No se pudo verificar el rol PostgreSQL.");
|
|
67
|
+
}
|
|
68
|
+
return {
|
|
69
|
+
superuser: role.superuser,
|
|
70
|
+
canCreateRole: role.can_create_role,
|
|
71
|
+
canCreateDatabase: role.can_create_database,
|
|
72
|
+
canWrite: role.can_write,
|
|
73
|
+
unsafeServerRole: role.unsafe_server_role,
|
|
74
|
+
transactionReadOnly: true,
|
|
75
|
+
};
|
|
76
|
+
}, { allowSuperuser: true });
|
|
77
|
+
}
|
|
78
|
+
async withReadonlyClient(dsn, work, options = {}) {
|
|
79
|
+
const client = new Client({
|
|
80
|
+
connectionString: dsn,
|
|
81
|
+
connectionTimeoutMillis: CONNECTION_TIMEOUT_MS,
|
|
82
|
+
statement_timeout: STATEMENT_TIMEOUT_MS,
|
|
83
|
+
});
|
|
84
|
+
// A connected pg client can emit a later socket error after rejecting the
|
|
85
|
+
// active query. Keep it from becoming an unhandled EventEmitter error; the
|
|
86
|
+
// awaited operation below still maps it to a secret-free tool failure.
|
|
87
|
+
client.on("error", () => { });
|
|
88
|
+
let transactionStarted = false;
|
|
89
|
+
let connected = false;
|
|
90
|
+
const abort = () => {
|
|
91
|
+
// Calls use an isolated client, so destroying it is a reliable PostgreSQL
|
|
92
|
+
// cancellation mechanism. It avoids `pg`'s `Client.cancel` trap, whose
|
|
93
|
+
// receiver must be a separate control client and can otherwise corrupt
|
|
94
|
+
// the active connection. The server rolls the read-only transaction back.
|
|
95
|
+
void client.end().catch(() => undefined);
|
|
96
|
+
};
|
|
97
|
+
if (options.signal?.aborted)
|
|
98
|
+
throw queryCancelled();
|
|
99
|
+
options.signal?.addEventListener("abort", abort, { once: true });
|
|
100
|
+
try {
|
|
101
|
+
await client.connect();
|
|
102
|
+
connected = true;
|
|
103
|
+
await client.query("SET default_transaction_read_only = on");
|
|
104
|
+
await client.query("BEGIN READ ONLY");
|
|
105
|
+
transactionStarted = true;
|
|
106
|
+
const readOnly = await client.query("SHOW transaction_read_only");
|
|
107
|
+
if (readOnly.rows[0]?.transaction_read_only !== "on") {
|
|
108
|
+
throw new PostgresToolError("READ_ONLY_POLICY", "PostgreSQL no confirmó una transacción de solo lectura.");
|
|
109
|
+
}
|
|
110
|
+
if (!options.allowSuperuser)
|
|
111
|
+
await rejectUnsafeExecutionRole(client);
|
|
112
|
+
await client.query("SET LOCAL statement_timeout = '30s'");
|
|
113
|
+
return await work(client);
|
|
114
|
+
}
|
|
115
|
+
catch (error) {
|
|
116
|
+
if (options.signal?.aborted)
|
|
117
|
+
throw queryCancelled();
|
|
118
|
+
if (error instanceof PostgresToolError)
|
|
119
|
+
throw error;
|
|
120
|
+
throw safeDriverError(error, connected);
|
|
121
|
+
}
|
|
122
|
+
finally {
|
|
123
|
+
options.signal?.removeEventListener("abort", abort);
|
|
124
|
+
// An abort tears down this per-call socket, which makes PostgreSQL roll
|
|
125
|
+
// the transaction back itself. Do not issue a query or wait for cursor
|
|
126
|
+
// cleanup on a connection that intentionally cannot become ready again.
|
|
127
|
+
if (transactionStarted && !options.signal?.aborted) {
|
|
128
|
+
await client.query("ROLLBACK").catch(() => undefined);
|
|
129
|
+
}
|
|
130
|
+
await client.end().catch(() => undefined);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
async function readBoundedCursor(client, sql, values, signal) {
|
|
135
|
+
if (signal?.aborted)
|
|
136
|
+
throw queryCancelled();
|
|
137
|
+
const cursor = client.query(new Cursor(sql, [...values]));
|
|
138
|
+
const cancel = () => {
|
|
139
|
+
// `withReadonlyClient` also observes this signal during connection/setup.
|
|
140
|
+
// Keep the cursor listener so a live read is terminated immediately.
|
|
141
|
+
void client.end().catch(() => undefined);
|
|
142
|
+
};
|
|
143
|
+
signal?.addEventListener("abort", cancel, { once: true });
|
|
144
|
+
let closeCursor = true;
|
|
145
|
+
try {
|
|
146
|
+
return await readCursorRows(cursor, signal);
|
|
147
|
+
}
|
|
148
|
+
catch (error) {
|
|
149
|
+
closeCursor = closesActiveCursorAfter(error);
|
|
150
|
+
if (signal?.aborted)
|
|
151
|
+
throw queryCancelled();
|
|
152
|
+
throw error;
|
|
153
|
+
}
|
|
154
|
+
finally {
|
|
155
|
+
signal?.removeEventListener("abort", cancel);
|
|
156
|
+
// pg-cursor close waits for ReadyForQuery. After explicit cancellation the
|
|
157
|
+
// isolated client socket was destroyed, so waiting here would turn a
|
|
158
|
+
// successful cancel into a hung finally; the client teardown owns cleanup.
|
|
159
|
+
// pg-cursor sends ReadyForQuery itself after a query error. Calling close
|
|
160
|
+
// after its read promise rejects can subscribe too late and wait forever
|
|
161
|
+
// for a second ReadyForQuery. The isolated transaction rollback below owns
|
|
162
|
+
// cleanup in that case; close only when the portal still needs a Close +
|
|
163
|
+
// Sync (normal completion or a local size-bound rejection).
|
|
164
|
+
if (!signal?.aborted && closeCursor)
|
|
165
|
+
await cursor.close().catch(() => undefined);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
function closesActiveCursorAfter(error) {
|
|
169
|
+
// A result-size rejection originates locally while the cursor portal is
|
|
170
|
+
// still active, so Close + Sync is required before ROLLBACK can proceed.
|
|
171
|
+
// PostgreSQL errors already emitted their own Sync; closing then can wait for
|
|
172
|
+
// a second ReadyForQuery that never arrives.
|
|
173
|
+
return error instanceof PostgresToolError && error.code === "RESULT_TOO_LARGE";
|
|
174
|
+
}
|
|
175
|
+
function queryCancelled() {
|
|
176
|
+
return new PostgresToolError("QUERY_CANCELLED", "La consulta fue cancelada.");
|
|
177
|
+
}
|
|
178
|
+
async function rejectUnsafeExecutionRole(client) {
|
|
179
|
+
const result = await client.query(`SELECT
|
|
180
|
+
role_info.rolsuper AS superuser,
|
|
181
|
+
COALESCE(
|
|
182
|
+
(
|
|
183
|
+
SELECT bool_or(
|
|
184
|
+
pg_has_role(current_user, dangerous_role.oid, 'USAGE')
|
|
185
|
+
OR pg_has_role(current_user, dangerous_role.oid, 'MEMBER')
|
|
186
|
+
)
|
|
187
|
+
FROM pg_roles dangerous_role
|
|
188
|
+
WHERE dangerous_role.rolname IN (
|
|
189
|
+
'pg_signal_backend',
|
|
190
|
+
'pg_signal_autovacuum_worker',
|
|
191
|
+
'pg_read_server_files',
|
|
192
|
+
'pg_write_server_files',
|
|
193
|
+
'pg_execute_server_program'
|
|
194
|
+
)
|
|
195
|
+
),
|
|
196
|
+
false
|
|
197
|
+
) AS unsafe_server_role
|
|
198
|
+
FROM pg_roles role_info
|
|
199
|
+
WHERE role_info.rolname = current_user`);
|
|
200
|
+
const role = result.rows[0];
|
|
201
|
+
if (role === undefined) {
|
|
202
|
+
throw new PostgresToolError("DATABASE_ROLE_UNKNOWN", "No se pudo verificar la seguridad del rol PostgreSQL.");
|
|
203
|
+
}
|
|
204
|
+
if (role.superuser) {
|
|
205
|
+
throw new PostgresToolError("DATABASE_ROLE_UNSAFE", "La ejecución de tools requiere un rol PostgreSQL que no sea superusuario.");
|
|
206
|
+
}
|
|
207
|
+
if (role.unsafe_server_role) {
|
|
208
|
+
throw new PostgresToolError("DATABASE_ROLE_UNSAFE", "La ejecución de tools requiere un rol sin privilegios de servidor peligrosos.");
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
async function readCursorRows(cursor, signal) {
|
|
212
|
+
const rows = [];
|
|
213
|
+
// Track the JSON representation as rows arrive. `DatabaseToolCatalog` makes
|
|
214
|
+
// the final exact 4 MiB decision once it adds the canonical envelope, while
|
|
215
|
+
// this transport-level bound prevents a 1001-row cursor result from being
|
|
216
|
+
// accumulated in memory first.
|
|
217
|
+
let serializedBytes = 2; // []
|
|
218
|
+
while (rows.length <= MAX_TOOL_ROWS) {
|
|
219
|
+
const batch = await cursor.read(1);
|
|
220
|
+
if (signal?.aborted)
|
|
221
|
+
throw queryCancelled();
|
|
222
|
+
const row = batch[0];
|
|
223
|
+
if (row === undefined)
|
|
224
|
+
return { rows, truncated: false };
|
|
225
|
+
if (rows.length === MAX_TOOL_ROWS)
|
|
226
|
+
return { rows, truncated: true };
|
|
227
|
+
serializedBytes += serializedRowBytes(row) + (rows.length === 0 ? 0 : 1);
|
|
228
|
+
if (serializedBytes > MAX_TOOL_RESULT_BYTES) {
|
|
229
|
+
throw new PostgresToolError("RESULT_TOO_LARGE", "El resultado serializado supera 4 MiB.");
|
|
230
|
+
}
|
|
231
|
+
rows.push(row);
|
|
232
|
+
}
|
|
233
|
+
return { rows, truncated: true };
|
|
234
|
+
}
|
|
235
|
+
function serializedRowBytes(row) {
|
|
236
|
+
try {
|
|
237
|
+
return Buffer.byteLength(JSON.stringify(row), "utf8");
|
|
238
|
+
}
|
|
239
|
+
catch {
|
|
240
|
+
throw new PostgresToolError("RESULT_TOO_LARGE", "El resultado no se puede serializar de forma segura.");
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
function safeDriverError(error, connected) {
|
|
244
|
+
const code = readDriverCode(error);
|
|
245
|
+
if (!connected) {
|
|
246
|
+
return new PostgresToolError("DATABASE_CONNECTION_FAILED", "No se pudo abrir la conexión PostgreSQL.");
|
|
247
|
+
}
|
|
248
|
+
if (code === "42601") {
|
|
249
|
+
return new PostgresToolError("INVALID_SQL", "La consulta PostgreSQL tiene sintaxis inválida.");
|
|
250
|
+
}
|
|
251
|
+
if (code === "57014") {
|
|
252
|
+
return new PostgresToolError("QUERY_TIMEOUT", "La consulta excedió el límite de 30 segundos.");
|
|
253
|
+
}
|
|
254
|
+
if (code === "25006") {
|
|
255
|
+
return new PostgresToolError("READ_ONLY_POLICY", "PostgreSQL rechazó una operación fuera de la transacción de solo lectura.");
|
|
256
|
+
}
|
|
257
|
+
if (code?.startsWith("28") || code === "3D000") {
|
|
258
|
+
return new PostgresToolError("DATABASE_CONNECTION_FAILED", "No se pudo autenticar o abrir la base de datos.");
|
|
259
|
+
}
|
|
260
|
+
if (code === "ECONNREFUSED" ||
|
|
261
|
+
code === "ECONNRESET" ||
|
|
262
|
+
code === "ENETUNREACH" ||
|
|
263
|
+
code === "EHOSTUNREACH" ||
|
|
264
|
+
code === "ENOTFOUND" ||
|
|
265
|
+
code === "ETIMEDOUT" ||
|
|
266
|
+
code === "ERR_INVALID_URL") {
|
|
267
|
+
return new PostgresToolError("DATABASE_CONNECTION_FAILED", "No se pudo abrir la conexión PostgreSQL.");
|
|
268
|
+
}
|
|
269
|
+
return new PostgresToolError("DATABASE_QUERY_FAILED", "PostgreSQL no pudo completar la operación.");
|
|
270
|
+
}
|
|
271
|
+
function readDriverCode(error) {
|
|
272
|
+
if (error === null || typeof error !== "object")
|
|
273
|
+
return undefined;
|
|
274
|
+
const code = error.code;
|
|
275
|
+
return typeof code === "string" ? code : undefined;
|
|
276
|
+
}
|
|
277
|
+
//# sourceMappingURL=postgres-readonly-tools.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"postgres-readonly-tools.js","sourceRoot":"","sources":["../../src/adapters/postgres-readonly-tools.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import { encodeToolResponse, toolFailure } from "../domain/database-tools.js";
|
|
2
|
+
const PROTOCOL_VERSION = "2025-06-18";
|
|
3
|
+
/** JSON-RPC adapter over the shared catalog. It owns no tool rules. */
|
|
4
|
+
export function createDatabaseMcpServer(deps) {
|
|
5
|
+
let lifecycle = "new";
|
|
6
|
+
const activeCalls = new Map();
|
|
7
|
+
const cancelledBeforeStart = new Set();
|
|
8
|
+
function reply(id, result) {
|
|
9
|
+
if (!isJsonRpcResponseId(id))
|
|
10
|
+
return;
|
|
11
|
+
deps.send({ jsonrpc: "2.0", id, result });
|
|
12
|
+
}
|
|
13
|
+
function replyError(id, code, message) {
|
|
14
|
+
if (!isJsonRpcResponseId(id))
|
|
15
|
+
return;
|
|
16
|
+
deps.send({ jsonrpc: "2.0", id, error: { code, message } });
|
|
17
|
+
}
|
|
18
|
+
function toolResult(response) {
|
|
19
|
+
return {
|
|
20
|
+
content: [{ type: "text", text: encodeToolResponse(response), mimeType: "application/json" }],
|
|
21
|
+
...(response.success ? {} : { isError: true }),
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
async function dispatchToolCall(message) {
|
|
25
|
+
const params = isRecord(message.params) ? message.params : {};
|
|
26
|
+
const name = typeof params.name === "string" ? params.name : "";
|
|
27
|
+
const requestKey = requestIdKey(message.id);
|
|
28
|
+
// tools/call is a request, not a notification. Rejecting an id-less call
|
|
29
|
+
// prevents an untracked database operation that the client cannot cancel.
|
|
30
|
+
if (requestKey === undefined) {
|
|
31
|
+
replyError(message.id, -32600, "tools/call requiere un id de solicitud válido");
|
|
32
|
+
return true;
|
|
33
|
+
}
|
|
34
|
+
if (cancelledBeforeStart.delete(requestKey)) {
|
|
35
|
+
reply(message.id, toolResult(toolFailure("QUERY_CANCELLED", "La consulta fue cancelada.")));
|
|
36
|
+
return true;
|
|
37
|
+
}
|
|
38
|
+
const controller = new AbortController();
|
|
39
|
+
activeCalls.set(requestKey, controller);
|
|
40
|
+
try {
|
|
41
|
+
const outcome = await deps.catalog.call({
|
|
42
|
+
tool: name,
|
|
43
|
+
connection: deps.connection,
|
|
44
|
+
input: params.arguments,
|
|
45
|
+
signal: controller.signal,
|
|
46
|
+
});
|
|
47
|
+
reply(message.id, toolResult(outcome.response));
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
reply(message.id, toolResult(toolFailure("TOOL_EXECUTION_FAILED", "La tool no pudo completar la solicitud.")));
|
|
51
|
+
}
|
|
52
|
+
finally {
|
|
53
|
+
if (activeCalls.get(requestKey) === controller) {
|
|
54
|
+
activeCalls.delete(requestKey);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return true;
|
|
58
|
+
}
|
|
59
|
+
async function cancelToolCall(message) {
|
|
60
|
+
const params = isRecord(message.params) ? message.params : {};
|
|
61
|
+
const key = requestIdKey(params.requestId);
|
|
62
|
+
if (key === undefined)
|
|
63
|
+
return true;
|
|
64
|
+
const active = activeCalls.get(key);
|
|
65
|
+
if (active !== undefined) {
|
|
66
|
+
active.abort();
|
|
67
|
+
return true;
|
|
68
|
+
}
|
|
69
|
+
// A stdio transport may receive cancellation while an earlier call keeps a
|
|
70
|
+
// later request queued. Retain bounded intent so that later call never
|
|
71
|
+
// reaches PostgreSQL once it starts.
|
|
72
|
+
cancelledBeforeStart.add(key);
|
|
73
|
+
if (cancelledBeforeStart.size > 1_000) {
|
|
74
|
+
const oldest = cancelledBeforeStart.values().next().value;
|
|
75
|
+
if (oldest !== undefined)
|
|
76
|
+
cancelledBeforeStart.delete(oldest);
|
|
77
|
+
}
|
|
78
|
+
return true;
|
|
79
|
+
}
|
|
80
|
+
async function initialize(message) {
|
|
81
|
+
if (lifecycle !== "new") {
|
|
82
|
+
replyError(message.id, -32600, "initialize sólo puede llamarse una vez por sesión");
|
|
83
|
+
return true;
|
|
84
|
+
}
|
|
85
|
+
lifecycle = "awaiting-initialized";
|
|
86
|
+
reply(message.id, {
|
|
87
|
+
protocolVersion: PROTOCOL_VERSION,
|
|
88
|
+
capabilities: { tools: {} },
|
|
89
|
+
serverInfo: { name: "agent-workflow-db", version: "1" },
|
|
90
|
+
});
|
|
91
|
+
return true;
|
|
92
|
+
}
|
|
93
|
+
async function initialized() {
|
|
94
|
+
if (lifecycle === "awaiting-initialized") {
|
|
95
|
+
lifecycle = "ready";
|
|
96
|
+
await deps.onHostLoadObserved?.();
|
|
97
|
+
}
|
|
98
|
+
return true;
|
|
99
|
+
}
|
|
100
|
+
function readyOrReply(message) {
|
|
101
|
+
if (lifecycle === "ready")
|
|
102
|
+
return true;
|
|
103
|
+
replyError(message.id, -32002, "Server not initialized");
|
|
104
|
+
return false;
|
|
105
|
+
}
|
|
106
|
+
async function listTools(message) {
|
|
107
|
+
if (!readyOrReply(message))
|
|
108
|
+
return true;
|
|
109
|
+
try {
|
|
110
|
+
const list = deps.catalog.list(deps.connection);
|
|
111
|
+
reply(message.id, "tools" in list.payload ? list.payload : toolResult(list.payload));
|
|
112
|
+
}
|
|
113
|
+
catch {
|
|
114
|
+
reply(message.id, toolResult(toolFailure("MCP_CONNECTION_INVALID", "No se pudo resolver la conexión solicitada.")));
|
|
115
|
+
}
|
|
116
|
+
return true;
|
|
117
|
+
}
|
|
118
|
+
async function callTool(message) {
|
|
119
|
+
if (!readyOrReply(message))
|
|
120
|
+
return true;
|
|
121
|
+
return await dispatchToolCall(message);
|
|
122
|
+
}
|
|
123
|
+
const handlers = {
|
|
124
|
+
initialize,
|
|
125
|
+
"notifications/initialized": async () => await initialized(),
|
|
126
|
+
"notifications/cancelled": async (message) => await cancelToolCall(message),
|
|
127
|
+
"tools/list": listTools,
|
|
128
|
+
"tools/call": callTool,
|
|
129
|
+
};
|
|
130
|
+
return {
|
|
131
|
+
async handle(message) {
|
|
132
|
+
if (!isJsonRpcEnvelope(message))
|
|
133
|
+
return false;
|
|
134
|
+
const handler = handlers[message.method];
|
|
135
|
+
if (handler !== undefined)
|
|
136
|
+
return await handler(message);
|
|
137
|
+
replyError(message.id, -32601, "Method not found");
|
|
138
|
+
return true;
|
|
139
|
+
},
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
/** Accept only envelopes for which every response remains valid JSON-RPC. */
|
|
143
|
+
function isJsonRpcEnvelope(value) {
|
|
144
|
+
return (isRecord(value) &&
|
|
145
|
+
value.jsonrpc === "2.0" &&
|
|
146
|
+
typeof value.method === "string" &&
|
|
147
|
+
isJsonRpcId(value.id));
|
|
148
|
+
}
|
|
149
|
+
function isJsonRpcId(value) {
|
|
150
|
+
return value === undefined || isJsonRpcResponseId(value);
|
|
151
|
+
}
|
|
152
|
+
function isJsonRpcResponseId(value) {
|
|
153
|
+
return value === null || typeof value === "string" || isFiniteId(value);
|
|
154
|
+
}
|
|
155
|
+
function isFiniteId(value) {
|
|
156
|
+
return typeof value === "number" && Number.isFinite(value);
|
|
157
|
+
}
|
|
158
|
+
function requestIdKey(value) {
|
|
159
|
+
if (typeof value === "string")
|
|
160
|
+
return `string:${value}`;
|
|
161
|
+
if (typeof value === "number" && Number.isFinite(value))
|
|
162
|
+
return `number:${value}`;
|
|
163
|
+
return undefined;
|
|
164
|
+
}
|
|
165
|
+
function isRecord(value) {
|
|
166
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
167
|
+
}
|
|
168
|
+
//# sourceMappingURL=database-mcp-server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"database-mcp-server.js","sourceRoot":"","sources":["../../src/application/database-mcp-server.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { StringDecoder } from "node:string_decoder";
|
|
2
|
+
import { MAX_TOOL_TRANSPORT_INPUT_BYTES } from "../domain/database-tools.js";
|
|
3
|
+
import { createDatabaseMcpServer } from "./database-mcp-server.js";
|
|
4
|
+
// The RPC envelope is allowed a small fixed overhead beyond the canonical 1
|
|
5
|
+
// MiB tool input. The shared catalog still makes the exact input decision.
|
|
6
|
+
const MAX_MCP_INPUT_FRAME_BYTES = MAX_TOOL_TRANSPORT_INPUT_BYTES;
|
|
7
|
+
/**
|
|
8
|
+
* Line-framed stdio transport. stdout is exclusively JSON-RPC; malformed input
|
|
9
|
+
* is diagnosed on stderr and cannot corrupt a later valid request.
|
|
10
|
+
*/
|
|
11
|
+
export function runDatabaseMcpStdio(deps) {
|
|
12
|
+
const server = createDatabaseMcpServer({
|
|
13
|
+
send: (message) => deps.output.write(`${JSON.stringify(message)}\n`),
|
|
14
|
+
catalog: deps.catalog,
|
|
15
|
+
connection: deps.connection,
|
|
16
|
+
...(deps.onHostLoadObserved === undefined
|
|
17
|
+
? {}
|
|
18
|
+
: { onHostLoadObserved: deps.onHostLoadObserved }),
|
|
19
|
+
});
|
|
20
|
+
const frames = {
|
|
21
|
+
buffer: "",
|
|
22
|
+
bufferedBytes: 0,
|
|
23
|
+
discardingOversizedLine: false,
|
|
24
|
+
decoder: new StringDecoder("utf8"),
|
|
25
|
+
};
|
|
26
|
+
let queue = Promise.resolve();
|
|
27
|
+
function enqueue(line) {
|
|
28
|
+
let message;
|
|
29
|
+
try {
|
|
30
|
+
message = JSON.parse(line);
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
deps.diagnostics.write("aw mcp serve-db: línea JSON-RPC inválida descartada\n");
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
// Cancellation must not wait behind a running database call in the serial
|
|
37
|
+
// queue. All other frames retain order, including the lifecycle sequence.
|
|
38
|
+
if (isCancellationNotification(message)) {
|
|
39
|
+
void server.handle(message).catch(() => {
|
|
40
|
+
deps.diagnostics.write("aw mcp serve-db: línea JSON-RPC inválida descartada\n");
|
|
41
|
+
});
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
queue = queue.then(async () => {
|
|
45
|
+
try {
|
|
46
|
+
await server.handle(message);
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
deps.diagnostics.write("aw mcp serve-db: línea JSON-RPC inválida descartada\n");
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
return new Promise((resolve) => {
|
|
54
|
+
let ended = false;
|
|
55
|
+
const finish = () => {
|
|
56
|
+
if (ended)
|
|
57
|
+
return;
|
|
58
|
+
ended = true;
|
|
59
|
+
// An unterminated tail is not a JSON-RPC frame. Do not enqueue it, but
|
|
60
|
+
// flush the decoder so no multibyte bytes remain retained by the stream.
|
|
61
|
+
if (!frames.discardingOversizedLine)
|
|
62
|
+
frames.buffer += frames.decoder.end();
|
|
63
|
+
void queue.then(() => resolve());
|
|
64
|
+
};
|
|
65
|
+
deps.input.on("data", (chunk) => {
|
|
66
|
+
const bytes = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
67
|
+
enqueueConsumption(consumeChunk(frames, bytes), deps, enqueue);
|
|
68
|
+
});
|
|
69
|
+
deps.input.on("end", finish);
|
|
70
|
+
deps.input.on("close", finish);
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
function isCancellationNotification(message) {
|
|
74
|
+
return (message !== null &&
|
|
75
|
+
typeof message === "object" &&
|
|
76
|
+
!Array.isArray(message) &&
|
|
77
|
+
message.method === "notifications/cancelled");
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Splits raw bytes at newline boundaries before decoding. This matters for an
|
|
81
|
+
* oversized frame already terminated in the same chunk: appending/splitting a
|
|
82
|
+
* decoded 100 MiB string merely to reject it would defeat the input bound.
|
|
83
|
+
*/
|
|
84
|
+
function consumeChunk(state, bytes) {
|
|
85
|
+
const frames = [];
|
|
86
|
+
let oversizedFrames = 0;
|
|
87
|
+
let offset = 0;
|
|
88
|
+
while (offset < bytes.length) {
|
|
89
|
+
const newline = bytes.indexOf(0x0a, offset);
|
|
90
|
+
const end = newline < 0 ? bytes.length : newline + 1;
|
|
91
|
+
const segment = bytes.subarray(offset, end);
|
|
92
|
+
if (state.discardingOversizedLine) {
|
|
93
|
+
if (newline >= 0) {
|
|
94
|
+
state.discardingOversizedLine = false;
|
|
95
|
+
state.decoder = new StringDecoder("utf8");
|
|
96
|
+
}
|
|
97
|
+
offset = end;
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
if (state.bufferedBytes + segment.length > MAX_MCP_INPUT_FRAME_BYTES) {
|
|
101
|
+
state.buffer = "";
|
|
102
|
+
state.bufferedBytes = 0;
|
|
103
|
+
state.decoder = new StringDecoder("utf8");
|
|
104
|
+
state.discardingOversizedLine = newline < 0;
|
|
105
|
+
oversizedFrames += 1;
|
|
106
|
+
offset = end;
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
state.bufferedBytes += segment.length;
|
|
110
|
+
state.buffer += state.decoder.write(segment);
|
|
111
|
+
if (newline >= 0)
|
|
112
|
+
consumeCompleteLine(state, frames);
|
|
113
|
+
offset = end;
|
|
114
|
+
}
|
|
115
|
+
return { frames, oversizedFrames };
|
|
116
|
+
}
|
|
117
|
+
function consumeCompleteLine(state, frames) {
|
|
118
|
+
const newline = state.buffer.indexOf("\n");
|
|
119
|
+
if (newline < 0)
|
|
120
|
+
return;
|
|
121
|
+
const trimmed = state.buffer.slice(0, newline).replace(/\r$/, "").trim();
|
|
122
|
+
state.buffer = state.buffer.slice(newline + 1);
|
|
123
|
+
state.bufferedBytes = 0;
|
|
124
|
+
if (trimmed.length > 0)
|
|
125
|
+
frames.push(trimmed);
|
|
126
|
+
}
|
|
127
|
+
function reportOversizedFrames(output, count) {
|
|
128
|
+
for (let index = 0; index < count; index += 1) {
|
|
129
|
+
output.write("aw mcp serve-db: frame JSON-RPC demasiado grande descartado\n");
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
function enqueueConsumption(consumed, deps, enqueue) {
|
|
133
|
+
reportOversizedFrames(deps.diagnostics, consumed.oversizedFrames);
|
|
134
|
+
for (const frame of consumed.frames)
|
|
135
|
+
enqueue(frame);
|
|
136
|
+
}
|
|
137
|
+
//# sourceMappingURL=database-mcp-stdio.js.map
|