mssql-mcp 2.3.1 → 2.3.2

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 CHANGED
@@ -1,4 +1,4 @@
1
- # MS SQL Server MCP Server v2.3.1
1
+ # MS SQL Server MCP Server v2.3.2
2
2
 
3
3
  🚀 **Model Context Protocol (MCP) server** for Microsoft SQL Server - compatible with Claude Desktop, Cursor, Windsurf and VS Code.
4
4
 
@@ -1,5 +1,5 @@
1
1
  export declare const SERVER_NAME = "mssql-mcp-server";
2
- export declare const SERVER_VERSION = "2.3.1";
2
+ export declare const SERVER_VERSION = "2.3.2";
3
3
  export declare const DEFAULT_PORT = 1433;
4
4
  export declare const DEFAULT_ENCRYPT = true;
5
5
  export declare const DEFAULT_TRUST_SERVER_CERTIFICATE = false;
@@ -1,5 +1,5 @@
1
1
  export const SERVER_NAME = "mssql-mcp-server";
2
- export const SERVER_VERSION = "2.3.1";
2
+ export const SERVER_VERSION = "2.3.2";
3
3
  export const DEFAULT_PORT = 1433;
4
4
  export const DEFAULT_ENCRYPT = true;
5
5
  export const DEFAULT_TRUST_SERVER_CERTIFICATE = false;
@@ -19,6 +19,7 @@ export async function connectPool(config) {
19
19
  encrypt: config.encrypt,
20
20
  trustServerCertificate: config.trustServerCertificate,
21
21
  enableArithAbort: true,
22
+ useUTC: false,
22
23
  },
23
24
  connectionTimeout: config.connectionTimeout,
24
25
  requestTimeout: config.requestTimeout,
@@ -1,10 +1,9 @@
1
1
  import { z } from "zod";
2
2
  import { requirePool } from "../db/connection.js";
3
- import { toActionableError, toolError, toolSuccess } from "../utils/errors.js";
3
+ import { toActionableError, toolError, toolSuccess, toolSuccessMarkdown } from "../utils/errors.js";
4
4
  import { formatJson } from "../utils/format.js";
5
5
  import { formatMarkdownTable } from "../utils/markdown.js";
6
6
  import { buildPaginationMeta, clampLimit } from "../utils/pagination.js";
