mssql-mcp 2.1.1 → 2.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +161 -89
- package/dist/src/config.d.ts +39 -0
- package/dist/src/config.js +37 -0
- package/dist/src/constants.d.ts +15 -0
- package/dist/src/constants.js +15 -0
- package/dist/src/db/connection.d.ts +8 -0
- package/dist/src/db/connection.js +80 -0
- package/dist/src/db/query-builders.d.ts +3 -0
- package/dist/src/db/query-builders.js +58 -0
- package/dist/src/db/validators.d.ts +5 -0
- package/dist/src/db/validators.js +25 -0
- package/dist/src/index.js +26 -0
- package/dist/src/resources/connection.d.ts +2 -0
- package/dist/src/resources/connection.js +19 -0
- package/dist/src/resources/metadata.d.ts +2 -0
- package/dist/src/resources/metadata.js +58 -0
- package/dist/src/schemas/outputs.d.ts +153 -0
- package/dist/src/schemas/outputs.js +54 -0
- package/dist/src/server.d.ts +2 -0
- package/dist/src/server.js +27 -0
- package/dist/src/tools/connect.d.ts +2 -0
- package/dist/src/tools/connect.js +45 -0
- package/dist/src/tools/databases.d.ts +2 -0
- package/dist/src/tools/databases.js +53 -0
- package/dist/src/tools/procedure.d.ts +2 -0
- package/dist/src/tools/procedure.js +106 -0
- package/dist/src/tools/query.d.ts +2 -0
- package/dist/src/tools/query.js +92 -0
- package/dist/src/tools/schema.d.ts +2 -0
- package/dist/src/tools/schema.js +96 -0
- package/dist/src/tools/status.d.ts +2 -0
- package/dist/src/tools/status.js +17 -0
- package/dist/src/tools/table.d.ts +2 -0
- package/dist/src/tools/table.js +261 -0
- package/dist/src/transports/http.d.ts +3 -0
- package/dist/src/transports/http.js +54 -0
- package/dist/src/transports/stdio.d.ts +2 -0
- package/dist/src/transports/stdio.js +23 -0
- package/dist/src/types.d.ts +37 -0
- package/dist/src/types.js +1 -0
- package/dist/src/utils/errors.d.ts +19 -0
- package/dist/src/utils/errors.js +29 -0
- package/dist/src/utils/format.d.ts +6 -0
- package/dist/src/utils/format.js +27 -0
- package/dist/src/utils/markdown.d.ts +3 -0
- package/dist/src/utils/markdown.js +33 -0
- package/dist/src/utils/pagination.d.ts +3 -0
- package/dist/src/utils/pagination.js +18 -0
- package/dist/tests/unit/markdown.test.d.ts +1 -0
- package/dist/tests/unit/markdown.test.js +70 -0
- package/dist/tests/unit/query-builders.test.d.ts +1 -0
- package/dist/tests/unit/query-builders.test.js +63 -0
- package/dist/tests/unit/tool-contracts.test.d.ts +1 -0
- package/dist/tests/unit/tool-contracts.test.js +62 -0
- package/dist/tests/unit/validators.test.d.ts +1 -0
- package/dist/tests/unit/validators.test.js +51 -0
- package/package.json +10 -6
- package/dist/index.js +0 -648
- /package/dist/{index.d.ts → src/index.d.ts} +0 -0
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { getPool } from "../db/connection.js";
|
|
2
|
+
function notConnectedText() {
|
|
3
|
+
return JSON.stringify({ error: "Not connected. Use connect_database first." });
|
|
4
|
+
}
|
|
5
|
+
export function registerMetadataResources(server) {
|
|
6
|
+
server.registerResource("databases", "mssql://databases", {
|
|
7
|
+
title: "Databases",
|
|
8
|
+
description: "Snapshot list of all databases on the SQL Server instance.",
|
|
9
|
+
mimeType: "application/json",
|
|
10
|
+
}, async () => {
|
|
11
|
+
const pool = getPool();
|
|
12
|
+
if (!pool?.connected) {
|
|
13
|
+
return {
|
|
14
|
+
contents: [
|
|
15
|
+
{ uri: "mssql://databases", text: notConnectedText(), mimeType: "application/json" },
|
|
16
|
+
],
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
const result = await pool
|
|
20
|
+
.request()
|
|
21
|
+
.query(`SELECT name, state_desc FROM sys.databases ORDER BY name`);
|
|
22
|
+
return {
|
|
23
|
+
contents: [
|
|
24
|
+
{
|
|
25
|
+
uri: "mssql://databases",
|
|
26
|
+
text: JSON.stringify(result.recordset, null, 2),
|
|
27
|
+
mimeType: "application/json",
|
|
28
|
+
},
|
|
29
|
+
],
|
|
30
|
+
};
|
|
31
|
+
});
|
|
32
|
+
server.registerResource("schemas", "mssql://schemas", {
|
|
33
|
+
title: "Schemas",
|
|
34
|
+
description: "Snapshot list of all schemas in the current database.",
|
|
35
|
+
mimeType: "application/json",
|
|
36
|
+
}, async () => {
|
|
37
|
+
const pool = getPool();
|
|
38
|
+
if (!pool?.connected) {
|
|
39
|
+
return {
|
|
40
|
+
contents: [
|
|
41
|
+
{ uri: "mssql://schemas", text: notConnectedText(), mimeType: "application/json" },
|
|
42
|
+
],
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
const result = await pool
|
|
46
|
+
.request()
|
|
47
|
+
.query(`SELECT SCHEMA_NAME, SCHEMA_OWNER FROM INFORMATION_SCHEMA.SCHEMATA ORDER BY SCHEMA_NAME`);
|
|
48
|
+
return {
|
|
49
|
+
contents: [
|
|
50
|
+
{
|
|
51
|
+
uri: "mssql://schemas",
|
|
52
|
+
text: JSON.stringify(result.recordset, null, 2),
|
|
53
|
+
mimeType: "application/json",
|
|
54
|
+
},
|
|
55
|
+
],
|
|
56
|
+
};
|
|
57
|
+
});
|
|
58
|
+
}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export declare const PaginationSchema: z.ZodObject<{
|
|
3
|
+
count: z.ZodNumber;
|
|
4
|
+
limit: z.ZodNumber;
|
|
5
|
+
offset: z.ZodNumber;
|
|
6
|
+
has_more: z.ZodBoolean;
|
|
7
|
+
next_offset: z.ZodNullable<z.ZodNumber>;
|
|
8
|
+
total_count: z.ZodOptional<z.ZodNumber>;
|
|
9
|
+
}, "strip", z.ZodTypeAny, {
|
|
10
|
+
count: number;
|
|
11
|
+
limit: number;
|
|
12
|
+
offset: number;
|
|
13
|
+
has_more: boolean;
|
|
14
|
+
next_offset: number | null;
|
|
15
|
+
total_count?: number | undefined;
|
|
16
|
+
}, {
|
|
17
|
+
count: number;
|
|
18
|
+
limit: number;
|
|
19
|
+
offset: number;
|
|
20
|
+
has_more: boolean;
|
|
21
|
+
next_offset: number | null;
|
|
22
|
+
total_count?: number | undefined;
|
|
23
|
+
}>;
|
|
24
|
+
export declare const ConnectionStateSchema: z.ZodObject<{
|
|
25
|
+
connected: z.ZodBoolean;
|
|
26
|
+
config: z.ZodNullable<z.ZodObject<{
|
|
27
|
+
server: z.ZodString;
|
|
28
|
+
database: z.ZodOptional<z.ZodString>;
|
|
29
|
+
port: z.ZodNumber;
|
|
30
|
+
}, "strip", z.ZodTypeAny, {
|
|
31
|
+
server: string;
|
|
32
|
+
port: number;
|
|
33
|
+
database?: string | undefined;
|
|
34
|
+
}, {
|
|
35
|
+
server: string;
|
|
36
|
+
port: number;
|
|
37
|
+
database?: string | undefined;
|
|
38
|
+
}>>;
|
|
39
|
+
pool_info: z.ZodNullable<z.ZodObject<{
|
|
40
|
+
size: z.ZodNumber;
|
|
41
|
+
available: z.ZodNumber;
|
|
42
|
+
pending: z.ZodNumber;
|
|
43
|
+
borrowed: z.ZodNumber;
|
|
44
|
+
}, "strip", z.ZodTypeAny, {
|
|
45
|
+
size: number;
|
|
46
|
+
available: number;
|
|
47
|
+
pending: number;
|
|
48
|
+
borrowed: number;
|
|
49
|
+
}, {
|
|
50
|
+
size: number;
|
|
51
|
+
available: number;
|
|
52
|
+
pending: number;
|
|
53
|
+
borrowed: number;
|
|
54
|
+
}>>;
|
|
55
|
+
}, "strip", z.ZodTypeAny, {
|
|
56
|
+
connected: boolean;
|
|
57
|
+
config: {
|
|
58
|
+
server: string;
|
|
59
|
+
port: number;
|
|
60
|
+
database?: string | undefined;
|
|
61
|
+
} | null;
|
|
62
|
+
pool_info: {
|
|
63
|
+
size: number;
|
|
64
|
+
available: number;
|
|
65
|
+
pending: number;
|
|
66
|
+
borrowed: number;
|
|
67
|
+
} | null;
|
|
68
|
+
}, {
|
|
69
|
+
connected: boolean;
|
|
70
|
+
config: {
|
|
71
|
+
server: string;
|
|
72
|
+
port: number;
|
|
73
|
+
database?: string | undefined;
|
|
74
|
+
} | null;
|
|
75
|
+
pool_info: {
|
|
76
|
+
size: number;
|
|
77
|
+
available: number;
|
|
78
|
+
pending: number;
|
|
79
|
+
borrowed: number;
|
|
80
|
+
} | null;
|
|
81
|
+
}>;
|
|
82
|
+
export declare const ColumnInfoSchema: z.ZodObject<{
|
|
83
|
+
COLUMN_NAME: z.ZodString;
|
|
84
|
+
DATA_TYPE: z.ZodString;
|
|
85
|
+
CHARACTER_MAXIMUM_LENGTH: z.ZodNullable<z.ZodNumber>;
|
|
86
|
+
NUMERIC_PRECISION: z.ZodNullable<z.ZodNumber>;
|
|
87
|
+
NUMERIC_SCALE: z.ZodNullable<z.ZodNumber>;
|
|
88
|
+
IS_NULLABLE: z.ZodString;
|
|
89
|
+
COLUMN_DEFAULT: z.ZodNullable<z.ZodString>;
|
|
90
|
+
ORDINAL_POSITION: z.ZodNumber;
|
|
91
|
+
}, "strip", z.ZodTypeAny, {
|
|
92
|
+
COLUMN_NAME: string;
|
|
93
|
+
DATA_TYPE: string;
|
|
94
|
+
CHARACTER_MAXIMUM_LENGTH: number | null;
|
|
95
|
+
NUMERIC_PRECISION: number | null;
|
|
96
|
+
NUMERIC_SCALE: number | null;
|
|
97
|
+
IS_NULLABLE: string;
|
|
98
|
+
COLUMN_DEFAULT: string | null;
|
|
99
|
+
ORDINAL_POSITION: number;
|
|
100
|
+
}, {
|
|
101
|
+
COLUMN_NAME: string;
|
|
102
|
+
DATA_TYPE: string;
|
|
103
|
+
CHARACTER_MAXIMUM_LENGTH: number | null;
|
|
104
|
+
NUMERIC_PRECISION: number | null;
|
|
105
|
+
NUMERIC_SCALE: number | null;
|
|
106
|
+
IS_NULLABLE: string;
|
|
107
|
+
COLUMN_DEFAULT: string | null;
|
|
108
|
+
ORDINAL_POSITION: number;
|
|
109
|
+
}>;
|
|
110
|
+
export declare const SchemaObjectSchema: z.ZodObject<{
|
|
111
|
+
TABLE_SCHEMA: z.ZodString;
|
|
112
|
+
TABLE_NAME: z.ZodString;
|
|
113
|
+
TABLE_TYPE: z.ZodString;
|
|
114
|
+
OBJECT_TYPE: z.ZodString;
|
|
115
|
+
}, "strip", z.ZodTypeAny, {
|
|
116
|
+
TABLE_SCHEMA: string;
|
|
117
|
+
TABLE_NAME: string;
|
|
118
|
+
TABLE_TYPE: string;
|
|
119
|
+
OBJECT_TYPE: string;
|
|
120
|
+
}, {
|
|
121
|
+
TABLE_SCHEMA: string;
|
|
122
|
+
TABLE_NAME: string;
|
|
123
|
+
TABLE_TYPE: string;
|
|
124
|
+
OBJECT_TYPE: string;
|
|
125
|
+
}>;
|
|
126
|
+
export declare const DatabaseInfoSchema: z.ZodObject<{
|
|
127
|
+
name: z.ZodString;
|
|
128
|
+
database_id: z.ZodNumber;
|
|
129
|
+
create_date: z.ZodUnknown;
|
|
130
|
+
collation_name: z.ZodNullable<z.ZodString>;
|
|
131
|
+
state_desc: z.ZodString;
|
|
132
|
+
user_access_desc: z.ZodString;
|
|
133
|
+
is_read_only: z.ZodBoolean;
|
|
134
|
+
recovery_model_desc: z.ZodString;
|
|
135
|
+
}, "strip", z.ZodTypeAny, {
|
|
136
|
+
name: string;
|
|
137
|
+
database_id: number;
|
|
138
|
+
collation_name: string | null;
|
|
139
|
+
state_desc: string;
|
|
140
|
+
user_access_desc: string;
|
|
141
|
+
is_read_only: boolean;
|
|
142
|
+
recovery_model_desc: string;
|
|
143
|
+
create_date?: unknown;
|
|
144
|
+
}, {
|
|
145
|
+
name: string;
|
|
146
|
+
database_id: number;
|
|
147
|
+
collation_name: string | null;
|
|
148
|
+
state_desc: string;
|
|
149
|
+
user_access_desc: string;
|
|
150
|
+
is_read_only: boolean;
|
|
151
|
+
recovery_model_desc: string;
|
|
152
|
+
create_date?: unknown;
|
|
153
|
+
}>;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export const PaginationSchema = z.object({
|
|
3
|
+
count: z.number(),
|
|
4
|
+
limit: z.number(),
|
|
5
|
+
offset: z.number(),
|
|
6
|
+
has_more: z.boolean(),
|
|
7
|
+
next_offset: z.number().nullable(),
|
|
8
|
+
total_count: z.number().optional(),
|
|
9
|
+
});
|
|
10
|
+
export const ConnectionStateSchema = z.object({
|
|
11
|
+
connected: z.boolean(),
|
|
12
|
+
config: z
|
|
13
|
+
.object({
|
|
14
|
+
server: z.string(),
|
|
15
|
+
database: z.string().optional(),
|
|
16
|
+
port: z.number(),
|
|
17
|
+
})
|
|
18
|
+
.nullable(),
|
|
19
|
+
pool_info: z
|
|
20
|
+
.object({
|
|
21
|
+
size: z.number(),
|
|
22
|
+
available: z.number(),
|
|
23
|
+
pending: z.number(),
|
|
24
|
+
borrowed: z.number(),
|
|
25
|
+
})
|
|
26
|
+
.nullable(),
|
|
27
|
+
});
|
|
28
|
+
export const ColumnInfoSchema = z.object({
|
|
29
|
+
COLUMN_NAME: z.string(),
|
|
30
|
+
DATA_TYPE: z.string(),
|
|
31
|
+
CHARACTER_MAXIMUM_LENGTH: z.number().nullable(),
|
|
32
|
+
NUMERIC_PRECISION: z.number().nullable(),
|
|
33
|
+
NUMERIC_SCALE: z.number().nullable(),
|
|
34
|
+
IS_NULLABLE: z.string(),
|
|
35
|
+
COLUMN_DEFAULT: z.string().nullable(),
|
|
36
|
+
ORDINAL_POSITION: z.number(),
|
|
37
|
+
});
|
|
38
|
+
export const SchemaObjectSchema = z.object({
|
|
39
|
+
TABLE_SCHEMA: z.string(),
|
|
40
|
+
TABLE_NAME: z.string(),
|
|
41
|
+
TABLE_TYPE: z.string(),
|
|
42
|
+
OBJECT_TYPE: z.string(),
|
|
43
|
+
});
|
|
44
|
+
// create_date is a JS Date from mssql; z.unknown() avoids type mismatch after JSON serialization.
|
|
45
|
+
export const DatabaseInfoSchema = z.object({
|
|
46
|
+
name: z.string(),
|
|
47
|
+
database_id: z.number(),
|
|
48
|
+
create_date: z.unknown(),
|
|
49
|
+
collation_name: z.string().nullable(),
|
|
50
|
+
state_desc: z.string(),
|
|
51
|
+
user_access_desc: z.string(),
|
|
52
|
+
is_read_only: z.boolean(),
|
|
53
|
+
recovery_model_desc: z.string(),
|
|
54
|
+
});
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { SERVER_NAME, SERVER_VERSION } from "./constants.js";
|
|
3
|
+
import { registerConnectTools } from "./tools/connect.js";
|
|
4
|
+
import { registerStatusTool } from "./tools/status.js";
|
|
5
|
+
import { registerQueryTools } from "./tools/query.js";
|
|
6
|
+
import { registerSchemaTools } from "./tools/schema.js";
|
|
7
|
+
import { registerTableTools } from "./tools/table.js";
|
|
8
|
+
import { registerProcedureTools } from "./tools/procedure.js";
|
|
9
|
+
import { registerDatabasesTools } from "./tools/databases.js";
|
|
10
|
+
import { registerConnectionResource } from "./resources/connection.js";
|
|
11
|
+
import { registerMetadataResources } from "./resources/metadata.js";
|
|
12
|
+
export function createServer() {
|
|
13
|
+
const server = new McpServer({
|
|
14
|
+
name: SERVER_NAME,
|
|
15
|
+
version: SERVER_VERSION,
|
|
16
|
+
});
|
|
17
|
+
registerConnectTools(server);
|
|
18
|
+
registerStatusTool(server);
|
|
19
|
+
registerQueryTools(server);
|
|
20
|
+
registerSchemaTools(server);
|
|
21
|
+
registerTableTools(server);
|
|
22
|
+
registerProcedureTools(server);
|
|
23
|
+
registerDatabasesTools(server);
|
|
24
|
+
registerConnectionResource(server);
|
|
25
|
+
registerMetadataResources(server);
|
|
26
|
+
return server;
|
|
27
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { loadConfigFromEnv } from "../config.js";
|
|
3
|
+
import { connectPool, disconnectPool, getConnectionState } from "../db/connection.js";
|
|
4
|
+
import { toActionableError, toolError, toolSuccess } from "../utils/errors.js";
|
|
5
|
+
import { ConnectionStateSchema } from "../schemas/outputs.js";
|
|
6
|
+
export function registerConnectTools(server) {
|
|
7
|
+
server.registerTool("mssql_connect_database", {
|
|
8
|
+
title: "Connect Database",
|
|
9
|
+
description: "Connects to MS SQL Server using environment variables (DB_SERVER, DB_DATABASE, DB_USER, DB_PASSWORD, " +
|
|
10
|
+
"DB_PORT, DB_ENCRYPT, DB_TRUST_SERVER_CERTIFICATE). No credentials accepted as parameters — all " +
|
|
11
|
+
"connection settings come from the server environment only. Idempotent: calling again reconnects.",
|
|
12
|
+
inputSchema: {},
|
|
13
|
+
outputSchema: ConnectionStateSchema,
|
|
14
|
+
annotations: { readOnlyHint: false, idempotentHint: true, openWorldHint: false },
|
|
15
|
+
}, async () => {
|
|
16
|
+
try {
|
|
17
|
+
const config = loadConfigFromEnv();
|
|
18
|
+
await connectPool(config);
|
|
19
|
+
const state = getConnectionState();
|
|
20
|
+
return toolSuccess(`✅ Connected to ${config.server}${config.database ? ` (database: ${config.database})` : ""}`, state);
|
|
21
|
+
}
|
|
22
|
+
catch (err) {
|
|
23
|
+
const msg = toActionableError(err);
|
|
24
|
+
console.error("mssql_connect_database failed:", msg);
|
|
25
|
+
return toolError(`Connection failed: ${msg}`);
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
server.registerTool("mssql_disconnect_database", {
|
|
29
|
+
title: "Disconnect Database",
|
|
30
|
+
description: "Closes the current database connection and releases the connection pool. " +
|
|
31
|
+
"Safe to call even if not connected. Idempotent.",
|
|
32
|
+
inputSchema: {},
|
|
33
|
+
outputSchema: { message: z.string() },
|
|
34
|
+
annotations: { readOnlyHint: false, idempotentHint: true, openWorldHint: false },
|
|
35
|
+
}, async () => {
|
|
36
|
+
try {
|
|
37
|
+
await disconnectPool();
|
|
38
|
+
const msg = "✅ Disconnected from database.";
|
|
39
|
+
return toolSuccess(msg, { message: msg });
|
|
40
|
+
}
|
|
41
|
+
catch (err) {
|
|
42
|
+
return toolError(`Disconnect failed: ${toActionableError(err)}`);
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { requirePool } from "../db/connection.js";
|
|
3
|
+
import { toActionableError, toolError, toolSuccess } from "../utils/errors.js";
|
|
4
|
+
import { formatJson } from "../utils/format.js";
|
|
5
|
+
import { formatMarkdownTable } from "../utils/markdown.js";
|
|
6
|
+
import { buildPaginationMeta, clampLimit } from "../utils/pagination.js";
|
|
7
|
+
import { PaginationSchema, DatabaseInfoSchema } from "../schemas/outputs.js";
|
|
8
|
+
export function registerDatabasesTools(server) {
|
|
9
|
+
server.registerTool("mssql_list_databases", {
|
|
10
|
+
title: "List Databases",
|
|
11
|
+
description: "Lists all databases visible on the connected SQL Server instance with state and recovery information. " +
|
|
12
|
+
"Read-only. Supports pagination.",
|
|
13
|
+
inputSchema: {
|
|
14
|
+
limit: z.number().int().min(1).max(200).optional().default(20).describe("Max databases (default 20)"),
|
|
15
|
+
offset: z.number().int().min(0).optional().default(0).describe("Skip N databases"),
|
|
16
|
+
response_format: z
|
|
17
|
+
.enum(["json", "markdown"])
|
|
18
|
+
.optional()
|
|
19
|
+
.default("json")
|
|
20
|
+
.describe("Output format: 'json' for structured data, 'markdown' for human-readable table"),
|
|
21
|
+
},
|
|
22
|
+
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
|
|
23
|
+
outputSchema: { databases: z.array(DatabaseInfoSchema), pagination: PaginationSchema },
|
|
24
|
+
}, async ({ limit: rawLimit, offset, response_format }) => {
|
|
25
|
+
try {
|
|
26
|
+
const pool = requirePool();
|
|
27
|
+
const limit = clampLimit(rawLimit);
|
|
28
|
+
const result = await pool.request().query(`
|
|
29
|
+
SELECT name, database_id, create_date, collation_name,
|
|
30
|
+
state_desc, user_access_desc, is_read_only,
|
|
31
|
+
recovery_model_desc
|
|
32
|
+
FROM sys.databases
|
|
33
|
+
ORDER BY name
|
|
34
|
+
`);
|
|
35
|
+
const allRows = result.recordset ?? [];
|
|
36
|
+
const page = allRows.slice(offset, offset + limit);
|
|
37
|
+
const pagination = buildPaginationMeta(page.length, limit, offset, allRows.length);
|
|
38
|
+
const structured = { databases: page, pagination };
|
|
39
|
+
if (response_format === "markdown") {
|
|
40
|
+
const rows = page;
|
|
41
|
+
let text = formatMarkdownTable(rows, "Databases");
|
|
42
|
+
text += `\n\n*Showing ${page.length} of ${allRows.length} databases*`;
|
|
43
|
+
return toolSuccess(text, structured);
|
|
44
|
+
}
|
|
45
|
+
return toolSuccess(formatJson(structured), structured);
|
|
46
|
+
}
|
|
47
|
+
catch (err) {
|
|
48
|
+
const msg = toActionableError(err);
|
|
49
|
+
console.error("mssql_list_databases failed:", msg);
|
|
50
|
+
return toolError(`List databases failed: ${msg}`);
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { requirePool } from "../db/connection.js";
|
|
3
|
+
import { validateIdentifier, bracketIdentifier } from "../db/validators.js";
|
|
4
|
+
import { toActionableError, toolError, toolSuccess } from "../utils/errors.js";
|
|
5
|
+
import { formatJson } from "../utils/format.js";
|
|
6
|
+
import { formatMarkdownMultiRecordsets } from "../utils/markdown.js";
|
|
7
|
+
const ProcedureOutputSchema = {
|
|
8
|
+
recordsets: z.array(z.array(z.record(z.unknown()))),
|
|
9
|
+
rows_affected: z.array(z.number()),
|
|
10
|
+
output: z.record(z.unknown()),
|
|
11
|
+
return_value: z.unknown(),
|
|
12
|
+
};
|
|
13
|
+
export function registerProcedureTools(server) {
|
|
14
|
+
server.registerTool("mssql_execute_stored_procedure", {
|
|
15
|
+
title: "Execute Stored Procedure",
|
|
16
|
+
description: "Executes a stored procedure by name. " +
|
|
17
|
+
"⚠️ May modify data — use only when an action is intended. " +
|
|
18
|
+
"Schema and procedure names are validated as safe identifiers. " +
|
|
19
|
+
"Pass all value parameters via 'parameters'.",
|
|
20
|
+
inputSchema: {
|
|
21
|
+
procedureName: z.string().min(1).describe("Stored procedure name"),
|
|
22
|
+
schemaName: z.string().optional().default("dbo").describe("Schema name (default: dbo)"),
|
|
23
|
+
parameters: z
|
|
24
|
+
.record(z.union([z.string(), z.number(), z.boolean(), z.null()]))
|
|
25
|
+
.optional()
|
|
26
|
+
.describe("Procedure input parameters (key-value pairs)"),
|
|
27
|
+
response_format: z
|
|
28
|
+
.enum(["json", "markdown"])
|
|
29
|
+
.optional()
|
|
30
|
+
.default("json")
|
|
31
|
+
.describe("Output format: 'json' for structured data, 'markdown' for human-readable tables"),
|
|
32
|
+
},
|
|
33
|
+
annotations: { readOnlyHint: false, idempotentHint: false, openWorldHint: true },
|
|
34
|
+
outputSchema: ProcedureOutputSchema,
|
|
35
|
+
}, async ({ procedureName, schemaName, parameters, response_format }) => {
|
|
36
|
+
try {
|
|
37
|
+
const pool = requirePool();
|
|
38
|
+
validateIdentifier(procedureName, "procedure name");
|
|
39
|
+
validateIdentifier(schemaName, "schema name");
|
|
40
|
+
const qualifiedName = `${bracketIdentifier(schemaName)}.${bracketIdentifier(procedureName)}`;
|
|
41
|
+
const request = pool.request();
|
|
42
|
+
if (parameters) {
|
|
43
|
+
for (const [key, value] of Object.entries(parameters)) {
|
|
44
|
+
request.input(key, value);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
const result = await request.execute(qualifiedName);
|
|
48
|
+
const structured = {
|
|
49
|
+
recordsets: result.recordsets,
|
|
50
|
+
rows_affected: result.rowsAffected,
|
|
51
|
+
output: result.output,
|
|
52
|
+
return_value: result.returnValue,
|
|
53
|
+
};
|
|
54
|
+
if (response_format === "markdown") {
|
|
55
|
+
return toolSuccess(formatMarkdownMultiRecordsets(result.recordsets, qualifiedName), structured);
|
|
56
|
+
}
|
|
57
|
+
return toolSuccess(formatJson(structured), structured);
|
|
58
|
+
}
|
|
59
|
+
catch (err) {
|
|
60
|
+
const msg = toActionableError(err);
|
|
61
|
+
console.error("mssql_execute_stored_procedure failed:", msg);
|
|
62
|
+
return toolError(`Procedure execution failed: ${msg}`);
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
// Backward-compatible alias
|
|
66
|
+
server.registerTool("mssql_execute_procedure", {
|
|
67
|
+
title: "Execute Procedure (deprecated — use mssql_execute_stored_procedure)",
|
|
68
|
+
description: "Deprecated alias for mssql_execute_stored_procedure.",
|
|
69
|
+
inputSchema: {
|
|
70
|
+
procedureName: z.string().describe("Name of the stored procedure"),
|
|
71
|
+
schemaName: z.string().optional().default("dbo"),
|
|
72
|
+
parameters: z
|
|
73
|
+
.record(z.union([z.string(), z.number(), z.boolean(), z.null()]))
|
|
74
|
+
.optional(),
|
|
75
|
+
response_format: z.enum(["json", "markdown"]).optional().default("json"),
|
|
76
|
+
},
|
|
77
|
+
annotations: { readOnlyHint: false, idempotentHint: false, openWorldHint: true },
|
|
78
|
+
}, async ({ procedureName, schemaName, parameters, response_format }) => {
|
|
79
|
+
try {
|
|
80
|
+
const pool = requirePool();
|
|
81
|
+
validateIdentifier(procedureName, "procedure name");
|
|
82
|
+
validateIdentifier(schemaName, "schema name");
|
|
83
|
+
const qualifiedName = `${bracketIdentifier(schemaName)}.${bracketIdentifier(procedureName)}`;
|
|
84
|
+
const request = pool.request();
|
|
85
|
+
if (parameters) {
|
|
86
|
+
for (const [key, value] of Object.entries(parameters)) {
|
|
87
|
+
request.input(key, value);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
const result = await request.execute(qualifiedName);
|
|
91
|
+
const structured = {
|
|
92
|
+
recordsets: result.recordsets,
|
|
93
|
+
rowsAffected: result.rowsAffected,
|
|
94
|
+
output: result.output,
|
|
95
|
+
returnValue: result.returnValue,
|
|
96
|
+
};
|
|
97
|
+
if (response_format === "markdown") {
|
|
98
|
+
return toolSuccess(formatMarkdownMultiRecordsets(result.recordsets));
|
|
99
|
+
}
|
|
100
|
+
return toolSuccess(formatJson(structured), structured);
|
|
101
|
+
}
|
|
102
|
+
catch (err) {
|
|
103
|
+
return toolError(`Procedure execution failed: ${toActionableError(err)}`);
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { requirePool } from "../db/connection.js";
|
|
3
|
+
import { toActionableError, toolError, toolSuccess } from "../utils/errors.js";
|
|
4
|
+
import { formatJson, truncatePayload } from "../utils/format.js";
|
|
5
|
+
import { formatMarkdownTable } from "../utils/markdown.js";
|
|
6
|
+
const QueryOutputSchema = {
|
|
7
|
+
recordset: z.array(z.record(z.unknown())),
|
|
8
|
+
rows_affected: z.array(z.number()),
|
|
9
|
+
output: z.record(z.unknown()),
|
|
10
|
+
execution_time_ms: z.number(),
|
|
11
|
+
parameters_used: z.number(),
|
|
12
|
+
truncated: z.boolean(),
|
|
13
|
+
truncation_message: z.string().optional(),
|
|
14
|
+
};
|
|
15
|
+
async function runSqlQueryCore(query, parameters, response_format, logLabel) {
|
|
16
|
+
try {
|
|
17
|
+
const pool = requirePool();
|
|
18
|
+
const request = pool.request();
|
|
19
|
+
if (parameters) {
|
|
20
|
+
for (const [key, value] of Object.entries(parameters)) {
|
|
21
|
+
request.input(key, value);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
const start = Date.now();
|
|
25
|
+
const result = await request.query(query);
|
|
26
|
+
const elapsed = Date.now() - start;
|
|
27
|
+
const { data, truncated, truncation_message } = truncatePayload(result.recordset ?? []);
|
|
28
|
+
const structured = {
|
|
29
|
+
recordset: data,
|
|
30
|
+
rows_affected: result.rowsAffected,
|
|
31
|
+
output: result.output,
|
|
32
|
+
execution_time_ms: elapsed,
|
|
33
|
+
parameters_used: parameters ? Object.keys(parameters).length : 0,
|
|
34
|
+
truncated,
|
|
35
|
+
};
|
|
36
|
+
if (truncation_message)
|
|
37
|
+
structured.truncation_message = truncation_message;
|
|
38
|
+
if (response_format === "markdown") {
|
|
39
|
+
const rows = data;
|
|
40
|
+
let text = formatMarkdownTable(rows);
|
|
41
|
+
if (truncated)
|
|
42
|
+
text += `\n\n> ⚠️ ${truncation_message}`;
|
|
43
|
+
text += `\n\n*Rows affected: ${(result.rowsAffected ?? []).join(", ")} · ${elapsed}ms*`;
|
|
44
|
+
return toolSuccess(text, structured);
|
|
45
|
+
}
|
|
46
|
+
return toolSuccess(formatJson(structured), structured);
|
|
47
|
+
}
|
|
48
|
+
catch (err) {
|
|
49
|
+
const msg = toActionableError(err);
|
|
50
|
+
console.error(`${logLabel} failed:`, msg);
|
|
51
|
+
return toolError(`Query failed: ${msg}`);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
export function registerQueryTools(server) {
|
|
55
|
+
server.registerTool("mssql_run_sql_query", {
|
|
56
|
+
title: "Run SQL Query",
|
|
57
|
+
description: "Executes an arbitrary SQL statement against the connected database. " +
|
|
58
|
+
"⚠️ WARNING: This tool can read AND modify data (INSERT, UPDATE, DELETE, DDL). " +
|
|
59
|
+
"Always prefer parameterized inputs via the 'parameters' field — never embed user-supplied values " +
|
|
60
|
+
"directly in the query string. " +
|
|
61
|
+
"Example: query='SELECT * FROM dbo.Users WHERE Id = @id', parameters={id: 42}",
|
|
62
|
+
inputSchema: {
|
|
63
|
+
query: z.string().min(1).describe("SQL statement to execute"),
|
|
64
|
+
parameters: z
|
|
65
|
+
.record(z.union([z.string(), z.number(), z.boolean(), z.null()]))
|
|
66
|
+
.optional()
|
|
67
|
+
.describe("Named parameters referenced in the query via @paramName"),
|
|
68
|
+
response_format: z
|
|
69
|
+
.enum(["json", "markdown"])
|
|
70
|
+
.optional()
|
|
71
|
+
.default("json")
|
|
72
|
+
.describe("Output format: 'json' for structured data, 'markdown' for human-readable table"),
|
|
73
|
+
},
|
|
74
|
+
annotations: { readOnlyHint: false, idempotentHint: false, openWorldHint: true },
|
|
75
|
+
outputSchema: QueryOutputSchema,
|
|
76
|
+
}, ({ query, parameters, response_format }) => runSqlQueryCore(query, parameters, response_format, "mssql_run_sql_query"));
|
|
77
|
+
// Backward-compatible alias
|
|
78
|
+
server.registerTool("mssql_execute_query", {
|
|
79
|
+
title: "Execute Query (deprecated — use mssql_run_sql_query)",
|
|
80
|
+
description: "Deprecated alias for mssql_run_sql_query. Use mssql_run_sql_query instead. " +
|
|
81
|
+
"⚠️ This tool can read AND modify data.",
|
|
82
|
+
inputSchema: {
|
|
83
|
+
query: z.string().min(1).describe("SQL query to execute"),
|
|
84
|
+
parameters: z
|
|
85
|
+
.record(z.union([z.string(), z.number(), z.boolean(), z.null()]))
|
|
86
|
+
.optional()
|
|
87
|
+
.describe("Query parameters"),
|
|
88
|
+
response_format: z.enum(["json", "markdown"]).optional().default("json"),
|
|
89
|
+
},
|
|
90
|
+
annotations: { readOnlyHint: false, idempotentHint: false, openWorldHint: true },
|
|
91
|
+
}, ({ query, parameters, response_format }) => runSqlQueryCore(query, parameters, response_format, "mssql_execute_query"));
|
|
92
|
+
}
|