@contextflo/postgres-mcp 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.
Files changed (60) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +267 -0
  3. package/dist/config.d.ts +47 -0
  4. package/dist/config.js +159 -0
  5. package/dist/config.js.map +1 -0
  6. package/dist/context/context-file.d.ts +53 -0
  7. package/dist/context/context-file.js +248 -0
  8. package/dist/context/context-file.js.map +1 -0
  9. package/dist/context/init.d.ts +14 -0
  10. package/dist/context/init.js +71 -0
  11. package/dist/context/init.js.map +1 -0
  12. package/dist/context/store.d.ts +34 -0
  13. package/dist/context/store.js +87 -0
  14. package/dist/context/store.js.map +1 -0
  15. package/dist/db/errors.d.ts +9 -0
  16. package/dist/db/errors.js +58 -0
  17. package/dist/db/errors.js.map +1 -0
  18. package/dist/db/introspection.d.ts +55 -0
  19. package/dist/db/introspection.js +178 -0
  20. package/dist/db/introspection.js.map +1 -0
  21. package/dist/db/pool.d.ts +37 -0
  22. package/dist/db/pool.js +213 -0
  23. package/dist/db/pool.js.map +1 -0
  24. package/dist/http.d.ts +8 -0
  25. package/dist/http.js +137 -0
  26. package/dist/http.js.map +1 -0
  27. package/dist/index.d.ts +2 -0
  28. package/dist/index.js +125 -0
  29. package/dist/index.js.map +1 -0
  30. package/dist/log.d.ts +46 -0
  31. package/dist/log.js +100 -0
  32. package/dist/log.js.map +1 -0
  33. package/dist/safety/errors.d.ts +11 -0
  34. package/dist/safety/errors.js +65 -0
  35. package/dist/safety/errors.js.map +1 -0
  36. package/dist/safety/validate.d.ts +12 -0
  37. package/dist/safety/validate.js +145 -0
  38. package/dist/safety/validate.js.map +1 -0
  39. package/dist/safety/walk.d.ts +26 -0
  40. package/dist/safety/walk.js +61 -0
  41. package/dist/safety/walk.js.map +1 -0
  42. package/dist/server.d.ts +9 -0
  43. package/dist/server.js +132 -0
  44. package/dist/server.js.map +1 -0
  45. package/dist/tools/add-table-context.d.ts +28 -0
  46. package/dist/tools/add-table-context.js +105 -0
  47. package/dist/tools/add-table-context.js.map +1 -0
  48. package/dist/tools/context.d.ts +12 -0
  49. package/dist/tools/context.js +2 -0
  50. package/dist/tools/context.js.map +1 -0
  51. package/dist/tools/get-table-context.d.ts +20 -0
  52. package/dist/tools/get-table-context.js +102 -0
  53. package/dist/tools/get-table-context.js.map +1 -0
  54. package/dist/tools/list-tables.d.ts +25 -0
  55. package/dist/tools/list-tables.js +78 -0
  56. package/dist/tools/list-tables.js.map +1 -0
  57. package/dist/tools/query.d.ts +22 -0
  58. package/dist/tools/query.js +138 -0
  59. package/dist/tools/query.js.map +1 -0
  60. package/package.json +59 -0
