@vicinitysoftware/mcp-sql 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +2 -0
- package/dist/index.js +194 -0
- package/package.json +30 -0
- package/resources/Vicinity-Schema-Context.md +0 -0
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
3
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
|
+
import { CallToolRequestSchema, ListToolsRequestSchema, ListResourcesRequestSchema, ReadResourceRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
5
|
+
import * as sql from "mssql";
|
|
6
|
+
import * as fs from "fs";
|
|
7
|
+
import * as path from "path";
|
|
8
|
+
import * as url from "url";
|
|
9
|
+
import * as dotenv from "dotenv";
|
|
10
|
+
dotenv.config();
|
|
11
|
+
// ── Database config ───────────────────────────────────────────────────────────
|
|
12
|
+
const dbConfig = {
|
|
13
|
+
server: process.env.VICINITY_SQL_SERVER,
|
|
14
|
+
database: process.env.VICINITY_SQL_DATABASE,
|
|
15
|
+
user: process.env.VICINITY_SQL_USER,
|
|
16
|
+
password: process.env.VICINITY_SQL_PASSWORD,
|
|
17
|
+
port: parseInt(process.env.VICINITY_SQL_PORT || "1433"),
|
|
18
|
+
options: {
|
|
19
|
+
encrypt: process.env.VICINITY_SQL_ENCRYPT === "true",
|
|
20
|
+
trustServerCertificate: process.env.VICINITY_SQL_TRUST_SERVER_CERT !== "false",
|
|
21
|
+
connectTimeout: 15000,
|
|
22
|
+
requestTimeout: parseInt(process.env.VICINITY_SQL_TIMEOUT || "30000"),
|
|
23
|
+
},
|
|
24
|
+
pool: { max: 5, min: 0, idleTimeoutMillis: 30000 },
|
|
25
|
+
};
|
|
26
|
+
const MAX_ROWS = parseInt(process.env.VICINITY_SQL_MAX_ROWS || "500");
|
|
27
|
+
let pool;
|
|
28
|
+
async function getPool() {
|
|
29
|
+
if (!pool) {
|
|
30
|
+
pool = await sql.connect(dbConfig);
|
|
31
|
+
}
|
|
32
|
+
return pool;
|
|
33
|
+
}
|
|
34
|
+
async function runQuery(querySql, maxRows = 100) {
|
|
35
|
+
const trimmed = querySql.trim().toUpperCase();
|
|
36
|
+
if (!trimmed.startsWith("SELECT") && !trimmed.startsWith("WITH")) {
|
|
37
|
+
throw new Error("Only SELECT queries are permitted.");
|
|
38
|
+
}
|
|
39
|
+
const limit = Math.min(maxRows, MAX_ROWS);
|
|
40
|
+
const p = await getPool();
|
|
41
|
+
const result = await p.request().query(querySql);
|
|
42
|
+
const rows = result.recordset.slice(0, limit);
|
|
43
|
+
if (rows.length === 0)
|
|
44
|
+
return "Query returned no results.";
|
|
45
|
+
const columns = Object.keys(rows[0]);
|
|
46
|
+
const colWidths = columns.map(c => Math.max(c.length, ...rows.map(r => String(r[c] ?? "NULL").length)));
|
|
47
|
+
const fmt = (vals) => vals.map((v, i) => v.padEnd(colWidths[i])).join(" ");
|
|
48
|
+
const lines = [
|
|
49
|
+
fmt(columns),
|
|
50
|
+
colWidths.map(w => "-".repeat(w)).join("--"),
|
|
51
|
+
...rows.map(r => fmt(columns.map(c => String(r[c] ?? "NULL")))),
|
|
52
|
+
`\n(${rows.length} rows${result.recordset.length > limit ? " — truncated" : ""})`,
|
|
53
|
+
];
|
|
54
|
+
return lines.join("\n");
|
|
55
|
+
}
|
|
56
|
+
// ── Schema context loader ─────────────────────────────────────────────────────
|
|
57
|
+
function loadSchemaContext() {
|
|
58
|
+
const __dirname = path.dirname(url.fileURLToPath(import.meta.url));
|
|
59
|
+
const schemaFile = path.join(__dirname, "../resources/vicinity-schema-context.md");
|
|
60
|
+
const clientFile = path.join(__dirname, "../resources/vicinity-client-context.md");
|
|
61
|
+
if (!fs.existsSync(schemaFile)) {
|
|
62
|
+
return "Schema context file not found.";
|
|
63
|
+
}
|
|
64
|
+
let content = fs.readFileSync(schemaFile, "utf-8");
|
|
65
|
+
if (fs.existsSync(clientFile)) {
|
|
66
|
+
const clientContent = fs.readFileSync(clientFile, "utf-8");
|
|
67
|
+
content += "\n\n---\n\n## Client-Specific Context\n\n" + clientContent;
|
|
68
|
+
}
|
|
69
|
+
return content;
|
|
70
|
+
}
|
|
71
|
+
// ── MCP Server ────────────────────────────────────────────────────────────────
|
|
72
|
+
const server = new Server({ name: "vicinity-mcp-sql", version: "1.0.0" }, { capabilities: { tools: {}, resources: {} } });
|
|
73
|
+
// Resources
|
|
74
|
+
server.setRequestHandler(ListResourcesRequestSchema, async () => ({
|
|
75
|
+
resources: [
|
|
76
|
+
{
|
|
77
|
+
uri: "vicinity://schema-context",
|
|
78
|
+
name: "Vicinity Database Schema & Business Context",
|
|
79
|
+
description: "Table relationships, business terminology, and query patterns for Vicinity manufacturing databases",
|
|
80
|
+
mimeType: "text/markdown",
|
|
81
|
+
},
|
|
82
|
+
],
|
|
83
|
+
}));
|
|
84
|
+
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
|
|
85
|
+
if (request.params.uri === "vicinity://schema-context") {
|
|
86
|
+
return {
|
|
87
|
+
contents: [{
|
|
88
|
+
uri: request.params.uri,
|
|
89
|
+
mimeType: "text/markdown",
|
|
90
|
+
text: loadSchemaContext(),
|
|
91
|
+
}],
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
throw new Error(`Unknown resource: ${request.params.uri}`);
|
|
95
|
+
});
|
|
96
|
+
// Tools
|
|
97
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
98
|
+
tools: [
|
|
99
|
+
{
|
|
100
|
+
name: "list_tables",
|
|
101
|
+
description: "List all user tables in the Vicinity database.",
|
|
102
|
+
inputSchema: { type: "object", properties: {} },
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
name: "list_views",
|
|
106
|
+
description: "List all views in the Vicinity database.",
|
|
107
|
+
inputSchema: { type: "object", properties: {} },
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
name: "describe_table",
|
|
111
|
+
description: "Return column names, types, and nullability for a table or view.",
|
|
112
|
+
inputSchema: {
|
|
113
|
+
type: "object",
|
|
114
|
+
properties: {
|
|
115
|
+
table_name: { type: "string", description: "Table or view name" },
|
|
116
|
+
schema: { type: "string", description: "Schema name (default: dbo)" },
|
|
117
|
+
},
|
|
118
|
+
required: ["table_name"],
|
|
119
|
+
},
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
name: "run_query",
|
|
123
|
+
description: "Execute a read-only SELECT query against the Vicinity database.",
|
|
124
|
+
inputSchema: {
|
|
125
|
+
type: "object",
|
|
126
|
+
properties: {
|
|
127
|
+
sql: { type: "string", description: "SQL SELECT statement to execute" },
|
|
128
|
+
max_rows: { type: "number", description: "Maximum rows to return (default 100, max 500)" },
|
|
129
|
+
},
|
|
130
|
+
required: ["sql"],
|
|
131
|
+
},
|
|
132
|
+
},
|
|
133
|
+
],
|
|
134
|
+
}));
|
|
135
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
136
|
+
const { name, arguments: args } = request.params;
|
|
137
|
+
try {
|
|
138
|
+
let result = "";
|
|
139
|
+
if (name === "list_tables") {
|
|
140
|
+
const p = await getPool();
|
|
141
|
+
const res = await p.request().query(`
|
|
142
|
+
SELECT TABLE_SCHEMA, TABLE_NAME
|
|
143
|
+
FROM INFORMATION_SCHEMA.TABLES
|
|
144
|
+
WHERE TABLE_TYPE = 'BASE TABLE'
|
|
145
|
+
ORDER BY TABLE_SCHEMA, TABLE_NAME
|
|
146
|
+
`);
|
|
147
|
+
result = res.recordset.map((r) => `${r.TABLE_SCHEMA}.${r.TABLE_NAME}`).join("\n");
|
|
148
|
+
}
|
|
149
|
+
else if (name === "list_views") {
|
|
150
|
+
const p = await getPool();
|
|
151
|
+
const res = await p.request().query(`
|
|
152
|
+
SELECT TABLE_SCHEMA, TABLE_NAME
|
|
153
|
+
FROM INFORMATION_SCHEMA.VIEWS
|
|
154
|
+
ORDER BY TABLE_SCHEMA, TABLE_NAME
|
|
155
|
+
`);
|
|
156
|
+
result = res.recordset.map((r) => `${r.TABLE_SCHEMA}.${r.TABLE_NAME}`).join("\n") || "No views found.";
|
|
157
|
+
}
|
|
158
|
+
else if (name === "describe_table") {
|
|
159
|
+
const p = await getPool();
|
|
160
|
+
const res = await p.request()
|
|
161
|
+
.input("table", sql.NVarChar, args?.table_name)
|
|
162
|
+
.input("schema", sql.NVarChar, args?.schema ?? "dbo")
|
|
163
|
+
.query(`
|
|
164
|
+
SELECT COLUMN_NAME, DATA_TYPE, IS_NULLABLE, CHARACTER_MAXIMUM_LENGTH
|
|
165
|
+
FROM INFORMATION_SCHEMA.COLUMNS
|
|
166
|
+
WHERE TABLE_NAME = @table AND TABLE_SCHEMA = @schema
|
|
167
|
+
ORDER BY ORDINAL_POSITION
|
|
168
|
+
`);
|
|
169
|
+
if (res.recordset.length === 0) {
|
|
170
|
+
result = `Table '${args?.schema ?? "dbo"}.${args?.table_name}' not found.`;
|
|
171
|
+
}
|
|
172
|
+
else {
|
|
173
|
+
const lines = [`${"Column".padEnd(40)} ${"Type".padEnd(20)} ${"Nullable".padEnd(10)} MaxLen`, "-".repeat(80)];
|
|
174
|
+
for (const r of res.recordset) {
|
|
175
|
+
lines.push(`${String(r.COLUMN_NAME).padEnd(40)} ${String(r.DATA_TYPE).padEnd(20)} ${String(r.IS_NULLABLE).padEnd(10)} ${r.CHARACTER_MAXIMUM_LENGTH ?? ""}`);
|
|
176
|
+
}
|
|
177
|
+
result = lines.join("\n");
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
else if (name === "run_query") {
|
|
181
|
+
result = await runQuery(String(args?.sql), args?.max_rows);
|
|
182
|
+
}
|
|
183
|
+
else {
|
|
184
|
+
throw new Error(`Unknown tool: ${name}`);
|
|
185
|
+
}
|
|
186
|
+
return { content: [{ type: "text", text: result }] };
|
|
187
|
+
}
|
|
188
|
+
catch (err) {
|
|
189
|
+
return { content: [{ type: "text", text: `Error: ${err.message}` }], isError: true };
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
// ── Start ─────────────────────────────────────────────────────────────────────
|
|
193
|
+
const transport = new StdioServerTransport();
|
|
194
|
+
await server.connect(transport);
|
package/package.json
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@vicinitysoftware/mcp-sql",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "MCP server for Vicinity Software SQL databases",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"vicinity-mcp-sql": "dist/index.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"dist/",
|
|
12
|
+
"resources/",
|
|
13
|
+
"README.md"
|
|
14
|
+
],
|
|
15
|
+
"engines": { "node": ">=18.0.0" },
|
|
16
|
+
"scripts": {
|
|
17
|
+
"build": "tsc",
|
|
18
|
+
"prepublishOnly": "npm run build"
|
|
19
|
+
},
|
|
20
|
+
"dependencies": {
|
|
21
|
+
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
22
|
+
"dotenv": "^16.0.0",
|
|
23
|
+
"mssql": "^11.0.0"
|
|
24
|
+
},
|
|
25
|
+
"devDependencies": {
|
|
26
|
+
"@types/mssql": "^9.0.0",
|
|
27
|
+
"@types/node": "^20.0.0",
|
|
28
|
+
"typescript": "^5.0.0"
|
|
29
|
+
}
|
|
30
|
+
}
|
|
Binary file
|