7
- import { PaginationSchema, DatabaseInfoSchema } from "../schemas/outputs.js";
8
7
  export function registerDatabasesTools(server) {
9
8
  server.registerTool("mssql_list_databases", {
10
9
  title: "List Databases",
@@ -20,7 +19,6 @@ export function registerDatabasesTools(server) {
20
19
  .describe("Output format: 'json' for structured data, 'markdown' for human-readable table"),
21
20
  },
22
21
  annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
23
- outputSchema: { databases: z.array(DatabaseInfoSchema), pagination: PaginationSchema },
24
22
  }, async ({ limit: rawLimit, offset, response_format }) => {
25
23
  try {
26
24
  const pool = requirePool();
@@ -40,7 +38,7 @@ export function registerDatabasesTools(server) {
40
38
  const rows = page;
41
39
  let text = formatMarkdownTable(rows, "Databases");
42
40
  text += `\n\n*Showing ${page.length} of ${allRows.length} databases*`;
43
- return toolSuccess(text, structured);
41
+ return toolSuccessMarkdown(text);
44
42
  }
45
43
  return toolSuccess(formatJson(structured), structured);
46
44
  }
@@ -1,15 +1,9 @@
1
1
  import { z } from "zod";
2
2
  import { requirePool } from "../db/connection.js";
3
3
  import { validateIdentifier, bracketIdentifier } from "../db/validators.js";
4
- import { toActionableError, toolError, toolSuccess } from "../utils/errors.js";
4
+ import { toActionableError, toolError, toolSuccess, toolSuccessMarkdown } from "../utils/errors.js";
5
5
  import { formatJson } from "../utils/format.js";
6
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
7
  export function registerProcedureTools(server) {
14
8
  server.registerTool("mssql_execute_stored_procedure", {
15
9
  title: "Execute Stored Procedure",
@@ -31,7 +25,6 @@ export function registerProcedureTools(server) {
31
25
  .describe("Output format: 'json' for structured data, 'markdown' for human-readable tables"),
32
26
  },
33
27
  annotations: { readOnlyHint: false, idempotentHint: false, openWorldHint: true },
34
- outputSchema: ProcedureOutputSchema,
35
28
  }, async ({ procedureName, schemaName, parameters, response_format }) => {
36
29
  try {
37
30
  const pool = requirePool();
@@ -52,7 +45,7 @@ export function registerProcedureTools(server) {
52
45
  return_value: result.returnValue,
53
46
  };
54
47
  if (response_format === "markdown") {
55
- return toolSuccess(formatMarkdownMultiRecordsets(result.recordsets, qualifiedName), structured);
48
+ return toolSuccessMarkdown(formatMarkdownMultiRecordsets(result.recordsets, qualifiedName));
56
49
  }
57
50
  return toolSuccess(formatJson(structured), structured);
58
51
  }
@@ -95,7 +88,7 @@ export function registerProcedureTools(server) {
95
88
  returnValue: result.returnValue,
96
89
  };
97
90
  if (response_format === "markdown") {
98
- return toolSuccess(formatMarkdownMultiRecordsets(result.recordsets));
91
+ return toolSuccessMarkdown(formatMarkdownMultiRecordsets(result.recordsets));
99
92
  }
100
93
  return toolSuccess(formatJson(structured), structured);
101
94
  }
@@ -1,17 +1,8 @@
1
1
  import { z } from "zod";
2
2
  import { requirePool } from "../db/connection.js";
3
- import { toActionableError, toolError, toolSuccess } from "../utils/errors.js";
3
+ import { toActionableError, toolError, toolSuccess, toolSuccessMarkdown } from "../utils/errors.js";
4
4
  import { formatJson, truncatePayload } from "../utils/format.js";
5
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
6
  async function runSqlQueryCore(query, parameters, response_format, logLabel) {
16
7
  try {
17
8
  const pool = requirePool();
@@ -41,7 +32,7 @@ async function runSqlQueryCore(query, parameters, response_format, logLabel) {
41
32
  if (truncated)
42
33
  text += `\n\n> ⚠️ ${truncation_message}`;
43
34
  text += `\n\n*Rows affected: ${(result.rowsAffected ?? []).join(", ")} · ${elapsed}ms*`;
44
- return toolSuccess(text, structured);
35
+ return toolSuccessMarkdown(text);
45
36
  }
46
37
  return toolSuccess(formatJson(structured), structured);
47
38
  }
@@ -72,7 +63,6 @@ export function registerQueryTools(server) {
72
63
  .describe("Output format: 'json' for structured data, 'markdown' for human-readable table"),
73
64
  },
74
65
  annotations: { readOnlyHint: false, idempotentHint: false, openWorldHint: true },
75
- outputSchema: QueryOutputSchema,
76
66
  }, ({ query, parameters, response_format }) => runSqlQueryCore(query, parameters, response_format, "mssql_run_sql_query"));
77
67
  // Backward-compatible alias
78
68
  server.registerTool("mssql_execute_query", {
@@ -2,11 +2,10 @@ import { z } from "zod";
2
2
  import sql from "mssql";
3
3
  import { requirePool } from "../db/connection.js";
4
4
  import { buildSchemaObjectsQuery } from "../db/query-builders.js";
5
- import { toActionableError, toolError, toolSuccess } from "../utils/errors.js";
5
+ import { toActionableError, toolError, toolSuccess, toolSuccessMarkdown } from "../utils/errors.js";
6
6
  import { formatJson } from "../utils/format.js";
7
7
  import { formatMarkdownTable } from "../utils/markdown.js";
8
8
  import { buildPaginationMeta, clampLimit } from "../utils/pagination.js";
9
- import { PaginationSchema, SchemaObjectSchema } from "../schemas/outputs.js";
10
9
  export function registerSchemaTools(server) {
11
10
  server.registerTool("mssql_list_schema_objects", {
12
11
  title: "List Schema Objects",
@@ -32,7 +31,6 @@ export function registerSchemaTools(server) {
32
31
  .describe("Output format: 'json' for structured data, 'markdown' for human-readable table"),
33
32
  },
34
33
  annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
35
- outputSchema: { objects: z.array(SchemaObjectSchema), pagination: PaginationSchema },
36
34
  }, async ({ objectType, schemaName, limit: rawLimit, offset, response_format }) => {
37
35
  try {
38
36
  const pool = requirePool();
@@ -51,7 +49,7 @@ export function registerSchemaTools(server) {
51
49
  const rows = page;
52
50
  let text = formatMarkdownTable(rows, `Schema Objects (${objectType})`);
53
51
  text += `\n\n*Showing ${page.length} of ${allRows.length} · offset ${offset}*`;
54
- return toolSuccess(text, structured);
52
+ return toolSuccessMarkdown(text);
55
53
  }
56
54
  return toolSuccess(formatJson(structured), structured);
57
55
  }
@@ -85,7 +83,7 @@ export function registerSchemaTools(server) {
85
83
  const result = await request.query(query);
86
84
  const rows = result.recordset ?? [];
87
85
  if (response_format === "markdown") {
88
- return toolSuccess(formatMarkdownTable(rows));
86
+ return toolSuccessMarkdown(formatMarkdownTable(rows));
89
87
  }
90
88
  return toolSuccess(formatJson(rows));
91
89
  }
@@ -3,11 +3,10 @@ import sql from "mssql";
3
3
  import { requirePool } from "../db/connection.js";
4
4
  import { validateIdentifier, validateOrderBy, SAFE_IDENTIFIER_RE } from "../db/validators.js";
5
5
  import { buildSelectQuery, buildSelectWithWhereQuery } from "../db/query-builders.js";
6
- import { toActionableError, toolError, toolSuccess } from "../utils/errors.js";
6
+ import { toActionableError, toolError, toolSuccess, toolSuccessMarkdown } from "../utils/errors.js";
7
7
  import { formatJson, truncatePayload } from "../utils/format.js";
8
8
  import { formatMarkdownTable } from "../utils/markdown.js";
9
9
  import { buildPaginationMeta, clampLimit } from "../utils/pagination.js";
10
- import { PaginationSchema, ColumnInfoSchema } from "../schemas/outputs.js";
11
10
  export function registerTableTools(server) {
12
11
  server.registerTool("mssql_describe_table_columns", {
13
12
  title: "Describe Table Columns",
@@ -23,7 +22,6 @@ export function registerTableTools(server) {
23
22
  .describe("Output format: 'json' for structured data, 'markdown' for human-readable table"),
24
23
  },
25
24
  annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
26
- outputSchema: { columns: z.array(ColumnInfoSchema), table: z.string() },
27
25
  }, async ({ tableName, schemaName, response_format }) => {
28
26
  try {
29
27
  const pool = requirePool();
@@ -47,7 +45,7 @@ export function registerTableTools(server) {
47
45
  };
48
46
  if (response_format === "markdown") {
49
47
  const rows = result.recordset;
50
- return toolSuccess(formatMarkdownTable(rows, `Columns: ${schemaName}.${tableName}`), structured);
48
+ return toolSuccessMarkdown(formatMarkdownTable(rows, `Columns: ${schemaName}.${tableName}`));
51
49
  }
52
50
  return toolSuccess(formatJson(structured), structured);
53
51
  }
@@ -86,7 +84,7 @@ export function registerTableTools(server) {
86
84
  `);
87
85
  const rows = result.recordset ?? [];
88
86
  if (response_format === "markdown") {
89
- return toolSuccess(formatMarkdownTable(rows));
87
+ return toolSuccessMarkdown(formatMarkdownTable(rows));
90
88
  }
91
89
  return toolSuccess(formatJson(rows));
92
90
  }
@@ -140,14 +138,6 @@ export function registerTableTools(server) {
140
138
  .describe("Output format: 'json' for structured data, 'markdown' for human-readable table"),
141
139
  },
142
140
  annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
143
- outputSchema: {
144
- data: z.array(z.record(z.unknown())),
145
- pagination: PaginationSchema,
146
- table: z.string(),
147
- execution_time_ms: z.number(),
148
- truncated: z.boolean().optional(),
149
- truncation_message: z.string().optional(),
150
- },
151
141
  }, async ({ tableName, schemaName, columns, limit: rawLimit, offset, whereClause, orderBy, parameters, response_format }) => {
152
142
  try {
153
143
  const pool = requirePool();
@@ -185,7 +175,7 @@ export function registerTableTools(server) {
185
175
  if (truncated)
186
176
  text += `\n\n> ⚠️ ${truncation_message}`;
187
177
  text += `\n\n*${rows.length} rows · offset ${offset} · ${elapsed}ms*`;
188
- return toolSuccess(text, structured);
178
+ return toolSuccessMarkdown(text);
189
179
  }
190
180
  return toolSuccess(formatJson(structured), structured);
191
181
  }
@@ -250,7 +240,7 @@ export function registerTableTools(server) {
250
240
  structured.truncation_message = truncation_message;
251
241
  }
252
242
  if (response_format === "markdown") {
253
- return toolSuccess(formatMarkdownTable(data));
243
+ return toolSuccessMarkdown(formatMarkdownTable(data));
254
244
  }
255
245
  return toolSuccess(formatJson(structured), structured);
256
246
  }
@@ -10,6 +10,12 @@ export declare function toolError(message: string): {
10
10
  }[];
11
11
  isError: true;
12
12
  };
13
+ export declare function toolSuccessMarkdown(text: string): {
14
+ content: {
15
+ type: "text";
16
+ text: string;
17
+ }[];
18
+ };
13
19
  export declare function toolSuccess(text: string, structuredContent?: Record<string, unknown>): {
14
20
  content: Array<{
15
21
  type: "text";
@@ -1,3 +1,4 @@
1
+ import { normalizeOutput } from "./format.js";
1
2
  export class McpToolError extends Error {
2
3
  cause;
3
4
  constructor(message, cause) {
@@ -18,12 +19,17 @@ export function toolError(message) {
18
19
  isError: true,
19
20
  };
20
21
  }
22
+ export function toolSuccessMarkdown(text) {
23
+ return {
24
+ content: [{ type: "text", text }],
25
+ };
26
+ }
21
27
  export function toolSuccess(text, structuredContent) {
22
28
  const result = {
23
29
  content: [{ type: "text", text }],
24
30
  };
25
31
  if (structuredContent !== undefined) {
26
- result.structuredContent = structuredContent;
32
+ result.structuredContent = normalizeOutput(structuredContent);
27
33
  }
28
34
  return result;
29
35
  }
@@ -1,4 +1,12 @@
1
1
  export declare function formatJson(data: unknown): string;
2
+ export declare function formatDisplayValue(value: unknown, column?: {
3
+ type?: unknown;
4
+ scale?: number;
5
+ }): string;
6
+ export declare function normalizeOutput(value: unknown, column?: {
7
+ type?: unknown;
8
+ scale?: number;
9
+ }): unknown;
2
10
  export declare function truncatePayload(data: unknown[], maxBytes?: number): {
3
11
  data: unknown[];
4
12
  truncated: boolean;
@@ -1,18 +1,127 @@
1
1
  import { MAX_TEXT_PAYLOAD_BYTES } from "../constants.js";
2
+ function pad(value, width) {
3
+ return String(value).padStart(width, "0");
4
+ }
5
+ function getRecordsetColumns(value) {
6
+ if (!Array.isArray(value)) {
7
+ return undefined;
8
+ }
9
+ const columns = value.columns;
10
+ if (!columns || typeof columns !== "object" || Array.isArray(columns)) {
11
+ return undefined;
12
+ }
13
+ return columns;
14
+ }
15
+ function getTypeName(column) {
16
+ const type = column?.type;
17
+ if (typeof type === "function") {
18
+ return type.name || undefined;
19
+ }
20
+ if (type && typeof type === "object" && "name" in type) {
21
+ const name = type.name;
22
+ return typeof name === "string" ? name : undefined;
23
+ }
24
+ return undefined;
25
+ }
26
+ function getExtraFraction(date) {
27
+ const raw = typeof date.nanosecondsDelta === "number"
28
+ ? date.nanosecondsDelta
29
+ : typeof date.nanosecondDelta === "number"
30
+ ? date.nanosecondDelta
31
+ : 0;
32
+ if (!Number.isFinite(raw)) {
33
+ return 0;
34
+ }
35
+ return Math.max(0, Math.min(9999, Math.round(raw * 10_000_000)));
36
+ }
37
+ function buildFraction(date, milliseconds, scale) {
38
+ const base = `${pad(milliseconds, 3)}${pad(getExtraFraction(date), 4)}`;
39
+ const safeScale = scale === undefined ? undefined : Math.max(0, Math.min(7, scale));
40
+ if (safeScale === 0) {
41
+ return "";
42
+ }
43
+ if (safeScale !== undefined) {
44
+ return base.slice(0, safeScale);
45
+ }
46
+ return base.replace(/0+$/, "");
47
+ }
48
+ function formatDateParts(date, useUtcClock) {
49
+ return {
50
+ year: useUtcClock ? date.getUTCFullYear() : date.getFullYear(),
51
+ month: (useUtcClock ? date.getUTCMonth() : date.getMonth()) + 1,
52
+ day: useUtcClock ? date.getUTCDate() : date.getDate(),
53
+ hours: useUtcClock ? date.getUTCHours() : date.getHours(),
54
+ minutes: useUtcClock ? date.getUTCMinutes() : date.getMinutes(),
55
+ seconds: useUtcClock ? date.getUTCSeconds() : date.getSeconds(),
56
+ milliseconds: useUtcClock ? date.getUTCMilliseconds() : date.getMilliseconds(),
57
+ };
58
+ }
59
+ function formatDateValue(date, column) {
60
+ const typeName = getTypeName(column)?.toLowerCase();
61
+ const useUtcClock = typeName === "datetimeoffset";
62
+ const parts = formatDateParts(date, useUtcClock);
63
+ const fraction = buildFraction(date, parts.milliseconds, column?.scale);
64
+ const dateText = `${pad(parts.year, 4)}-${pad(parts.month, 2)}-${pad(parts.day, 2)}`;
65
+ const timeText = `${pad(parts.hours, 2)}:${pad(parts.minutes, 2)}:${pad(parts.seconds, 2)}`;
66
+ if (typeName === "date") {
67
+ return dateText;
68
+ }
69
+ if (typeName === "time") {
70
+ return fraction ? `${timeText}.${fraction}` : timeText;
71
+ }
72
+ return fraction ? `${dateText} ${timeText}.${fraction}` : `${dateText} ${timeText}`;
73
+ }
74
+ function isPlainObject(value) {
75
+ return typeof value === "object"
76
+ && value !== null
77
+ && !Array.isArray(value)
78
+ && !Buffer.isBuffer(value)
79
+ && !(value instanceof Date);
80
+ }
81
+ function normalizeRecordRow(row, columns) {
82
+ return Object.fromEntries(Object.entries(row).map(([key, value]) => [key, normalizeOutput(value, columns[key])]));
83
+ }
2
84
  export function formatJson(data) {
3
- return JSON.stringify(data, null, 2);
85
+ return JSON.stringify(normalizeOutput(data), null, 2);
86
+ }
87
+ export function formatDisplayValue(value, column) {
88
+ const normalized = normalizeOutput(value, column);
89
+ if (normalized == null) {
90
+ return "";
91
+ }
92
+ if (typeof normalized === "object") {
93
+ return JSON.stringify(normalized);
94
+ }
95
+ return String(normalized);
96
+ }
97
+ export function normalizeOutput(value, column) {
98
+ if (value instanceof Date) {
99
+ return formatDateValue(value, column);
100
+ }
101
+ const columns = getRecordsetColumns(value);
102
+ if (Array.isArray(value)) {
103
+ if (columns) {
104
+ return value.map((row) => isPlainObject(row) ? normalizeRecordRow(row, columns) : normalizeOutput(row));
105
+ }
106
+ return value.map((entry) => normalizeOutput(entry));
107
+ }
108
+ if (isPlainObject(value)) {
109
+ return Object.fromEntries(Object.entries(value).map(([key, entry]) => [key, normalizeOutput(entry)]));
110
+ }
111
+ return value;
4
112
  }
5
113
  export function truncatePayload(data, maxBytes = MAX_TEXT_PAYLOAD_BYTES) {
6
- const full = JSON.stringify(data);
114
+ const normalizedData = normalizeOutput(data);
115
+ const full = JSON.stringify(normalizedData);
7
116
  if (Buffer.byteLength(full, "utf8") <= maxBytes) {
8
- return { data, truncated: false };
117
+ return { data: normalizedData, truncated: false };
9
118
  }
10
119
  // Binary search for safe row count
11
120
  let lo = 0;
12
- let hi = data.length;
121
+ let hi = normalizedData.length;
13
122
  while (lo < hi) {
14
123
  const mid = Math.floor((lo + hi + 1) / 2);
15
- if (Buffer.byteLength(JSON.stringify(data.slice(0, mid)), "utf8") <= maxBytes) {
124
+ if (Buffer.byteLength(JSON.stringify(normalizedData.slice(0, mid)), "utf8") <= maxBytes) {
16
125
  lo = mid;
17
126
  }
18
127
  else {
@@ -20,8 +129,8 @@ export function truncatePayload(data, maxBytes = MAX_TEXT_PAYLOAD_BYTES) {
20
129
  }
21
130
  }
22
131
  return {
23
- data: data.slice(0, lo),
132
+ data: normalizedData.slice(0, lo),
24
133
  truncated: true,
25
- truncation_message: `Result truncated to ${lo} of ${data.length} rows to stay within ${maxBytes} byte limit.`,
134
+ truncation_message: `Result truncated to ${lo} of ${normalizedData.length} rows to stay within ${maxBytes} byte limit.`,
26
135
  };
27
136
  }
@@ -1,5 +1,6 @@
1
- function escapeCell(value) {
2
- return String(value ?? "")
1
+ import { formatDisplayValue } from "./format.js";
2
+ function escapeCell(value, column) {
3
+ return formatDisplayValue(value, column)
3
4
  .replace(/\r?\n|\r/g, " ")
4
5
  .replace(/\|/g, "\\|");
5
6
  }
@@ -7,17 +8,18 @@ export function formatMarkdownTable(rows, title) {
7
8
  if (rows.length === 0) {
8
9
  return title ? `**${title}**\n\nNo results found.` : "No results found.";
9
10
  }
11
+ const columns = (rows.columns) ?? undefined;
10
12
  const headers = Object.keys(rows[0]);
11
13
  const header = `| ${headers.join(" | ")} |`;
12
14
  const sep = `| ${headers.map(() => "---").join(" | ")} |`;
13
15
  const body = rows
14
- .map((row) => `| ${headers.map((h) => escapeCell(row[h])).join(" | ")} |`)
16
+ .map((row) => `| ${headers.map((h) => escapeCell(row[h], columns?.[h])).join(" | ")} |`)
15
17
  .join("\n");
16
18
  const table = [header, sep, body].join("\n");
17
19
  return title ? `**${title}**\n\n${table}` : table;
18
20
  }
19
21
  export function formatMarkdownList(obj, title) {
20
- const lines = Object.entries(obj).map(([k, v]) => `- **${k}**: ${typeof v === "object" ? JSON.stringify(v) : String(v ?? "")}`);
22
+ const lines = Object.entries(obj).map(([k, v]) => `- **${k}**: ${formatDisplayValue(v)}`);
21
23
  const list = lines.join("\n");
22
24
  return title ? `**${title}**\n\n${list}` : list;
23
25
  }
@@ -1,6 +1,16 @@
1
1
  import { strict as assert } from "node:assert";
2
2
  import { test } from "node:test";
3
3
  import { formatMarkdownTable, formatMarkdownList, formatMarkdownMultiRecordsets, } from "../../src/utils/markdown.js";
4
+ function DateTimeOffset() { }
5
+ function pad(value, width) {
6
+ return String(value).padStart(width, "0");
7
+ }
8
+ function formatUtcDateTime(date) {
9
+ return [
10
+ `${pad(date.getUTCFullYear(), 4)}-${pad(date.getUTCMonth() + 1, 2)}-${pad(date.getUTCDate(), 2)}`,
11
+ `${pad(date.getUTCHours(), 2)}:${pad(date.getUTCMinutes(), 2)}:${pad(date.getUTCSeconds(), 2)}.${pad(date.getUTCMilliseconds(), 3)}6938`,
12
+ ].join(" ");
13
+ }
4
14
  test("formatMarkdownTable - renders header and rows", () => {
5
15
  const rows = [
6
16
  { name: "Users", schema: "dbo" },
@@ -48,6 +58,16 @@ test("formatMarkdownTable - CRLF in cell values are replaced with space", () =>
48
58
  const result = formatMarkdownTable(rows);
49
59
  assert.ok(result.includes("line1 line2"));
50
60
  });
61
+ test("formatMarkdownTable - datetimeoffset values render without JS timezone string", () => {
62
+ const offsetTime = new Date(Date.UTC(2026, 3, 1, 10, 51, 23, 600));
63
+ Object.defineProperty(offsetTime, "nanosecondsDelta", { value: 0.0006938 });
64
+ const rows = Object.assign([{ offset_time: offsetTime }], {
65
+ columns: { offset_time: { type: DateTimeOffset, scale: 7 } },
66
+ });
67
+ const result = formatMarkdownTable(rows);
68
+ assert.ok(result.includes(formatUtcDateTime(offsetTime)));
69
+ assert.ok(!result.includes("GMT"));
70
+ });
51
71
  test("formatMarkdownList - renders key-value pairs", () => {
52
72
  const result = formatMarkdownList({ status: "connected", server: "localhost" });
53
73
  assert.ok(result.includes("**status**: connected"));
@@ -1,7 +1,18 @@
1
1
  import { strict as assert } from "node:assert";
2
2
  import { test } from "node:test";
3
+ import { toolSuccess, toolSuccessMarkdown } from "../../src/utils/errors.js";
3
4
  import { buildPaginationMeta, clampLimit } from "../../src/utils/pagination.js";
4
- import { truncatePayload } from "../../src/utils/format.js";
5
+ import { formatJson, truncatePayload } from "../../src/utils/format.js";
6
+ function DateTime2() { }
7
+ function pad(value, width) {
8
+ return String(value).padStart(width, "0");
9
+ }
10
+ function formatLocalDateTime(date) {
11
+ return [
12
+ `${pad(date.getFullYear(), 4)}-${pad(date.getMonth() + 1, 2)}-${pad(date.getDate(), 2)}`,
13
+ `${pad(date.getHours(), 2)}:${pad(date.getMinutes(), 2)}:${pad(date.getSeconds(), 2)}.${pad(date.getMilliseconds(), 3)}6938`,
14
+ ].join(" ");
15
+ }
5
16
  test("clampLimit - defaults to DEFAULT_PAGE_SIZE (20)", () => {
6
17
  assert.equal(clampLimit(undefined), 20);
7
18
  });
@@ -60,3 +71,25 @@ test("truncatePayload - empty array not truncated", () => {
60
71
  assert.equal(result.truncated, false);
61
72
  assert.equal(result.data.length, 0);
62
73
  });
74
+ test("formatJson - recordset datetime values use SQL-like formatting", () => {
75
+ const serverTime = new Date(2026, 3, 1, 10, 51, 23, 600);
76
+ Object.defineProperty(serverTime, "nanosecondsDelta", { value: 0.0006938 });
77
+ const rows = Object.assign([{ server_time: serverTime }], {
78
+ columns: { server_time: { type: DateTime2, scale: 7 } },
79
+ });
80
+ const result = formatJson({ recordset: rows });
81
+ assert.ok(result.includes(formatLocalDateTime(serverTime)));
82
+ assert.ok(!result.includes("GMT"));
83
+ assert.ok(!result.includes("T10:51:23"));
84
+ });
85
+ test("toolSuccessMarkdown - returns text content without structuredContent", () => {
86
+ const result = toolSuccessMarkdown("| col |\n| --- |\n| value |");
87
+ assert.equal(result.content[0].type, "text");
88
+ assert.ok(result.content[0].text.includes("| col |"));
89
+ assert.equal(result.structuredContent, undefined);
90
+ });
91
+ test("toolSuccess - keeps structuredContent for json-style responses", () => {
92
+ const result = toolSuccess("{\n \"ok\": true\n}", { ok: true });
93
+ assert.equal(result.content[0].type, "text");
94
+ assert.equal(result.structuredContent?.ok, true);
95
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mssql-mcp",
3
- "version": "2.3.1",
3
+ "version": "2.3.2",
4
4
  "description": "MCP Server for MS SQL Server integration with Claude Desktop, Cursor, Windsurf and VS Code",
5
5
  "main": "dist/src/index.js",
6
6
  "type": "module",