package/dist/server.js ADDED
@@ -0,0 +1,132 @@
1
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
2
+ import { parse as parseConnectionString } from 'pg-connection-string';
3
+ import { CallToolRequestSchema, ListResourcesRequestSchema, ListToolsRequestSchema, ReadResourceRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
4
+ import { getTableContext, listTables } from './db/introspection.js';
5
+ import { ADD_TABLE_CONTEXT_TOOL, runAddTableContextTool } from './tools/add-table-context.js';
6
+ import { GET_TABLE_CONTEXT_TOOL, runGetTableContextTool } from './tools/get-table-context.js';
7
+ import { LIST_TABLES_TOOL, runListTablesTool } from './tools/list-tables.js';
8
+ import { QUERY_TOOL, runQueryTool } from './tools/query.js';
9
+ const SCHEMA_PATH = 'schema';
10
+ const RESOURCE_LIST_LIMIT = 500;
11
+ export function createServer({ context, version, connectionString }) {
12
+ const server = new Server({ name: 'contextflo/postgres-mcp', version }, {
13
+ capabilities: { tools: {}, resources: {} },
14
+ // The curated preamble from .contextflo/context.md — business definitions, caveats,
15
+ // which tables to prefer — handed to the model once per session.
16
+ ...(context.contextFile.current.preamble ? { instructions: context.contextFile.current.preamble } : {}),
17
+ });
18
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({
19
+ tools: [
20
+ QUERY_TOOL,
21
+ LIST_TABLES_TOOL,
22
+ GET_TABLE_CONTEXT_TOOL,
23
+ ...(context.contextFile.writable ? [ADD_TABLE_CONTEXT_TOOL] : []),
24
+ ],
25
+ }));
26
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
27
+ const args = request.params.arguments;
28
+ switch (request.params.name) {
29
+ case QUERY_TOOL.name:
30
+ return runQueryTool(context, args);
31
+ case LIST_TABLES_TOOL.name:
32
+ return runListTablesTool(context, args);
33
+ case GET_TABLE_CONTEXT_TOOL.name:
34
+ return runGetTableContextTool(context, args);
35
+ case ADD_TABLE_CONTEXT_TOOL.name:
36
+ if (!context.contextFile.writable)
37
+ throw new Error(`Unknown tool: ${request.params.name}`);
38
+ return runAddTableContextTool(context, args);
39
+ default:
40
+ throw new Error(`Unknown tool: ${request.params.name}`);
41
+ }
42
+ });
43
+ registerResources(server, context, connectionString);
44
+ return server;
45
+ }
46
+ /**
47
+ * The archived server exposed table schemas as `postgres://<host>/<table>/schema`
48
+ * resources and nothing else. Keeping those URIs means anything pinned to them still
49
+ * resolves after swapping the package.
50
+ *
51
+ * They only ever listed the public schema; tables elsewhere get a schema-qualified URI
52
+ * rather than being invisible. Note that most clients never fetch resources on their own,
53
+ * which is why schema discovery lives in the tools — this is compatibility, not the path
54
+ * we expect models to take.
55
+ */
56
+ function registerResources(server, context, connectionString) {
57
+ const baseUrl = buildResourceBaseUrl(connectionString);
58
+ server.setRequestHandler(ListResourcesRequestSchema, async () => {
59
+ const { tables } = await listTables(context.database, { limit: RESOURCE_LIST_LIMIT });
60
+ return {
61
+ resources: tables.map((table) => ({
62
+ uri: resourceUri(baseUrl, table.schema, table.name),
63
+ mimeType: 'application/json',
64
+ name: `"${table.fullyQualifiedName}" database schema`,
65
+ })),
66
+ };
67
+ });
68
+ server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
69
+ const { schema, table } = parseResourceUri(request.params.uri);
70
+ const [described] = await getTableContext(context.database, [`${schema}.${table}`]);
71
+ if (!described) {
72
+ throw new Error(`Unknown resource: ${request.params.uri}`);
73
+ }
74
+ // Their payload was `[{ column_name, data_type }]`; keep those keys and append ours.
75
+ const columns = described.columns.map((column) => ({
76
+ column_name: column.name,
77
+ data_type: column.dataType,
78
+ is_nullable: column.isNullable,
79
+ description: column.description,
80
+ }));
81
+ return {
82
+ contents: [
83
+ {
84
+ uri: request.params.uri,
85
+ mimeType: 'application/json',
86
+ text: JSON.stringify(columns, null, 2),
87
+ },
88
+ ],
89
+ };
90
+ });
91
+ }
92
+ /**
93
+ * `postgres://user@host:port/database/`, built from the parsed pieces rather than
94
+ * `new URL(connectionString)`, which throws on `host=... dbname=...` strings and socket
95
+ * paths. The password is never included.
96
+ */
97
+ function buildResourceBaseUrl(connectionString) {
98
+ const { host, port, database, user } = parseConnectionString(connectionString);
99
+ let url;
100
+ try {
101
+ // A socket directory is not a hostname; the URI only needs to be stable, not dialable.
102
+ url = new URL(`postgres://${host && !host.startsWith('/') ? host : 'localhost'}${port ? `:${port}` : ''}/`);
103
+ }
104
+ catch {
105
+ url = new URL('postgres://localhost/');
106
+ }
107
+ if (user)
108
+ url.username = encodeURIComponent(user);
109
+ // Always a database segment, so parseResourceUri can tell it from a schema. The
110
+ // trailing slash keeps `new URL(path, base)` from eating the last path segment.
111
+ url.pathname = `/${encodeURIComponent(database || 'postgres')}/`;
112
+ return url;
113
+ }
114
+ function resourceUri(baseUrl, schema, table) {
115
+ const path = schema === 'public' ? `${table}/${SCHEMA_PATH}` : `${schema}/${table}/${SCHEMA_PATH}`;
116
+ return new URL(path, baseUrl).href;
117
+ }
118
+ function parseResourceUri(uri) {
119
+ const parts = new URL(uri).pathname.split('/').filter(Boolean);
120
+ if (parts.pop() !== SCHEMA_PATH) {
121
+ throw new Error(`Invalid resource URI: ${uri}`);
122
+ }
123
+ const table = parts.pop();
124
+ if (!table) {
125
+ throw new Error(`Invalid resource URI: ${uri}`);
126
+ }
127
+ // Anything left is the schema; the database name at the front is dropped, matching the
128
+ // archived server's parsing, which only ever looked at the last two segments.
129
+ const schema = parts.length > 1 ? parts[parts.length - 1] : 'public';
130
+ return { schema: decodeURIComponent(schema), table: decodeURIComponent(table) };
131
+ }
132
+ //# sourceMappingURL=server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,28 @@
1
+ import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';
2
+ import type { ToolContext } from './context.js';
3
+ export declare const ADD_TABLE_CONTEXT_TOOL: {
4
+ name: string;
5
+ description: string;
6
+ inputSchema: {
7
+ type: "object";
8
+ properties: {
9
+ table: {
10
+ type: string;
11
+ description: string;
12
+ };
13
+ note: {
14
+ type: string;
15
+ description: string;
16
+ };
17
+ columns: {
18
+ type: string;
19
+ additionalProperties: {
20
+ type: string;
21
+ };
22
+ description: string;
23
+ };
24
+ };
25
+ required: string[];
26
+ };
27
+ };
28
+ export declare function runAddTableContextTool(context: ToolContext, args: Record<string, unknown> | undefined): Promise<CallToolResult>;
@@ -0,0 +1,105 @@
1
+ import { getTableContext } from '../db/introspection.js';
2
+ /**
3
+ * Lets the agent write down what it learned the hard way — `amount` is in cents, `status`
4
+ * also holds 'void' on pre-2022 rows, `deleted_at IS NULL` is required everywhere — so the
5
+ * next session starts knowing it instead of rediscovering it.
6
+ *
7
+ * The database stays read-only; this writes only the local context file. Notes are
8
+ * appended, never substituted for what a person wrote, and land in a file that lives in
9
+ * the repo, so a human reviews them in a diff like any other change.
10
+ */
11
+ const MAX_NOTE_CHARS = 1_000;
12
+ const MAX_COLUMNS_PER_CALL = 50;
13
+ export const ADD_TABLE_CONTEXT_TOOL = {
14
+ name: 'add_table_context',
15
+ description: 'Record a durable fact about a table or its columns that you learned from working with the ' +
16
+ 'data and that someone writing SQL against it later would otherwise get wrong: units ' +
17
+ '("amount is in cents"), undocumented values ("status also contains \'void\' for rows before ' +
18
+ '2022"), required filters ("exclude rows where deleted_at is set"), or which table is the ' +
19
+ 'source of truth. The note is appended to the team\'s context file and shown by ' +
20
+ 'get_table_context from then on. Do not record what the schema already says (types, ' +
21
+ 'nullability, keys), one-off query results, or anything you have not verified against the data.',
22
+ inputSchema: {
23
+ type: 'object',
24
+ properties: {
25
+ table: {
26
+ type: 'string',
27
+ description: 'The table, ideally qualified as "schema.table".',
28
+ },
29
+ note: {
30
+ type: 'string',
31
+ description: 'Optional. One or two sentences about the table as a whole.',
32
+ },
33
+ columns: {
34
+ type: 'object',
35
+ additionalProperties: { type: 'string' },
36
+ description: 'Optional. Column name → one or two sentences about that column.',
37
+ },
38
+ },
39
+ required: ['table'],
40
+ },
41
+ };
42
+ export async function runAddTableContextTool(context, args) {
43
+ const table = typeof args?.table === 'string' ? args.table.trim() : '';
44
+ const note = typeof args?.note === 'string' && args.note.trim() !== '' ? args.note.trim() : undefined;
45
+ const rawColumns = isStringRecord(args?.columns) ? args.columns : {};
46
+ const columnEntries = Object.entries(rawColumns).filter(([, value]) => value.trim() !== '');
47
+ if (table === '')
48
+ return toolError('The "table" argument is required.');
49
+ if (args?.columns !== undefined && !isStringRecord(args.columns)) {
50
+ return toolError('"columns" must be an object mapping column names to notes, e.g. {"amount": "In cents."}.');
51
+ }
52
+ if (!note && columnEntries.length === 0) {
53
+ return toolError('Nothing to add: pass a "note" for the table, "columns" notes, or both.');
54
+ }
55
+ if (columnEntries.length > MAX_COLUMNS_PER_CALL) {
56
+ return toolError(`At most ${MAX_COLUMNS_PER_CALL} column notes per call.`);
57
+ }
58
+ const tooLong = [note, ...columnEntries.map(([, value]) => value)].some((text) => text !== undefined && text.length > MAX_NOTE_CHARS);
59
+ if (tooLong) {
60
+ return toolError(`Keep each note under ${MAX_NOTE_CHARS} characters — a sentence or two.`);
61
+ }
62
+ // Only tables that exist and are readable, so a guessed name cannot become a heading.
63
+ const matches = await getTableContext(context.database, [table]);
64
+ if (matches.length === 0) {
65
+ return toolError(`No readable table named ${table}. Use list_tables to find the exact name.`);
66
+ }
67
+ if (matches.length > 1) {
68
+ return toolError(`"${table}" matches more than one table: ${matches.map((match) => match.fullyQualifiedName).join(', ')}. ` +
69
+ 'Pass the schema-qualified name.');
70
+ }
71
+ const resolved = matches[0];
72
+ const actualColumns = new Map(resolved.columns.map((column) => [column.name.toLowerCase(), column.name]));
73
+ const unknown = columnEntries.filter(([name]) => !actualColumns.has(name.toLowerCase())).map(([name]) => name);
74
+ if (unknown.length > 0) {
75
+ return toolError(`${resolved.fullyQualifiedName} has no column ${unknown.join(', ')}. ` +
76
+ 'Use get_table_context to see its columns.');
77
+ }
78
+ if (!context.contextFile.writable || context.contextFile.path === null) {
79
+ return toolError('Context writes are turned off on this server (--no-context-writes).');
80
+ }
81
+ const columns = Object.fromEntries(columnEntries.map(([name, value]) => [actualColumns.get(name.toLowerCase()), value]));
82
+ await context.contextFile.addNotes(resolved.fullyQualifiedName, { note, columns });
83
+ const added = [note && 'the table note', columnEntries.length > 0 && `${columnEntries.length} column note(s)`]
84
+ .filter(Boolean)
85
+ .join(' and ');
86
+ return {
87
+ content: [
88
+ {
89
+ type: 'text',
90
+ text: `Added ${added} for ${resolved.fullyQualifiedName} to ${context.contextFile.path}.`,
91
+ },
92
+ ],
93
+ isError: false,
94
+ };
95
+ }
96
+ function isStringRecord(value) {
97
+ return (typeof value === 'object' &&
98
+ value !== null &&
99
+ !Array.isArray(value) &&
100
+ Object.values(value).every((item) => typeof item === 'string'));
101
+ }
102
+ function toolError(message) {
103
+ return { content: [{ type: 'text', text: message }], isError: true };
104
+ }
105
+ //# sourceMappingURL=add-table-context.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"add-table-context.js","sourceRoot":"","sources":["../../src/tools/add-table-context.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAA;AAGxD;;;;;;;;GAQG;AAEH,MAAM,cAAc,GAAG,KAAK,CAAA;AAC5B,MAAM,oBAAoB,GAAG,EAAE,CAAA;AAE/B,MAAM,CAAC,MAAM,sBAAsB,GAAG;IACpC,IAAI,EAAE,mBAAmB;IACzB,WAAW,EACT,4FAA4F;QAC5F,sFAAsF;QACtF,8FAA8F;QAC9F,2FAA2F;QAC3F,iFAAiF;QACjF,qFAAqF;QACrF,gGAAgG;IAClG,WAAW,EAAE;QACX,IAAI,EAAE,QAAiB;QACvB,UAAU,EAAE;YACV,KAAK,EAAE;gBACL,IAAI,EAAE,QAAQ;gBACd,WAAW,EAAE,iDAAiD;aAC/D;YACD,IAAI,EAAE;gBACJ,IAAI,EAAE,QAAQ;gBACd,WAAW,EAAE,4DAA4D;aAC1E;YACD,OAAO,EAAE;gBACP,IAAI,EAAE,QAAQ;gBACd,oBAAoB,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;gBACxC,WAAW,EAAE,iEAAiE;aAC/E;SACF;QACD,QAAQ,EAAE,CAAC,OAAO,CAAC;KACpB;CACF,CAAA;AAED,MAAM,CAAC,KAAK,UAAU,sBAAsB,CAC1C,OAAoB,EACpB,IAAyC;IAEzC,MAAM,KAAK,GAAG,OAAO,IAAI,EAAE,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAA;IACtE,MAAM,IAAI,GAAG,OAAO,IAAI,EAAE,IAAI,KAAK,QAAQ,IAAI,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,SAAS,CAAA;IACrG,MAAM,UAAU,GAAG,cAAc,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAA;IACpE,MAAM,aAAa,GAAG,MAAM,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,EAAE,CAAC,CAAA;IAE3F,IAAI,KAAK,KAAK,EAAE;QAAE,OAAO,SAAS,CAAC,mCAAmC,CAAC,CAAA;IACvE,IAAI,IAAI,EAAE,OAAO,KAAK,SAAS,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;QACjE,OAAO,SAAS,CAAC,0FAA0F,CAAC,CAAA;IAC9G,CAAC;IACD,IAAI,CAAC,IAAI,IAAI,aAAa,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACxC,OAAO,SAAS,CAAC,wEAAwE,CAAC,CAAA;IAC5F,CAAC;IACD,IAAI,aAAa,CAAC,MAAM,GAAG,oBAAoB,EAAE,CAAC;QAChD,OAAO,SAAS,CAAC,WAAW,oBAAoB,yBAAyB,CAAC,CAAA;IAC5E,CAAC;IACD,MAAM,OAAO,GAAG,CAAC,IAAI,EAAE,GAAG,aAAa,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CACrE,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,KAAK,SAAS,IAAI,IAAI,CAAC,MAAM,GAAG,cAAc,CAC7D,CAAA;IACD,IAAI,OAAO,EAAE,CAAC;QACZ,OAAO,SAAS,CAAC,wBAAwB,cAAc,kCAAkC,CAAC,CAAA;IAC5F,CAAC;IAED,sFAAsF;IACtF,MAAM,OAAO,GAAG,MAAM,eAAe,CAAC,OAAO,CAAC,QAAQ,EAAE,CAAC,KAAK,CAAC,CAAC,CAAA;IAChE,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACzB,OAAO,SAAS,CAAC,2BAA2B,KAAK,2CAA2C,CAAC,CAAA;IAC/F,CAAC;IACD,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACvB,OAAO,SAAS,CACd,IAAI,KAAK,kCAAkC,OAAO,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,kBAAkB,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI;YACxG,iCAAiC,CACpC,CAAA;IACH,CAAC;IAED,MAAM,QAAQ,GAAG,OAAO,CAAC,CAAC,CAAE,CAAA;IAC5B,MAAM,aAAa,GAAG,IAAI,GAAG,CAAC,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,WAAW,EAAE,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;IACzG,MAAM,OAAO,GAAG,aAAa,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC,CAAC,aAAa,CAAC,GAAG,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,CAAA;IAC9G,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACvB,OAAO,SAAS,CACd,GAAG,QAAQ,CAAC,kBAAkB,kBAAkB,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI;YACpE,2CAA2C,CAC9C,CAAA;IACH,CAAC;IAED,IAAI,CAAC,OAAO,CAAC,WAAW,CAAC,QAAQ,IAAI,OAAO,CAAC,WAAW,CAAC,IAAI,KAAK,IAAI,EAAE,CAAC;QACvE,OAAO,SAAS,CAAC,qEAAqE,CAAC,CAAA;IACzF,CAAC;IAED,MAAM,OAAO,GAAG,MAAM,CAAC,WAAW,CAChC,aAAa,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,CAAC,aAAa,CAAC,GAAG,CAAC,IAAI,CAAC,WAAW,EAAE,CAAE,EAAE,KAAK,CAAC,CAAC,CACtF,CAAA;IACD,MAAM,OAAO,CAAC,WAAW,CAAC,QAAQ,CAAC,QAAQ,CAAC,kBAAkB,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,CAAA;IAElF,MAAM,KAAK,GAAG,CAAC,IAAI,IAAI,gBAAgB,EAAE,aAAa,CAAC,MAAM,GAAG,CAAC,IAAI,GAAG,aAAa,CAAC,MAAM,iBAAiB,CAAC;SAC3G,MAAM,CAAC,OAAO,CAAC;SACf,IAAI,CAAC,OAAO,CAAC,CAAA;IAEhB,OAAO;QACL,OAAO,EAAE;YACP;gBACE,IAAI,EAAE,MAAM;gBACZ,IAAI,EAAE,SAAS,KAAK,QAAQ,QAAQ,CAAC,kBAAkB,OAAO,OAAO,CAAC,WAAW,CAAC,IAAI,GAAG;aAC1F;SACF;QACD,OAAO,EAAE,KAAK;KACf,CAAA;AACH,CAAC;AAED,SAAS,cAAc,CAAC,KAAc;IACpC,OAAO,CACL,OAAO,KAAK,KAAK,QAAQ;QACzB,KAAK,KAAK,IAAI;QACd,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QACrB,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,IAAI,KAAK,QAAQ,CAAC,CAC/D,CAAA;AACH,CAAC;AAED,SAAS,SAAS,CAAC,OAAe;IAChC,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAA;AACtE,CAAC"}
@@ -0,0 +1,12 @@
1
+ import type { ContextStore } from '../context/store.js';
2
+ import type { Database } from '../db/pool.js';
3
+ import type { QueryLog } from '../log.js';
4
+ /** Everything the tools share. Assembled once at startup in src/index.ts. */
5
+ export interface ToolContext {
6
+ database: Database;
7
+ contextFile: ContextStore;
8
+ log: QueryLog;
9
+ maxRows: number;
10
+ /** Character budget for one query result, so a wide result cannot flood the model's context. */
11
+ maxOutputChars: number;
12
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=context.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context.js","sourceRoot":"","sources":["../../src/tools/context.ts"],"names":[],"mappings":""}
@@ -0,0 +1,20 @@
1
+ import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';
2
+ import type { ToolContext } from './context.js';
3
+ export declare const GET_TABLE_CONTEXT_TOOL: {
4
+ name: string;
5
+ description: string;
6
+ inputSchema: {
7
+ type: "object";
8
+ properties: {
9
+ tables: {
10
+ type: string;
11
+ items: {
12
+ type: string;
13
+ };
14
+ description: string;
15
+ };
16
+ };
17
+ required: string[];
18
+ };
19
+ };
20
+ export declare function runGetTableContextTool(context: ToolContext, args: Record<string, unknown> | undefined): Promise<CallToolResult>;
@@ -0,0 +1,102 @@
1
+ import { getTableContext } from '../db/introspection.js';
2
+ import { notesForTable, preferCuratedDescription } from '../context/context-file.js';
3
+ export const GET_TABLE_CONTEXT_TOOL = {
4
+ name: 'get_table_context',
5
+ description: 'Describe one or more tables: columns, types, nullability, primary keys, foreign key ' +
6
+ 'targets, and any curated descriptions. Pass every candidate table at once rather than ' +
7
+ 'calling repeatedly. Read this before writing SQL against a table you have not used yet.',
8
+ inputSchema: {
9
+ type: 'object',
10
+ properties: {
11
+ tables: {
12
+ type: 'array',
13
+ items: { type: 'string' },
14
+ description: 'Table names, ideally qualified as "schema.table". An unqualified name resolves ' +
15
+ 'against any readable schema.',
16
+ },
17
+ },
18
+ required: ['tables'],
19
+ },
20
+ };
21
+ export async function runGetTableContextTool(context, args) {
22
+ const requested = Array.isArray(args?.tables)
23
+ ? args.tables.filter((name) => typeof name === 'string' && name.trim() !== '')
24
+ : [];
25
+ if (requested.length === 0) {
26
+ return {
27
+ content: [{ type: 'text', text: 'The "tables" argument is required: an array of table names.' }],
28
+ isError: true,
29
+ };
30
+ }
31
+ const [tables, document] = await Promise.all([
32
+ getTableContext(context.database, requested),
33
+ context.contextFile.refresh(),
34
+ ]);
35
+ if (tables.length === 0) {
36
+ return {
37
+ content: [
38
+ {
39
+ type: 'text',
40
+ text: `None of these are readable tables: ${requested.join(', ')}. ` +
41
+ 'Use list_tables to see what exists — the name may be in a different schema, or the ' +
42
+ 'connected role may not have SELECT on it.',
43
+ },
44
+ ],
45
+ isError: true,
46
+ };
47
+ }
48
+ const rendered = tables.map((table) => renderTable(document, table)).join('\n\n');
49
+ const missing = findMissing(requested, tables);
50
+ const notice = missing.length > 0 ? `\n\nNot found: ${missing.join(', ')}.` : '';
51
+ return { content: [{ type: 'text', text: rendered + notice }], isError: false };
52
+ }
53
+ function renderTable(document, table) {
54
+ const notes = notesForTable(document, table.fullyQualifiedName);
55
+ const description = preferCuratedDescription(notes?.description, table.description);
56
+ const heading = `## ${table.fullyQualifiedName} — ${table.kind}${formatRowCount(table.approximateRows)}`;
57
+ const lines = [heading];
58
+ if (description)
59
+ lines.push(description);
60
+ lines.push('');
61
+ for (const column of table.columns) {
62
+ lines.push(renderColumn(column, notes?.columns.get(column.name.toLowerCase())));
63
+ }
64
+ return lines.join('\n');
65
+ }
66
+ function renderColumn(column, curated) {
67
+ const facts = [column.dataType];
68
+ if (column.isPrimaryKey)
69
+ facts.push('primary key');
70
+ else if (!column.isNullable)
71
+ facts.push('not null');
72
+ if (column.references)
73
+ facts.push(`→ ${column.references}`);
74
+ if (column.enumValues?.length)
75
+ facts.push(formatEnumValues(column.enumValues));
76
+ if (column.defaultValue)
77
+ facts.push(`default ${column.defaultValue}`);
78
+ const description = preferCuratedDescription(curated, column.description);
79
+ return `- ${column.name} — ${facts.join(', ')}${description ? ` — ${description}` : ''}`;
80
+ }
81
+ /** The model otherwise guesses `'Active'` for a column whose values are `'active'`. */
82
+ const MAX_ENUM_VALUES = 30;
83
+ function formatEnumValues(values) {
84
+ const shown = values.slice(0, MAX_ENUM_VALUES).map((value) => `'${value}'`);
85
+ const more = values.length > MAX_ENUM_VALUES ? `, … ${values.length - MAX_ENUM_VALUES} more` : '';
86
+ return `one of ${shown.join(', ')}${more}`;
87
+ }
88
+ /** `reltuples` is a planner estimate, so say so rather than implying an exact count. */
89
+ function formatRowCount(approximateRows) {
90
+ if (approximateRows === null || approximateRows <= 0)
91
+ return '';
92
+ return `, ~${approximateRows.toLocaleString('en-US')} rows`;
93
+ }
94
+ function findMissing(requested, found) {
95
+ const resolved = new Set();
96
+ for (const table of found) {
97
+ resolved.add(table.fullyQualifiedName.toLowerCase());
98
+ resolved.add(table.fullyQualifiedName.split('.').pop().toLowerCase());
99
+ }
100
+ return requested.filter((name) => !resolved.has(name.toLowerCase()));
101
+ }
102
+ //# sourceMappingURL=get-table-context.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"get-table-context.js","sourceRoot":"","sources":["../../src/tools/get-table-context.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,25 @@
1
+ import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';
2
+ import type { ToolContext } from './context.js';
3
+ export declare const DEFAULT_LIST_LIMIT = 100;
4
+ export declare const LIST_TABLES_TOOL: {
5
+ name: string;
6
+ description: string;
7
+ inputSchema: {
8
+ type: "object";
9
+ properties: {
10
+ pattern: {
11
+ type: string;
12
+ description: string;
13
+ };
14
+ schema: {
15
+ type: string;
16
+ description: string;
17
+ };
18
+ limit: {
19
+ type: string;
20
+ description: string;
21
+ };
22
+ };
23
+ };
24
+ };
25
+ export declare function runListTablesTool(context: ToolContext, args: Record<string, unknown> | undefined): Promise<CallToolResult>;
@@ -0,0 +1,78 @@
1
+ import { listTables } from '../db/introspection.js';
2
+ import { notesForTable, preferCuratedDescription } from '../context/context-file.js';
3
+ export const DEFAULT_LIST_LIMIT = 100;
4
+ const MAX_LIST_LIMIT = 500;
5
+ export const LIST_TABLES_TOOL = {
6
+ name: 'list_tables',
7
+ description: 'List the tables, views, and materialized views this connection can read, with their ' +
8
+ 'descriptions. Call this first when you need to know what data exists — do not guess ' +
9
+ 'table names. Pass `pattern` to narrow by a word appearing anywhere in the table name ' +
10
+ 'or its description; call it again with a different word to try a synonym. For anything ' +
11
+ 'more specific — searching column names, types, or comments — query information_schema ' +
12
+ 'and pg_catalog directly with the query tool, they are ordinary tables.',
13
+ inputSchema: {
14
+ type: 'object',
15
+ properties: {
16
+ pattern: {
17
+ type: 'string',
18
+ description: 'Optional. Case-insensitive substring matched anywhere in the table name, the ' +
19
+ 'qualified schema.table name, or the table description. Omit to list everything.',
20
+ },
21
+ schema: {
22
+ type: 'string',
23
+ description: 'Optional. Restrict to one schema, e.g. "analytics".',
24
+ },
25
+ limit: {
26
+ type: 'integer',
27
+ description: `Optional. Maximum tables to return (default ${DEFAULT_LIST_LIMIT}, max ${MAX_LIST_LIMIT}).`,
28
+ },
29
+ },
30
+ },
31
+ };
32
+ export async function runListTablesTool(context, args) {
33
+ const pattern = optionalString(args?.pattern);
34
+ const schema = optionalString(args?.schema);
35
+ const limit = Math.min(Math.max(Number(args?.limit) || DEFAULT_LIST_LIMIT, 1), MAX_LIST_LIMIT);
36
+ const [result, document] = await Promise.all([
37
+ listTables(context.database, { pattern, schema, limit }),
38
+ context.contextFile.refresh(),
39
+ ]);
40
+ // Several clients never show the model the server's instructions, so the team's
41
+ // business definitions also ride along with the tool the model is told to call first.
42
+ const preamble = document.preamble ? `Business context from the team:\n\n${document.preamble}\n\n---\n\n` : '';
43
+ if (result.tables.length === 0) {
44
+ return {
45
+ content: [{ type: 'text', text: preamble + describeEmptyResult(pattern, schema) }],
46
+ isError: false,
47
+ };
48
+ }
49
+ const lines = result.tables.map((table) => {
50
+ const curated = notesForTable(document, table.fullyQualifiedName)?.description;
51
+ const description = preferCuratedDescription(curated, table.description);
52
+ return `${table.fullyQualifiedName} — ${table.kind}${description ? ` — ${description}` : ''}`;
53
+ });
54
+ const header = result.totalMatches > result.tables.length
55
+ ? `Showing ${result.tables.length} of ${result.totalMatches} matching tables. ` +
56
+ 'Narrow with `pattern` or `schema`, or raise `limit`.'
57
+ : `${result.tables.length} table${result.tables.length === 1 ? '' : 's'}.`;
58
+ return {
59
+ content: [{ type: 'text', text: `${preamble}${header}\n\n${lines.join('\n')}` }],
60
+ isError: false,
61
+ };
62
+ }
63
+ function describeEmptyResult(pattern, schema) {
64
+ if (!pattern && !schema) {
65
+ return ('No readable tables found. The connected role may not have SELECT on anything, or the ' +
66
+ 'database may be empty.');
67
+ }
68
+ const filters = [pattern && `pattern "${pattern}"`, schema && `schema "${schema}"`]
69
+ .filter(Boolean)
70
+ .join(' and ');
71
+ return (`No tables matched ${filters}. Try a synonym, drop the filters to see everything, or search ` +
72
+ 'column names directly, e.g. ' +
73
+ "SELECT table_schema, table_name, column_name FROM information_schema.columns WHERE column_name ILIKE '%term%'");
74
+ }
75
+ function optionalString(value) {
76
+ return typeof value === 'string' && value.trim() !== '' ? value.trim() : undefined;
77
+ }
78
+ //# sourceMappingURL=list-tables.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"list-tables.js","sourceRoot":"","sources":["../../src/tools/list-tables.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAA;AACnD,OAAO,EAAE,aAAa,EAAE,wBAAwB,EAAE,MAAM,4BAA4B,CAAA;AAGpF,MAAM,CAAC,MAAM,kBAAkB,GAAG,GAAG,CAAA;AACrC,MAAM,cAAc,GAAG,GAAG,CAAA;AAE1B,MAAM,CAAC,MAAM,gBAAgB,GAAG;IAC9B,IAAI,EAAE,aAAa;IACnB,WAAW,EACT,sFAAsF;QACtF,sFAAsF;QACtF,uFAAuF;QACvF,yFAAyF;QACzF,wFAAwF;QACxF,wEAAwE;IAC1E,WAAW,EAAE;QACX,IAAI,EAAE,QAAiB;QACvB,UAAU,EAAE;YACV,OAAO,EAAE;gBACP,IAAI,EAAE,QAAQ;gBACd,WAAW,EACT,+EAA+E;oBAC/E,iFAAiF;aACpF;YACD,MAAM,EAAE;gBACN,IAAI,EAAE,QAAQ;gBACd,WAAW,EAAE,qDAAqD;aACnE;YACD,KAAK,EAAE;gBACL,IAAI,EAAE,SAAS;gBACf,WAAW,EAAE,+CAA+C,kBAAkB,SAAS,cAAc,IAAI;aAC1G;SACF;KACF;CACF,CAAA;AAED,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,OAAoB,EACpB,IAAyC;IAEzC,MAAM,OAAO,GAAG,cAAc,CAAC,IAAI,EAAE,OAAO,CAAC,CAAA;IAC7C,MAAM,MAAM,GAAG,cAAc,CAAC,IAAI,EAAE,MAAM,CAAC,CAAA;IAC3C,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,kBAAkB,EAAE,CAAC,CAAC,EAAE,cAAc,CAAC,CAAA;IAE9F,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC;QAC3C,UAAU,CAAC,OAAO,CAAC,QAAQ,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC;QACxD,OAAO,CAAC,WAAW,CAAC,OAAO,EAAE;KAC9B,CAAC,CAAA;IACF,gFAAgF;IAChF,sFAAsF;IACtF,MAAM,QAAQ,GAAG,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC,sCAAsC,QAAQ,CAAC,QAAQ,aAAa,CAAC,CAAC,CAAC,EAAE,CAAA;IAE9G,IAAI,MAAM,CAAC,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC/B,OAAO;YACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,GAAG,mBAAmB,CAAC,OAAO,EAAE,MAAM,CAAC,EAAE,CAAC;YAClF,OAAO,EAAE,KAAK;SACf,CAAA;IACH,CAAC;IAED,MAAM,KAAK,GAAG,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE;QACxC,MAAM,OAAO,GAAG,aAAa,CAAC,QAAQ,EAAE,KAAK,CAAC,kBAAkB,CAAC,EAAE,WAAW,CAAA;QAC9E,MAAM,WAAW,GAAG,wBAAwB,CAAC,OAAO,EAAE,KAAK,CAAC,WAAW,CAAC,CAAA;QAExE,OAAO,GAAG,KAAK,CAAC,kBAAkB,MAAM,KAAK,CAAC,IAAI,GAAG,WAAW,CAAC,CAAC,CAAC,MAAM,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAAA;IAC/F,CAAC,CAAC,CAAA;IAEF,MAAM,MAAM,GACV,MAAM,CAAC,YAAY,GAAG,MAAM,CAAC,MAAM,CAAC,MAAM;QACxC,CAAC,CAAC,WAAW,MAAM,CAAC,MAAM,CAAC,MAAM,OAAO,MAAM,CAAC,YAAY,oBAAoB;YAC7E,sDAAsD;QACxD,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,CAAC,MAAM,SAAS,MAAM,CAAC,MAAM,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,GAAG,CAAA;IAE9E,OAAO;QACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,QAAQ,GAAG,MAAM,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC;QAChF,OAAO,EAAE,KAAK;KACf,CAAA;AACH,CAAC;AAED,SAAS,mBAAmB,CAAC,OAA2B,EAAE,MAA0B;IAClF,IAAI,CAAC,OAAO,IAAI,CAAC,MAAM,EAAE,CAAC;QACxB,OAAO,CACL,uFAAuF;YACvF,wBAAwB,CACzB,CAAA;IACH,CAAC;IAED,MAAM,OAAO,GAAG,CAAC,OAAO,IAAI,YAAY,OAAO,GAAG,EAAE,MAAM,IAAI,WAAW,MAAM,GAAG,CAAC;SAChF,MAAM,CAAC,OAAO,CAAC;SACf,IAAI,CAAC,OAAO,CAAC,CAAA;IAEhB,OAAO,CACL,qBAAqB,OAAO,iEAAiE;QAC7F,8BAA8B;QAC9B,+GAA+G,CAChH,CAAA;AACH,CAAC;AAED,SAAS,cAAc,CAAC,KAAc;IACpC,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,SAAS,CAAA;AACpF,CAAC"}
@@ -0,0 +1,22 @@
1
+ import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';
2
+ import type { ToolContext } from './context.js';
3
+ /**
4
+ * Name, argument, and result shape match the archived
5
+ * `@modelcontextprotocol/server-postgres` so existing prompts and configs keep working
6
+ * when someone swaps the package name.
7
+ */
8
+ export declare const QUERY_TOOL: {
9
+ name: string;
10
+ description: string;
11
+ inputSchema: {
12
+ type: "object";
13
+ properties: {
14
+ sql: {
15
+ type: string;
16
+ description: string;
17
+ };
18
+ };
19
+ required: string[];
20
+ };
21
+ };
22
+ export declare function runQueryTool(context: ToolContext, args: Record<string, unknown> | undefined): Promise<CallToolResult>;